Skip to content

Commit

Permalink
Reland of Enable whitelist generation for official builds.
Browse files Browse the repository at this point in the history
Previous: https://codereview.chromium.org/2271673002/

Currently, all resources are included in PAK files when Chrome is
built locally. Only official_buildbot.sh uses a resource whitelist. This
CL enables local builds to use resource whitelisting by setting the
enable_resource_whitelist_generation gn flag to true, or by building an
official build.

This will allow developers to more easily monitor the changes in APK
size for each commit they make.

However, a large amount of output is generated (_pragma is used to
create warnings to allow whitelisted resources to be listed), so for now
the whitelist will only be generated for official builds.

This change results in a ~1.5 mb difference when calculating the APK
size with resource_sizes.py.

BUG=632385

Review-Url: https://codereview.chromium.org/2272713004
Cr-Commit-Position: refs/heads/master@{#414148}
  • Loading branch information
estevenson authored and Commit bot committed Aug 24, 2016
1 parent db792de commit fa33a5b
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 89 deletions.
25 changes: 10 additions & 15 deletions build/toolchain/gcc_ar_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,7 @@
import subprocess
import sys


# When running on a Windows host and using a toolchain whose tools are
# actually wrapper scripts (i.e. .bat files on Windows) rather than binary
# executables, the "command" to run has to be prefixed with this magic.
# The GN toolchain definitions take care of that for when GN/Ninja is
# running the tool directly. When that command is passed in to this
# script, it appears as a unitary string but needs to be split up so that
# just 'cmd' is the actual command given to Python's subprocess module.
BAT_PREFIX = 'cmd /c call '

def CommandToRun(command):
if command[0].startswith(BAT_PREFIX):
command = command[0].split(None, 3) + command[1:]
return command
import wrapper_utils


def main():
Expand All @@ -44,12 +31,20 @@ def main():
metavar='ARCHIVE')
parser.add_argument('--plugin',
help='Load plugin')
parser.add_argument('--resource-whitelist',
help='Merge all resource whitelists into a single file.',
metavar='PATH')
parser.add_argument('operation',
help='Operation on the archive')
parser.add_argument('inputs', nargs='+',
help='Input files')
args = parser.parse_args()

if args.resource_whitelist:
whitelist_candidates = wrapper_utils.ResolveRspLinks(args.inputs)
wrapper_utils.CombineResourceWhitelists(
whitelist_candidates, args.resource_whitelist)

command = [args.ar, args.operation]
if args.plugin is not None:
command += ['--plugin', args.plugin]
Expand All @@ -64,7 +59,7 @@ def main():
raise

# Now just run the ar command.
return subprocess.call(CommandToRun(command))
return subprocess.call(wrapper_utils.CommandToRun(command))


if __name__ == "__main__":
Expand Down
43 changes: 43 additions & 0 deletions build/toolchain/gcc_compile_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Runs a compilation command.
This script exists to avoid using complex shell commands in
gcc_toolchain.gni's tool("cxx") and tool("cc") in case the host running the
compiler does not have a POSIX-like shell (e.g. Windows).
"""

import argparse
import sys

import wrapper_utils


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--resource-whitelist',
help='Generate a resource whitelist for this target.',
metavar='PATH')
parser.add_argument('command', nargs=argparse.REMAINDER,
help='Compilation command')
args = parser.parse_args()

returncode, stderr = wrapper_utils.CaptureCommandStderr(
wrapper_utils.CommandToRun(args.command))

used_resources = wrapper_utils.ExtractResourceIdsFromPragmaWarnings(stderr)
sys.stderr.write(stderr)

if args.resource_whitelist:
with open(args.resource_whitelist, 'w') as f:
if used_resources:
f.write('\n'.join(str(resource) for resource in used_resources))
f.write('\n')

return returncode

if __name__ == "__main__":
sys.exit(main())
37 changes: 16 additions & 21 deletions build/toolchain/gcc_solink_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,17 @@

import argparse
import os
import re
import subprocess
import sys


# When running on a Windows host and using a toolchain whose tools are
# actually wrapper scripts (i.e. .bat files on Windows) rather than binary
# executables, the "command" to run has to be prefixed with this magic.
# The GN toolchain definitions take care of that for when GN/Ninja is
# running the tool directly. When that command is passed in to this
# script, it appears as a unitary string but needs to be split up so that
# just 'cmd' is the actual command given to Python's subprocess module.
BAT_PREFIX = 'cmd /c call '

def CommandToRun(command):
if command[0].startswith(BAT_PREFIX):
command = command[0].split(None, 3) + command[1:]
return command
import wrapper_utils


def CollectSONAME(args):
"""Replaces: readelf -d $sofile | grep SONAME"""
toc = ''
readelf = subprocess.Popen(CommandToRun([args.readelf, '-d', args.sofile]),
stdout=subprocess.PIPE, bufsize=-1)
readelf = subprocess.Popen(wrapper_utils.CommandToRun(
[args.readelf, '-d', args.sofile]), stdout=subprocess.PIPE, bufsize=-1)
for line in readelf.stdout:
if 'SONAME' in line:
toc += line
Expand All @@ -46,7 +32,7 @@ def CollectSONAME(args):
def CollectDynSym(args):
"""Replaces: nm --format=posix -g -D $sofile | cut -f1-2 -d' '"""
toc = ''
nm = subprocess.Popen(CommandToRun([
nm = subprocess.Popen(wrapper_utils.CommandToRun([
args.nm, '--format=posix', '-g', '-D', args.sofile]),
stdout=subprocess.PIPE, bufsize=-1)
for line in nm.stdout:
Expand Down Expand Up @@ -96,6 +82,9 @@ def main():
required=True,
help='Final output shared object file',
metavar='FILE')
parser.add_argument('--resource-whitelist',
help='Merge all resource whitelists into a single file.',
metavar='PATH')
parser.add_argument('command', nargs='+',
help='Linking command')
args = parser.parse_args()
Expand All @@ -104,8 +93,14 @@ def main():
fast_env = dict(os.environ)
fast_env['LC_ALL'] = 'C'

if args.resource_whitelist:
whitelist_candidates = wrapper_utils.ResolveRspLinks(args.command)
wrapper_utils.CombineResourceWhitelists(
whitelist_candidates, args.resource_whitelist)

# First, run the actual link.
result = subprocess.call(CommandToRun(args.command), env=fast_env)
result = subprocess.call(
wrapper_utils.CommandToRun(args.command), env=fast_env)
if result != 0:
return result

Expand All @@ -120,8 +115,8 @@ def main():

# Finally, strip the linked shared object file (if desired).
if args.strip:
result = subprocess.call(CommandToRun([args.strip, '--strip-unneeded',
'-o', args.output, args.sofile]))
result = subprocess.call(wrapper_utils.CommandToRun(
[args.strip, '--strip-unneeded', '-o', args.output, args.sofile]))

return result

Expand Down
35 changes: 33 additions & 2 deletions build/toolchain/gcc_toolchain.gni
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import("//build/config/v8_target_cpu.gni")
import("//build/toolchain/cc_wrapper.gni")
import("//build/toolchain/goma.gni")
import("//build/toolchain/toolchain.gni")
import("//tools/grit/grit_rule.gni")

# This template defines a toolchain for something that works like gcc
# (including clang).
Expand Down Expand Up @@ -220,23 +221,41 @@ template("gcc_toolchain") {
object_subdir = "{{target_out_dir}}/{{label_name}}"

tool("cc") {
whitelist_flag = " "
if (enable_resource_whitelist_generation) {
whitelist_flag = " --resource-whitelist=\"{{output}}.whitelist\""
}
depfile = "{{output}}.d"
command = "$cc -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}}${extra_cppflags}${extra_cflags} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CC {{output}}"
outputs = [
# The whitelist file is also an output, but ninja does not
# currently support multiple outputs for tool("cc").
"$object_subdir/{{source_name_part}}.o",
]
compile_wrapper = rebase_path("//build/toolchain/gcc_compile_wrapper.py",
root_build_dir)
command = "$python_path \"$compile_wrapper\"$whitelist_flag $command"
}

tool("cxx") {
whitelist_flag = " "
if (enable_resource_whitelist_generation) {
whitelist_flag = " --resource-whitelist=\"{{output}}.whitelist\""
}
depfile = "{{output}}.d"
command = "$cxx -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}}${extra_cppflags}${extra_cxxflags} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CXX {{output}}"
outputs = [
# The whitelist file is also an output, but ninja does not
# currently support multiple outputs for tool("cxx").
"$object_subdir/{{source_name_part}}.o",
]
compile_wrapper = rebase_path("//build/toolchain/gcc_compile_wrapper.py",
root_build_dir)
command = "$python_path \"$compile_wrapper\"$whitelist_flag $command"
}

tool("asm") {
Expand All @@ -252,13 +271,17 @@ template("gcc_toolchain") {

tool("alink") {
rspfile = "{{output}}.rsp"
whitelist_flag = " "
if (enable_resource_whitelist_generation) {
whitelist_flag = " --resource-whitelist=\"{{output}}.whitelist\""
}

# This needs a Python script to avoid using simple sh features in this
# command, in case the host does not use a POSIX shell (e.g. compiling
# POSIX-like toolchains such as NaCl on Windows).
ar_wrapper =
rebase_path("//build/toolchain/gcc_ar_wrapper.py", root_build_dir)
command = "$python_path \"$ar_wrapper\" --output={{output}} --ar=\"$ar\" {{arflags}} rcsD @\"$rspfile\""
command = "$python_path \"$ar_wrapper\"$whitelist_flag --output={{output}} --ar=\"$ar\" {{arflags}} rcsD @\"$rspfile\""
description = "AR {{output}}"
rspfile_content = "{{inputs}}"
outputs = [
Expand All @@ -277,6 +300,11 @@ template("gcc_toolchain") {
sofile = "{{output_dir}}/$soname" # Possibly including toolchain dir.
rspfile = sofile + ".rsp"
pool = "//build/toolchain:link_pool($default_toolchain)"
whitelist_flag = " "
if (enable_resource_whitelist_generation) {
whitelist_file = "$sofile.whitelist"
whitelist_flag = " --resource-whitelist=\"$whitelist_file\""
}

if (defined(invoker.strip)) {
unstripped_sofile = "{{root_out_dir}}/lib.unstripped/$soname"
Expand All @@ -303,7 +331,7 @@ template("gcc_toolchain") {
# requiring sh control structures, pipelines, and POSIX utilities.
# The host might not have a POSIX shell and utilities (e.g. Windows).
solink_wrapper = rebase_path("//build/toolchain/gcc_solink_wrapper.py")
command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch --sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\" --output=\"$sofile\" -- $link_command"
command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch --sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\" --output=\"$sofile\"$whitelist_flag -- $link_command"

rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive $solink_libs_section_prefix {{libs}} $solink_libs_section_postfix"

Expand Down Expand Up @@ -332,6 +360,9 @@ template("gcc_toolchain") {
sofile,
tocfile,
]
if (enable_resource_whitelist_generation) {
outputs += [ whitelist_file ]
}
if (sofile != unstripped_sofile) {
outputs += [ unstripped_sofile ]
}
Expand Down
105 changes: 105 additions & 0 deletions build/toolchain/wrapper_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright (c) 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Helper functions for gcc_toolchain.gni wrappers."""

import os
import re
import subprocess
import shlex
import sys

_BAT_PREFIX = 'cmd /c call '
_WHITELIST_RE = re.compile('whitelisted_resource_(?P<resource_id>[0-9]+)')


def CommandToRun(command):
"""Generates commands compatible with Windows.
When running on a Windows host and using a toolchain whose tools are
actually wrapper scripts (i.e. .bat files on Windows) rather than binary
executables, the |command| to run has to be prefixed with this magic.
The GN toolchain definitions take care of that for when GN/Ninja is
running the tool directly. When that command is passed in to this
script, it appears as a unitary string but needs to be split up so that
just 'cmd' is the actual command given to Python's subprocess module.
Args:
command: List containing the UNIX style |command|.
Returns:
A list containing the Windows version of the |command|.
"""
if command[0].startswith(_BAT_PREFIX):
command = command[0].split(None, 3) + command[1:]
return command


def ResolveRspLinks(inputs):
"""Return a list of files contained in a response file.
Args:
inputs: A command containing rsp files.
Returns:
A set containing the rsp file content."""
rspfiles = [a[1:] for a in inputs if a.startswith('@')]
resolved = set()
for rspfile in rspfiles:
with open(rspfile, 'r') as f:
resolved.update(shlex.split(f.read()))

return resolved


def CombineResourceWhitelists(whitelist_candidates, outfile):
"""Combines all whitelists for a resource file into a single whitelist.
Args:
whitelist_candidates: List of paths to rsp files containing all targets.
outfile: Path to save the combined whitelist.
"""
whitelists = ('%s.whitelist' % candidate for candidate in whitelist_candidates
if os.path.exists('%s.whitelist' % candidate))

resources = set()
for whitelist in whitelists:
with open(whitelist, 'r') as f:
resources.update(f.readlines())

with open(outfile, 'w') as f:
f.writelines(resources)


def ExtractResourceIdsFromPragmaWarnings(text):
"""Returns set of resource IDs that are inside unknown pragma warnings.
Args:
text: The text that will be scanned for unknown pragma warnings.
Returns:
A set containing integers representing resource IDs.
"""
used_resources = set()
lines = text.splitlines()
for ln in lines:
match = _WHITELIST_RE.search(ln)
if match:
resource_id = int(match.group('resource_id'))
used_resources.add(resource_id)

return used_resources


def CaptureCommandStderr(command):
"""Returns the stderr of a command.
Args:
args: A list containing the command and arguments.
cwd: The working directory from where the command should be made.
env: Environment variables for the new process.
"""
child = subprocess.Popen(command, stderr=subprocess.PIPE)
_, stderr = child.communicate()
return child.returncode, stderr
Loading

0 comments on commit fa33a5b

Please sign in to comment.