Commit 0d9deacd authored by Andrew Grieve's avatar Andrew Grieve Committed by Commit Bot

Android: Refactor proguard.py to simplify it.

* optparse -> argparse
* Removes proguard_util.py by inlining relevant logic
* Ensures all outputs are written to temporary files,
  and then moved into place upon success
* Fixes --proguard-config-exclusions not being applied
  for R8.
* Fixes -assumevalues sdkint expression for non-R8
  (it failed for -assumenosideeffects).
* Unifies output hiding logic for R8/ProGuard
* Makes --expected-configs-file work for non-R8 mode
  * And runs it before the proguard step for faster feedback.
* Removes ProGuard-only --apply-mapping logic of stripping
  methods (no longer relevant).

Change-Id: I91aa80a52afbba674b4bbc10f41f7470142daa7e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1752842
Commit-Queue: Andrew Grieve <agrieve@chromium.org>
Reviewed-by: default avatarSam Maier <smaier@chromium.org>
Cr-Commit-Position: refs/heads/master@{#687401}
parent e352dbe8
...@@ -12,7 +12,6 @@ import tempfile ...@@ -12,7 +12,6 @@ import tempfile
import zipfile import zipfile
from util import build_utils from util import build_utils
from util import proguard_util
def main(args): def main(args):
......
...@@ -5,4 +5,3 @@ main_dex_list.py ...@@ -5,4 +5,3 @@ main_dex_list.py
util/__init__.py util/__init__.py
util/build_utils.py util/build_utils.py
util/md5_check.py util/md5_check.py
util/proguard_util.py
...@@ -4,114 +4,132 @@ ...@@ -4,114 +4,132 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import cStringIO import argparse
import optparse
import os import os
import re import re
import shutil import shutil
import sys import sys
import tempfile
from util import build_utils from util import build_utils
from util import diff_utils from util import diff_utils
from util import proguard_util
_GENERATED_PROGUARD_HEADER = """ class _ProguardOutputFilter(object):
################################################################################ """ProGuard outputs boring stuff to stdout (ProGuard version, jar path, etc)
# Dynamically generated from build/android/gyp/proguard.py as well as interesting stuff (notes, warnings, etc). If stdout is entirely
################################################################################ boring, this class suppresses the output.
""" """
# Example: IGNORE_RE = re.compile(
# android.arch.core.internal.SafeIterableMap$Entry -> b: r'Pro.*version|Note:|Reading|Preparing|Printing|ProgramClass:|Searching|'
# 1:1:java.lang.Object getKey():353:353 -> getKey r'jar \[|\d+ class path entries checked')
# 2:2:java.lang.Object getValue():359:359 -> getValue
def _RemoveMethodMappings(orig_path, out_fd): def __init__(self):
with open(orig_path) as in_fd: self._last_line_ignored = False
for line in in_fd: self._ignore_next_line = False
if line[:1] != ' ':
out_fd.write(line) def __call__(self, output):
out_fd.flush() ret = []
for line in output.splitlines(True):
if self._ignore_next_line:
def _ParseOptions(args): self._ignore_next_line = False
parser = optparse.OptionParser() continue
if '***BINARY RUN STATS***' in line:
self._last_line_ignored = True
self._ignore_next_line = True
elif not line.startswith(' '):
self._last_line_ignored = bool(self.IGNORE_RE.match(line))
elif 'You should check if you need to specify' in line:
self._last_line_ignored = True
if not self._last_line_ignored:
ret.append(line)
return ''.join(ret)
def _ParseOptions():
args = build_utils.ExpandFileArgs(sys.argv[1:])
parser = argparse.ArgumentParser()
build_utils.AddDepfileOption(parser) build_utils.AddDepfileOption(parser)
parser.add_option('--proguard-path', group = parser.add_mutually_exclusive_group(required=True)
help='Path to the proguard.jar to use.') group.add_argument('--proguard-path', help='Path to the proguard.jar to use.')
parser.add_option('--r8-path', group.add_argument('--r8-path', help='Path to the R8.jar to use.')
help='Path to the R8.jar to use.') parser.add_argument(
parser.add_option('--input-paths', '--input-paths', required=True, help='GN-list of .jar files to optimize.')
help='Paths to the .jar files proguard should run on.') parser.add_argument(
parser.add_option('--output-path', help='Path to the generated .jar file.') '--output-path', required=True, help='Path to the generated .jar file.')
parser.add_option('--proguard-configs', action='append', parser.add_argument(
help='Paths to proguard configuration files.') '--proguard-configs',
parser.add_option('--proguard-config-exclusions', action='append',
default='', required=True,
help='GN list of paths to proguard configuration files ' help='GN-list of configuration files.')
'included by --proguard-configs, but that should ' parser.add_argument(
'not actually be included.') '--proguard-config-exclusions',
parser.add_option( help='GN-list of paths to filter out of --proguard-configs')
'--apply-mapping', help='Path to proguard mapping to apply.') parser.add_argument(
parser.add_option('--mapping-output', '--apply-mapping', help='Path to ProGuard mapping to apply.')
help='Path for proguard to output mapping file to.') parser.add_argument(
parser.add_option( '--mapping-output',
required=True,
help='Path for ProGuard to output mapping file to.')
parser.add_argument(
'--extra-mapping-output-paths', '--extra-mapping-output-paths',
help='Additional paths to copy output mapping file to.') help='GN-list of additional paths to copy output mapping file to.')
parser.add_option( parser.add_argument(
'--output-config', '--output-config',
help='Path to write the merged proguard config file to.') help='Path to write the merged ProGuard config file to.')
parser.add_option( parser.add_argument(
'--expected-configs-file', '--expected-configs-file',
help='Path to a file containing the expected merged proguard configs') help='Path to a file containing the expected merged ProGuard configs')
parser.add_option( parser.add_argument(
'--verify-expected-configs', '--verify-expected-configs',
action='store_true', action='store_true',
help='Fail if the expected merged proguard configs differ from the ' help='Fail if the expected merged ProGuard configs differ from the '
'generated merged proguard configs.') 'generated merged ProGuard configs.')
parser.add_option('--classpath', action='append', parser.add_argument(
help='Classpath for proguard.') '--classpath',
parser.add_option('--main-dex-rules-path', action='append', action='append',
help='Paths to main dex rules for multidex' help='GN-list of .jar files to include as libraries.')
'- only works with R8.') parser.add_argument(
parser.add_option('--min-api', default='', '--main-dex-rules-path',
help='Minimum Android API level compatibility.') action='append',
parser.add_option('--verbose', '-v', action='store_true', help='Path to main dex rules for multidex'
help='Print all proguard output') '- only works with R8.')
parser.add_option( parser.add_argument(
'--min-api', help='Minimum Android API level compatibility.')
parser.add_argument(
'--verbose', '-v', action='store_true', help='Print all ProGuard output')
parser.add_argument(
'--repackage-classes', '--repackage-classes',
help='Unique package name given to an asynchronously proguarded module') help='Unique package name given to an asynchronously proguarded module')
parser.add_option( parser.add_argument(
'--disable-outlining', '--disable-outlining',
action='store_true', action='store_true',
help='Disable the outlining optimization provided by R8.') help='Disable the outlining optimization provided by R8.')
options, _ = parser.parse_args(args) options = parser.parse_args(args)
assert not options.main_dex_rules_path or options.r8_path, \ if options.main_dex_rules_path and not options.r8_path:
'R8 must be enabled to pass main dex rules.' parser.error('R8 must be enabled to pass main dex rules.')
classpath = [] if options.expected_configs_file and not options.output_config:
for arg in options.classpath: parser.error('--expected-configs-file requires --output-config')
classpath += build_utils.ParseGnList(arg)
options.classpath = classpath
configs = [] if options.proguard_path and options.disable_outlining:
for arg in options.proguard_configs: parser.error('--disable-outlining requires --r8-path')
configs += build_utils.ParseGnList(arg)
options.proguard_configs = configs
options.proguard_config_exclusions = (
build_utils.ParseGnList(options.proguard_config_exclusions))
options.classpath = build_utils.ParseGnList(options.classpath)
options.proguard_configs = build_utils.ParseGnList(options.proguard_configs)
options.proguard_config_exclusions = (build_utils.ParseGnList(
options.proguard_config_exclusions))
options.input_paths = build_utils.ParseGnList(options.input_paths) options.input_paths = build_utils.ParseGnList(options.input_paths)
options.extra_mapping_output_paths = build_utils.ParseGnList(
if not options.mapping_output: options.extra_mapping_output_paths)
options.mapping_output = options.output_path + '.mapping'
if options.apply_mapping: if options.apply_mapping:
options.apply_mapping = os.path.abspath(options.apply_mapping) options.apply_mapping = os.path.abspath(options.apply_mapping)
return options return options
...@@ -121,7 +139,7 @@ def _VerifyExpectedConfigs(expected_path, actual_path, fail_on_exit): ...@@ -121,7 +139,7 @@ def _VerifyExpectedConfigs(expected_path, actual_path, fail_on_exit):
return return
sys.stderr.write("""\ sys.stderr.write("""\
Proguard flag expectations file needs updating. For details see: ProGuard flag expectations file needs updating. For details see:
https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/README.md https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/README.md
""") """)
sys.stderr.write(msg) sys.stderr.write(msg)
...@@ -129,66 +147,190 @@ https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/README ...@@ -129,66 +147,190 @@ https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/README
sys.exit(1) sys.exit(1)
def _MoveTempDexFile(tmp_dex_dir, dex_path): def _OptimizeWithR8(options,
"""Move the temp dex file out of |tmp_dex_dir|. config_paths,
libraries,
Args: dynamic_config_data,
tmp_dex_dir: Path to temporary directory created with tempfile.mkdtemp(). print_stdout=False):
The directory should have just a single file. with build_utils.TempDir() as tmp_dir:
dex_path: Target path to move dex file to. if dynamic_config_data:
tmp_config_path = os.path.join(tmp_dir, 'proguard_config.txt')
Raises: with open(tmp_config_path, 'w') as f:
Exception if there are multiple files in |tmp_dex_dir|. f.write(dynamic_config_data)
""" config_paths = config_paths + [tmp_config_path]
tempfiles = os.listdir(tmp_dex_dir)
if len(tempfiles) > 1: tmp_mapping_path = os.path.join(tmp_dir, 'mapping.txt')
raise Exception('%d files created, expected 1' % len(tempfiles)) # If there is no output (no classes are kept), this prevents this script
# from failing.
tmp_dex_path = os.path.join(tmp_dex_dir, tempfiles[0]) build_utils.Touch(tmp_mapping_path)
shutil.move(tmp_dex_path, dex_path)
output_is_zipped = not options.output_path.endswith('.dex')
tmp_output = os.path.join(tmp_dir, 'r8out')
def _CreateR8Command(options, map_output_path, output_dir, tmp_config_path, if output_is_zipped:
libraries): tmp_output += '.jar'
cmd = [ else:
'java', '-jar', options.r8_path, os.mkdir(tmp_output)
'--no-desugaring',
'--no-data-resources', cmd = [
'--output', output_dir, 'java',
'--pg-map-output', map_output_path, '-jar',
] options.r8_path,
'--no-desugaring',
for lib in libraries: '--no-data-resources',
cmd += ['--lib', lib] '--output',
tmp_output,
for config_file in options.proguard_configs: '--pg-map-output',
cmd += ['--pg-conf', config_file] tmp_mapping_path,
]
temp_config_string = ''
if options.apply_mapping or options.repackage_classes: for lib in libraries:
with open(tmp_config_path, 'w') as f: cmd += ['--lib', lib]
if options.apply_mapping:
temp_config_string += '-applymapping \'%s\'\n' % (options.apply_mapping) for config_file in config_paths:
if options.repackage_classes: cmd += ['--pg-conf', config_file]
temp_config_string += '-repackageclasses \'%s\'\n' % (
options.repackage_classes) if options.min_api:
f.write(temp_config_string) cmd += ['--min-api', options.min_api]
cmd += ['--pg-conf', tmp_config_path]
if options.main_dex_rules_path:
for main_dex_rule in options.main_dex_rules_path:
cmd += ['--main-dex-rules', main_dex_rule]
cmd += options.input_paths
stderr_filter = None
env = os.environ.copy()
if options.disable_outlining:
stderr_filter = lambda l: re.sub(r'.*_JAVA_OPTIONS.*\n?', '', l)
env['_JAVA_OPTIONS'] = '-Dcom.android.tools.r8.disableOutlining=1'
build_utils.CheckOutput(
cmd, env=env, print_stdout=print_stdout, stderr_filter=stderr_filter)
if not output_is_zipped:
found_files = os.listdir(tmp_output)
if len(found_files) > 1:
raise Exception('Too many files created: {}'.format(found_files))
tmp_output = os.path.join(tmp_output, found_files[0])
# Copy output files to correct locations.
shutil.move(tmp_output, options.output_path)
with open(options.mapping_output, 'w') as out_file, \
open(tmp_mapping_path) as in_file:
# Mapping files generated by R8 include comments that may break
# some of our tooling so remove those (specifically: apkanalyzer).
out_file.writelines(l for l in in_file if not l.startswith('#'))
def _OptimizeWithProguard(options,
config_paths,
libraries,
dynamic_config_data,
print_stdout=False):
with build_utils.TempDir() as tmp_dir:
combined_injars_path = os.path.join(tmp_dir, 'injars.jar')
combined_libjars_path = os.path.join(tmp_dir, 'libjars.jar')
combined_proguard_configs_path = os.path.join(tmp_dir, 'includes.txt')
tmp_mapping_path = os.path.join(tmp_dir, 'mapping.txt')
tmp_output_jar = os.path.join(tmp_dir, 'output.jar')
build_utils.MergeZips(combined_injars_path, options.input_paths)
build_utils.MergeZips(combined_libjars_path, libraries)
with open(combined_proguard_configs_path, 'w') as f:
f.write(_CombineConfigs(config_paths, dynamic_config_data))
if options.proguard_path.endswith('.jar'):
cmd = [
'java', '-jar', options.proguard_path, '-include',
combined_proguard_configs_path
]
else:
cmd = [options.proguard_path, '@' + combined_proguard_configs_path]
cmd += [
'-forceprocessing',
'-libraryjars',
combined_libjars_path,
'-injars',
combined_injars_path,
'-outjars',
tmp_output_jar,
'-printmapping',
tmp_mapping_path,
]
# Warning: and Error: are sent to stderr, but messages and Note: are sent
# to stdout.
stdout_filter = None
stderr_filter = None
if print_stdout:
stdout_filter = _ProguardOutputFilter()
stderr_filter = _ProguardOutputFilter()
build_utils.CheckOutput(
cmd,
print_stdout=True,
print_stderr=True,
stdout_filter=stdout_filter,
stderr_filter=stderr_filter)
# ProGuard will skip writing if the file would be empty.
build_utils.Touch(tmp_mapping_path)
# Copy output files to correct locations.
shutil.move(tmp_output_jar, options.output_path)
shutil.move(tmp_mapping_path, options.mapping_output)
def _CombineConfigs(configs, dynamic_config_data, exclude_generated=False):
ret = []
def add_header(name):
ret.append('#' * 80)
ret.append('# ' + name)
ret.append('#' * 80)
for config in sorted(configs):
if exclude_generated and config.endswith('.resources.proguard.txt'):
continue
add_header(config)
with open(config) as config_file:
contents = config_file.read().rstrip()
# Fix up line endings (third_party configs can have windows endings).
contents = contents.replace('\r', '')
# Remove numbers from generated rule comments to make file more
# diff'able.
contents = re.sub(r' #generated:\d+', '', contents)
ret.append(contents)
ret.append('')
if dynamic_config_data:
add_header('Dynamically generated from build/android/gyp/proguard.py')
ret.append(dynamic_config_data)
ret.append('')
return '\n'.join(ret)
def _CreateDynamicConfig(options):
ret = []
if not options.r8_path and options.min_api:
# R8 adds this option automatically, and uses -assumenosideeffects instead
# (which ProGuard doesn't support doing).
ret.append("""\
-assumevalues class android.os.Build$VERSION {
public static final int SDK_INT return %s..9999;
}""" % options.min_api)
if options.min_api: if options.apply_mapping:
cmd += ['--min-api', options.min_api] ret.append("-applymapping '%s'" % options.apply_mapping)
if options.repackage_classes:
if options.main_dex_rules_path: ret.append("-repackageclasses '%s'" % options.repackage_classes)
for main_dex_rule in options.main_dex_rules_path: return '\n'.join(ret)
cmd += ['--main-dex-rules', main_dex_rule]
cmd += options.input_paths
return cmd, temp_config_string
def main(args): def main():
args = build_utils.ExpandFileArgs(args) options = _ParseOptions()
options = _ParseOptions(args)
libraries = [] libraries = []
for p in options.classpath: for p in options.classpath:
...@@ -196,118 +338,52 @@ def main(args): ...@@ -196,118 +338,52 @@ def main(args):
if p not in libraries and p not in options.input_paths: if p not in libraries and p not in options.input_paths:
libraries.append(p) libraries.append(p)
# TODO(agrieve): Remove proguard usages. # Apply config exclusion filter.
if options.r8_path: config_paths = [
temp_config_string = '' p for p in options.proguard_configs
with build_utils.TempDir() as tmp_dir: if p not in options.proguard_config_exclusions
tmp_mapping_path = os.path.join(tmp_dir, 'mapping.txt') ]
tmp_proguard_config_path = os.path.join(tmp_dir, 'proguard_config.txt')
# If there is no output (no classes are kept), this prevents this script # ProGuard configs that are derived from flags.
# from failing. dynamic_config_data = _CreateDynamicConfig(options)
build_utils.Touch(tmp_mapping_path)
# ProGuard configs that are derived from flags.
f = cStringIO.StringIO() merged_configs = _CombineConfigs(
proguard_util.WriteFlagsFile( config_paths, dynamic_config_data, exclude_generated=True)
options.proguard_configs, f, exclude_generated=True) print_stdout = '-whyareyoukeeping' in merged_configs or options.verbose
merged_configs = f.getvalue()
# Fix up line endings (third_party configs can have windows endings) # Writing the config output before we know ProGuard is going to succeed isn't
merged_configs = merged_configs.replace('\r', '') # great, since then a failure will result in one of the outputs being updated.
f.close() # We do it anyways though because the error message prints out the path to the
print_stdout = '-whyareyoukeeping' in merged_configs # config. Ninja will still know to re-run the command because of the other
# stale outputs.
def run_r8(cmd): if options.output_config:
stderr_filter = None with open(options.output_config, 'w') as f:
env = os.environ.copy()
if options.disable_outlining:
stderr_filter = lambda l: re.sub(r'.*_JAVA_OPTIONS.*\n?', '', l)
env['_JAVA_OPTIONS'] = '-Dcom.android.tools.r8.disableOutlining=1'
build_utils.CheckOutput(
cmd,
env=env,
print_stdout=print_stdout,
stderr_filter=stderr_filter)
if options.output_path.endswith('.dex'):
with build_utils.TempDir() as tmp_dex_dir:
cmd, temp_config_string = _CreateR8Command(
options, tmp_mapping_path, tmp_dex_dir, tmp_proguard_config_path,
libraries)
run_r8(cmd)
_MoveTempDexFile(tmp_dex_dir, options.output_path)
else:
cmd, temp_config_string = _CreateR8Command(
options, tmp_mapping_path, options.output_path,
tmp_proguard_config_path, libraries)
run_r8(cmd)
# Copy output files to correct locations.
with build_utils.AtomicOutput(options.mapping_output) as mapping:
# Mapping files generated by R8 include comments that may break
# some of our tooling so remove those.
with open(tmp_mapping_path) as tmp:
mapping.writelines(l for l in tmp if not l.startswith('#'))
for output in build_utils.ParseGnList(options.extra_mapping_output_paths):
shutil.copy(tmp_mapping_path, output)
with build_utils.AtomicOutput(options.output_config) as f:
f.write(merged_configs) f.write(merged_configs)
if temp_config_string:
f.write(_GENERATED_PROGUARD_HEADER)
f.write(temp_config_string)
if options.expected_configs_file: if options.expected_configs_file:
_VerifyExpectedConfigs(options.expected_configs_file, _VerifyExpectedConfigs(options.expected_configs_file,
options.output_config, options.output_config,
options.verify_expected_configs) options.verify_expected_configs)
other_inputs = [] if options.r8_path:
if options.apply_mapping: _OptimizeWithR8(options, config_paths, libraries, dynamic_config_data,
other_inputs += options.apply_mapping print_stdout)
build_utils.WriteDepfile(
options.depfile,
options.output_path,
inputs=options.proguard_configs + options.input_paths + libraries +
other_inputs,
add_pydeps=False)
else: else:
proguard = proguard_util.ProguardCmdBuilder(options.proguard_path) _OptimizeWithProguard(options, config_paths, libraries, dynamic_config_data,
proguard.injars(options.input_paths) print_stdout)
proguard.configs(options.proguard_configs)
proguard.config_exclusions(options.proguard_config_exclusions) # After ProGuard / R8 has run:
proguard.outjar(options.output_path) for output in options.extra_mapping_output_paths:
proguard.mapping_output(options.mapping_output) shutil.copy(options.mapping_output, output)
proguard.libraryjars(libraries)
proguard.verbose(options.verbose) inputs = options.proguard_configs + options.input_paths + libraries
proguard.min_api(options.min_api) if options.apply_mapping:
# Do not consider the temp file as an input since its name is random. inputs += options.apply_mapping
input_paths = proguard.GetInputs()
build_utils.WriteDepfile(
with tempfile.NamedTemporaryFile() as f: options.depfile, options.output_path, inputs=inputs, add_pydeps=False)
if options.apply_mapping:
input_paths.append(options.apply_mapping)
# Maintain only class name mappings in the .mapping file in order to
# work around what appears to be a ProGuard bug in -applymapping:
# method 'int close()' is not being kept as 'a', but remapped to 'c'
_RemoveMethodMappings(options.apply_mapping, f)
proguard.mapping(f.name)
with build_utils.TempDir() as d:
proguard.tmp_dir(d)
input_strings = proguard.build()
if f.name in input_strings:
input_strings[input_strings.index(f.name)] = '$M'
build_utils.CallAndWriteDepfileIfStale(
proguard.CheckOutput,
options,
input_paths=input_paths,
input_strings=input_strings,
output_paths=proguard.GetOutputs(),
depfile_deps=proguard.GetDepfileDeps(),
add_pydeps=False)
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main(sys.argv[1:])) main()
...@@ -6,4 +6,3 @@ util/__init__.py ...@@ -6,4 +6,3 @@ util/__init__.py
util/build_utils.py util/build_utils.py
util/diff_utils.py util/diff_utils.py
util/md5_check.py util/md5_check.py
util/proguard_util.py
# Copyright 2015 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.
import os
import re
from util import build_utils
class ProguardOutputFilter(object):
"""ProGuard outputs boring stuff to stdout (proguard version, jar path, etc)
as well as interesting stuff (notes, warnings, etc). If stdout is entirely
boring, this class suppresses the output.
"""
IGNORE_RE = re.compile(
r'Pro.*version|Note:|Reading|Preparing|Printing|ProgramClass:|Searching|'
r'jar \[|\d+ class path entries checked')
def __init__(self):
self._last_line_ignored = False
self._ignore_next_line = False
def __call__(self, output):
ret = []
for line in output.splitlines(True):
if self._ignore_next_line:
self._ignore_next_line = False
continue
if '***BINARY RUN STATS***' in line:
self._last_line_ignored = True
self._ignore_next_line = True
elif not line.startswith(' '):
self._last_line_ignored = bool(self.IGNORE_RE.match(line))
elif 'You should check if you need to specify' in line:
self._last_line_ignored = True
if not self._last_line_ignored:
ret.append(line)
return ''.join(ret)
class ProguardCmdBuilder(object):
def __init__(self, proguard_jar):
assert os.path.exists(proguard_jar)
self._proguard_jar_path = proguard_jar
self._mapping = None
self._libraries = None
self._injars = None
self._configs = None
self._config_exclusions = None
self._outjar = None
self._mapping_output = None
self._verbose = False
self._min_api = None
self._tmp_dir = None
self._disabled_optimizations = []
def outjar(self, path):
assert self._outjar is None
self._outjar = path
def mapping_output(self, path):
assert self._mapping_output is None
self._mapping_output = path
def mapping(self, path):
assert self._mapping is None
assert os.path.exists(path), path
self._mapping = path
def tmp_dir(self, path):
assert self._tmp_dir is None
self._tmp_dir = path
def libraryjars(self, paths):
assert self._libraries is None
for p in paths:
assert os.path.exists(p), p
self._libraries = paths
def injars(self, paths):
assert self._injars is None
for p in paths:
assert os.path.exists(p), p
self._injars = paths
def configs(self, paths):
assert self._configs is None
self._configs = paths
for p in self._configs:
assert os.path.exists(p), p
def config_exclusions(self, paths):
assert self._config_exclusions is None
self._config_exclusions = paths
def verbose(self, verbose):
self._verbose = verbose
def min_api(self, min_api):
assert self._min_api is None
self._min_api = min_api
def disable_optimizations(self, optimizations):
self._disabled_optimizations += optimizations
def build(self):
assert self._injars is not None
assert self._outjar is not None
assert self._configs is not None
_combined_injars_path = os.path.join(self._tmp_dir, 'injars.jar')
_combined_libjars_path = os.path.join(self._tmp_dir, 'libjars.jar')
_combined_proguard_configs_path = os.path.join(self._tmp_dir,
'includes.txt')
build_utils.MergeZips(_combined_injars_path, self._injars)
build_utils.MergeZips(_combined_libjars_path, self._libraries)
_CombineConfigs(_combined_proguard_configs_path, self.GetConfigs())
if self._proguard_jar_path.endswith('.jar'):
cmd = [
'java', '-jar', self._proguard_jar_path, '-include',
_combined_proguard_configs_path
]
else:
cmd = [self._proguard_jar_path, '@' + _combined_proguard_configs_path]
if self._mapping:
cmd += ['-applymapping', self._mapping]
if self._libraries:
cmd += ['-libraryjars', _combined_libjars_path]
if self._min_api:
cmd += [
'-assumenosideeffects class android.os.Build$VERSION {' +
' public static final int SDK_INT return ' + self._min_api +
'..9999; }'
]
for optimization in self._disabled_optimizations:
cmd += [ '-optimizations', '!' + optimization ]
# The output jar must be specified after inputs.
cmd += [
'-forceprocessing',
'-injars',
_combined_injars_path,
'-outjars',
self._outjar,
'-printseeds',
self._outjar + '.seeds',
'-printusage',
self._outjar + '.usage',
'-printmapping',
self._mapping_output,
]
if self._verbose:
cmd.append('-verbose')
return cmd
def GetDepfileDeps(self):
# The list of inputs that the GN target does not directly know about.
inputs = self._configs + self._injars
if self._libraries:
inputs += self._libraries
return inputs
def GetConfigs(self):
ret = list(self._configs)
for path in self._config_exclusions:
ret.remove(path)
return ret
def GetInputs(self):
inputs = self.GetDepfileDeps()
inputs += [self._proguard_jar_path]
if self._mapping:
inputs.append(self._mapping)
return inputs
def GetOutputs(self):
return [
self._outjar,
self._outjar + '.flags',
self._mapping_output,
self._outjar + '.seeds',
self._outjar + '.usage',
]
def _WriteFlagsFile(self, cmd, out):
# Quite useful for auditing proguard flags.
WriteFlagsFile(self._configs, out)
out.write('#' * 80 + '\n')
out.write('# Command-line\n')
out.write('#' * 80 + '\n')
out.write('# ' + ' '.join(cmd) + '\n')
def CheckOutput(self):
cmd = self.build()
# There are a couple scenarios (.mapping files and switching from no
# proguard -> proguard) where GN's copy() target is used on output
# paths. These create hardlinks, so we explicitly unlink here to avoid
# updating files with multiple links.
for path in self.GetOutputs():
if os.path.exists(path):
os.unlink(path)
with open(self._outjar + '.flags', 'w') as out:
self._WriteFlagsFile(cmd, out)
# Warning: and Error: are sent to stderr, but messages and Note: are sent
# to stdout.
stdout_filter = None
stderr_filter = None
if not self._verbose:
stdout_filter = ProguardOutputFilter()
stderr_filter = ProguardOutputFilter()
build_utils.CheckOutput(cmd, print_stdout=True,
print_stderr=True,
stdout_filter=stdout_filter,
stderr_filter=stderr_filter)
# Proguard will skip writing -printseeds / -printusage / -printmapping if
# the files would be empty, but ninja needs all outputs to exist.
open(self._outjar + '.seeds', 'a').close()
open(self._outjar + '.usage', 'a').close()
open(self._outjar + '.mapping', 'a').close()
def _CombineConfigs(output_config_path, input_configs):
# Combine all input_configs into one config file at output_config_path.
output_string = ''
for input_config in input_configs:
with open(input_config) as f_input_config:
output_string += f_input_config.read()
with open(output_config_path, "w+") as f_output_config:
f_output_config.write(output_string)
def WriteFlagsFile(configs, out, exclude_generated=False):
for config in sorted(configs):
if exclude_generated and config.endswith('.resources.proguard.txt'):
continue
out.write('#' * 80 + '\n')
out.write('# ' + config + '\n')
out.write('#' * 80 + '\n')
with open(config) as config_file:
contents = config_file.read().rstrip()
# Remove numbers from generated rule comments to make file more
# diff'able.
contents = re.sub(r' #generated:\d+', '', contents)
out.write(contents)
out.write('\n\n')
...@@ -1582,8 +1582,7 @@ def main(argv): ...@@ -1582,8 +1582,7 @@ def main(argv):
# Exclude dex files from the test apk that exist within the apk under test. # Exclude dex files from the test apk that exist within the apk under test.
# TODO(agrieve): When proguard is enabled, this filtering logic happens # TODO(agrieve): When proguard is enabled, this filtering logic happens
# within proguard_util.py. Move the logic for the proguard case into # within proguard.py. Move the logic for the proguard case to here.
# here as well.
tested_apk_library_deps = tested_apk_deps.All('java_library') tested_apk_library_deps = tested_apk_deps.All('java_library')
tested_apk_deps_dex_files = [c['dex_path'] for c in tested_apk_library_deps] tested_apk_deps_dex_files = [c['dex_path'] for c in tested_apk_library_deps]
deps_dex_files = [ deps_dex_files = [
......
...@@ -696,4 +696,3 @@ ...@@ -696,4 +696,3 @@
-keepclassmembers class * { -keepclassmembers class * {
@com.google.vr.dynamite.client.UsedByReflection *; @com.google.vr.dynamite.client.UsedByReflection *;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment