tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

proguard.py (27240B)


      1 #!/usr/bin/env python3
      2 #
      3 # Copyright 2013 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 import argparse
      8 import json
      9 import logging
     10 import os
     11 import pathlib
     12 import re
     13 import shutil
     14 import sys
     15 import zipfile
     16 
     17 import dex
     18 from util import build_utils
     19 from util import diff_utils
     20 import action_helpers  # build_utils adds //build to sys.path.
     21 import zip_helpers
     22 
     23 _IGNORE_WARNINGS = (
     24    # E.g. Triggers for weblayer_instrumentation_test_apk since both it and its
     25    # apk_under_test have no shared_libraries.
     26    # https://crbug.com/1364192 << To fix this in a better way.
     27    r'Missing class org.chromium.build.NativeLibraries',
     28    # Caused by protobuf runtime using -identifiernamestring in a way that
     29    # doesn't work with R8. Looks like:
     30    # Rule matches the static final field `...`, which may have been inlined...
     31    # com.google.protobuf.*GeneratedExtensionRegistryLite {
     32    #   static java.lang.String CONTAINING_TYPE_*;
     33    # }
     34    r'GeneratedExtensionRegistryLite\.CONTAINING_TYPE_',
     35    # Relevant for R8 when optimizing an app that doesn't use protobuf.
     36    r'Ignoring -shrinkunusedprotofields since the protobuf-lite runtime is',
     37    # Ignore Unused Rule Warnings in third_party libraries.
     38    r'/third_party/.*Proguard configuration rule does not match anything',
     39    # Ignore cronet's test rules (low priority to fix).
     40    r'cronet/android/test/proguard.cfg.*Proguard configuration rule does not',
     41    r'Proguard configuration rule does not match anything:.*(?:' + '|'.join([
     42        # aapt2 generates keeps for these.
     43        r'class android\.',
     44        # Used internally.
     45        r'com.no.real.class.needed.receiver',
     46        # Ignore Unused Rule Warnings for annotations.
     47        r'@',
     48        # Ignore Unused Rule Warnings for * implements Foo (androidx has these).
     49        r'class \*+ implements',
     50        # Ignore rules that opt out of this check.
     51        r'!cr_allowunused',
     52        # https://crbug.com/1441225
     53        r'EditorDialogToolbar',
     54        # https://crbug.com/1441226
     55        r'PaymentRequest[BH]',
     56    ]) + ')',
     57    # We enforce that this class is removed via -checkdiscard.
     58    r'FastServiceLoader\.class:.*Could not inline ServiceLoader\.load',
     59    # Happens on internal builds. It's a real failure, but happens in dead code.
     60    r'(?:GeneratedExtensionRegistryLoader|ExtensionRegistryLite)\.class:.*Could not inline ServiceLoader\.load',  # pylint: disable=line-too-long
     61    # This class is referenced by kotlinx-coroutines-core-jvm but it does not
     62    # depend on it. Not actually needed though.
     63    r'Missing class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement',
     64    # TODO(b/404818708): androidx.appsearch code is referencing classes in the
     65    # Android B sdk thus we must ignore these warnings until after the sdk roll.
     66    r'Missing class .* androidx.appsearch.platformstorage.converter.*\$ApiHelperForB',
     67    # Ignore MethodParameter attribute count isn't matching in espresso.
     68    # This is a banner warning and each individual file affected will have
     69    # its own warning.
     70    r'Warning: Invalid parameter counts in MethodParameter attributes',
     71    # Full error: "Warning: InnerClasses attribute has entries missing a
     72    # corresponding EnclosingMethod attribute. Such InnerClasses attribute
     73    # entries are ignored."
     74    r'Warning: InnerClasses attribute has entries missing a corresponding EnclosingMethod attribute',  # pylint: disable=line-too-long
     75    # Warning in obj/third_party/android_deps/google_play_services_fido_java/classes.jar:com/google/android/gms/internal/fido/zzel$zza.class:  # pylint: disable=line-too-long
     76    # Classes with missing EnclosingMethod: com.google.android.gms.internal.fido.zzel$zza  # pylint: disable=line-too-long
     77    r'Classes with missing EnclosingMethod',
     78    # Full error example: "Warning in <path to target prebuilt>:
     79    # androidx/test/espresso/web/internal/deps/guava/collect/Maps$1.class:"
     80    # Also happens in espresso core.
     81    r'Warning in .*:androidx/test/espresso/.*/guava/collect/.*',
     82 )
     83 
     84 _BLOCKLISTED_EXPECTATION_PATHS = [
     85    # A separate expectation file is created for these files.
     86    'clank/third_party/google3/cipd/pg_confs/',
     87 ]
     88 
     89 
     90 def _ParseOptions():
     91  args = build_utils.ExpandFileArgs(sys.argv[1:])
     92  parser = argparse.ArgumentParser()
     93  action_helpers.add_depfile_arg(parser)
     94  parser.add_argument('--r8-path',
     95                      required=True,
     96                      help='Path to the R8.jar to use.')
     97  parser.add_argument('--custom-r8-path',
     98                      required=True,
     99                      help='Path to our custom R8 wrapper to use.')
    100  parser.add_argument('--input-paths',
    101                      action='append',
    102                      help='GN-list of .jar files to optimize, excluding'
    103                      ' those --feature-jars.')
    104  parser.add_argument('--output-path', help='Path to the generated .jar file.')
    105  parser.add_argument('--tracerefs-json-out')
    106  parser.add_argument(
    107      '--proguard-configs',
    108      action='append',
    109      required=True,
    110      help='GN-list of configuration files.')
    111  parser.add_argument(
    112      '--apply-mapping', help='Path to ProGuard mapping to apply.')
    113  parser.add_argument(
    114      '--mapping-output',
    115      required=True,
    116      help='Path for ProGuard to output mapping file to.')
    117  parser.add_argument(
    118      '--extra-mapping-output-paths',
    119      help='GN-list of additional paths to copy output mapping file to.')
    120  parser.add_argument('--sdk-jars',
    121                      action='append',
    122                      help='GN-list of .jar files to include as libraries.')
    123  parser.add_argument(
    124      '--sdk-extension-jars',
    125      action='append',
    126      help='GN-list of .jar files to include as libraries, and that are not a '
    127      'part of R8\'s API database.')
    128  parser.add_argument('--main-dex-rules-path',
    129                      action='append',
    130                      help='Path to main dex rules for multidex.')
    131  parser.add_argument(
    132      '--min-api', help='Minimum Android API level compatibility.')
    133  parser.add_argument('--enable-obfuscation',
    134                      action='store_true',
    135                      help='Minify symbol names')
    136  parser.add_argument(
    137      '--verbose', '-v', action='store_true', help='Print all ProGuard output')
    138  parser.add_argument('--repackage-classes',
    139                      default='',
    140                      help='Value for -repackageclasses.')
    141  parser.add_argument('--disable-checks',
    142                      action='store_true',
    143                      help='Disable -checkdiscard directives')
    144  parser.add_argument('--source-file', help='Value for source file attribute.')
    145  parser.add_argument('--package-name',
    146                      help='Goes into a comment in the mapping file.')
    147  parser.add_argument(
    148      '--force-enable-assertions',
    149      action='store_true',
    150      help='Forcefully enable javac generated assertion code.')
    151  parser.add_argument('--assertion-handler',
    152                      help='The class name of the assertion handler class.')
    153  parser.add_argument(
    154      '--feature-jars',
    155      action='append',
    156      help='GN list of path to jars which comprise the corresponding feature.')
    157  parser.add_argument(
    158      '--dex-dest',
    159      action='append',
    160      dest='dex_dests',
    161      help='Destination for dex file of the corresponding feature.')
    162  parser.add_argument(
    163      '--feature-name',
    164      action='append',
    165      dest='feature_names',
    166      help='The name of the feature module.')
    167  parser.add_argument(
    168      '--uses-split',
    169      action='append',
    170      help='List of name pairs separated by : mapping a feature module to a '
    171      'dependent feature module.')
    172  parser.add_argument('--input-art-profile',
    173                      help='Path to the input unobfuscated ART profile.')
    174  parser.add_argument('--output-art-profile',
    175                      help='Path to the output obfuscated ART profile.')
    176  parser.add_argument(
    177      '--apply-startup-profile',
    178      action='store_true',
    179      help='Whether to pass --input-art-profile as a startup profile to R8.')
    180  parser.add_argument(
    181      '--keep-rules-targets-regex',
    182      metavar='KEEP_RULES_REGEX',
    183      help='If passed outputs keep rules for references from all other inputs '
    184      'to the subset of inputs that satisfy the KEEP_RULES_REGEX.')
    185  parser.add_argument(
    186      '--keep-rules-output-path',
    187      help='Output path to the keep rules for references to the '
    188      '--keep-rules-targets-regex inputs from the rest of the inputs.')
    189  parser.add_argument('--warnings-as-errors',
    190                      action='store_true',
    191                      help='Treat all warnings as errors.')
    192  parser.add_argument('--show-desugar-default-interface-warnings',
    193                      action='store_true',
    194                      help='Enable desugaring warnings.')
    195  parser.add_argument('--dump-inputs',
    196                      action='store_true',
    197                      help='Use when filing R8 bugs to capture inputs.'
    198                      ' Stores inputs to r8inputs.zip')
    199  parser.add_argument(
    200      '--dump-unknown-refs',
    201      action='store_true',
    202      help='Log all reasons why API modelling cannot determine API level')
    203  parser.add_argument(
    204      '--stamp',
    205      help='File to touch upon success. Mutually exclusive with --output-path')
    206  parser.add_argument('--desugared-library-keep-rule-output',
    207                      help='Path to desugared library keep rule output file.')
    208 
    209  diff_utils.AddCommandLineFlags(parser)
    210  options = parser.parse_args(args)
    211 
    212  if options.feature_names:
    213    if options.output_path:
    214      parser.error('Feature splits cannot specify an output in GN.')
    215    if not options.actual_file and not options.stamp:
    216      parser.error('Feature splits require a stamp file as output.')
    217  elif not options.output_path:
    218    parser.error('Output path required when feature splits aren\'t used')
    219 
    220  if bool(options.keep_rules_targets_regex) != bool(
    221      options.keep_rules_output_path):
    222    parser.error('You must path both --keep-rules-targets-regex and '
    223                 '--keep-rules-output-path')
    224 
    225  if options.output_art_profile and not options.input_art_profile:
    226    parser.error('--output-art-profile requires --input-art-profile')
    227  if options.apply_startup_profile and not options.input_art_profile:
    228    parser.error('--apply-startup-profile requires --input-art-profile')
    229 
    230  if options.force_enable_assertions and options.assertion_handler:
    231    parser.error('Cannot use both --force-enable-assertions and '
    232                 '--assertion-handler')
    233 
    234  options.sdk_jars = action_helpers.parse_gn_list(options.sdk_jars)
    235  options.sdk_extension_jars = action_helpers.parse_gn_list(
    236      options.sdk_extension_jars)
    237  options.proguard_configs = action_helpers.parse_gn_list(
    238      options.proguard_configs)
    239  options.extra_mapping_output_paths = action_helpers.parse_gn_list(
    240      options.extra_mapping_output_paths)
    241  if os.environ.get('R8_VERBOSE') == '1':
    242    options.verbose = True
    243 
    244  if options.feature_names:
    245    if 'base' not in options.feature_names:
    246      parser.error('"base" feature required when feature arguments are used.')
    247    if len(options.feature_names) != len(options.feature_jars) or len(
    248        options.feature_names) != len(options.dex_dests):
    249      parser.error('Invalid feature argument lengths.')
    250 
    251    options.feature_jars = [
    252        action_helpers.parse_gn_list(x) for x in options.feature_jars
    253    ]
    254    assert not options.input_paths
    255    input_paths = set()
    256    for jar_paths in options.feature_jars:
    257      input_paths.update(jar_paths)
    258    options.input_paths = sorted(input_paths)
    259  else:
    260    options.input_paths = action_helpers.parse_gn_list(options.input_paths)
    261 
    262  split_map = {}
    263  if options.uses_split:
    264    for split_pair in options.uses_split:
    265      child, parent = split_pair.split(':')
    266      for name in (child, parent):
    267        if name not in options.feature_names:
    268          parser.error('"%s" referenced in --uses-split not present.' % name)
    269      split_map[child] = parent
    270  options.uses_split = split_map
    271 
    272  return options
    273 
    274 
    275 class _SplitContext:
    276  def __init__(self, name, output_path, input_jars, work_dir, parent_name=None):
    277    self.name = name
    278    self.parent_name = parent_name
    279    self.input_jars = set(input_jars)
    280    self.final_output_path = output_path
    281    self.staging_dir = os.path.join(work_dir, name)
    282    os.mkdir(self.staging_dir)
    283 
    284  def CreateOutput(self):
    285    found_files = build_utils.FindInDirectory(self.staging_dir)
    286    if not found_files:
    287      raise Exception('Missing dex outputs in {}'.format(self.staging_dir))
    288 
    289    if self.final_output_path.endswith('.dex'):
    290      if len(found_files) != 1:
    291        raise Exception('Expected exactly 1 dex file output, found: {}'.format(
    292            '\t'.join(found_files)))
    293      shutil.move(found_files[0], self.final_output_path)
    294      return
    295 
    296    # Add to .jar using Python rather than having R8 output to a .zip directly
    297    # in order to disable compression of the .jar, saving ~500ms.
    298    tmp_jar_output = self.staging_dir + '.jar'
    299    zip_helpers.add_files_to_zip(found_files,
    300                                 tmp_jar_output,
    301                                 base_dir=self.staging_dir)
    302    shutil.move(tmp_jar_output, self.final_output_path)
    303 
    304 
    305 def _OptimizeWithR8(options, config_paths, libraries, dynamic_config_data):
    306  with build_utils.TempDir() as tmp_dir:
    307    if dynamic_config_data:
    308      dynamic_config_path = os.path.join(tmp_dir, 'dynamic_config.flags')
    309      with open(dynamic_config_path, 'w') as f:
    310        f.write(dynamic_config_data)
    311      config_paths = config_paths + [dynamic_config_path]
    312 
    313    tmp_mapping_path = os.path.join(tmp_dir, 'mapping.txt')
    314    # If there is no output (no classes are kept), this prevents this script
    315    # from failing.
    316    build_utils.Touch(tmp_mapping_path)
    317 
    318    tmp_output = os.path.join(tmp_dir, 'r8out')
    319    os.mkdir(tmp_output)
    320 
    321    split_contexts_by_name = {}
    322    if options.feature_names:
    323      for name, dest_dex, input_jars in zip(options.feature_names,
    324                                            options.dex_dests,
    325                                            options.feature_jars):
    326        parent_name = options.uses_split.get(name)
    327        if parent_name is None and name != 'base':
    328          parent_name = 'base'
    329        split_context = _SplitContext(name,
    330                                      dest_dex,
    331                                      input_jars,
    332                                      tmp_output,
    333                                      parent_name=parent_name)
    334        split_contexts_by_name[name] = split_context
    335    else:
    336      split_contexts_by_name['base'] = _SplitContext('base',
    337                                                     options.output_path,
    338                                                     options.input_paths,
    339                                                     tmp_output)
    340    base_context = split_contexts_by_name['base']
    341 
    342    # R8 OOMs with xmx=3G.
    343    cmd = build_utils.JavaCmd(xmx='4G') + [
    344        # Allows -whyareyounotinlining, which we don't have by default, but
    345        # which is useful for one-off queries.
    346        '-Dcom.android.tools.r8.experimental.enablewhyareyounotinlining=1',
    347        # Restricts horizontal class merging to apply only to classes that
    348        # share a .java file (nested classes). https://crbug.com/1363709
    349        '-Dcom.android.tools.r8.enableSameFilePolicy=1',
    350        # Allow ServiceLoaderUtil.maybeCreate() to work with types that are
    351        # -kept (e.g. due to containing JNI).
    352        '-Dcom.android.tools.r8.allowServiceLoaderRewritingPinnedTypes=1',
    353        # Allow R8 to inline kept methods by default.
    354        # See: b/364267880#2
    355        '-Dcom.android.tools.r8.allowCodeReplacement=false',
    356        # Required to use "-keep,allowcodereplacement"
    357        '-Dcom.android.tools.r8.allowTestProguardOptions=true',
    358        # Needed because we don't add an unconditional -keep for Enum.values()
    359        # methods. http://b/204939965
    360        '-Dcom.android.tools.r8.experimentalTraceAndroidEnumSerialization=1',
    361    ]
    362    if options.sdk_extension_jars:
    363      # Enable API modelling for OS extensions. https://b/326252366
    364      cmd += [
    365          '-Dcom.android.tools.r8.androidApiExtensionLibraries=' +
    366          ','.join(options.sdk_extension_jars)
    367      ]
    368    if options.dump_inputs:
    369      cmd += ['-Dcom.android.tools.r8.dumpinputtofile=r8inputs.zip']
    370    if options.dump_unknown_refs:
    371      cmd += ['-Dcom.android.tools.r8.reportUnknownApiReferences=1']
    372    cmd += [
    373        '-cp',
    374        '{}:{}'.format(options.r8_path, options.custom_r8_path),
    375        'org.chromium.build.CustomR8',
    376        '--no-data-resources',
    377        '--map-id-template',
    378        f'{options.source_file} ({options.package_name})',
    379        '--source-file-template',
    380        options.source_file,
    381        '--output',
    382        base_context.staging_dir,
    383        '--pg-map-output',
    384        tmp_mapping_path,
    385    ]
    386 
    387    if options.uses_split:
    388      cmd += ['--isolated-splits']
    389 
    390    if options.disable_checks:
    391      cmd += ['--map-diagnostics:CheckDiscardDiagnostic', 'error', 'none']
    392    # Triggered by rules from deps we cannot control.
    393    cmd += [('--map-diagnostics:EmptyMemberRulesToDefaultInitRuleConversion'
    394             'Diagnostic'), 'warning', 'none']
    395    cmd += ['--map-diagnostics', 'info', 'warning']
    396    # An "error" level diagnostic causes r8 to return an error exit code. Doing
    397    # this allows our filter to decide what should/shouldn't break our build.
    398    cmd += ['--map-diagnostics', 'error', 'warning']
    399 
    400    if options.min_api:
    401      cmd += ['--min-api', options.min_api]
    402 
    403    if options.assertion_handler:
    404      cmd += ['--force-assertions-handler:' + options.assertion_handler]
    405    elif options.force_enable_assertions:
    406      cmd += ['--force-enable-assertions']
    407 
    408    for lib in libraries:
    409      cmd += ['--lib', lib]
    410 
    411    for config_file in config_paths:
    412      cmd += ['--pg-conf', config_file]
    413 
    414    if options.main_dex_rules_path:
    415      for main_dex_rule in options.main_dex_rules_path:
    416        cmd += ['--main-dex-rules', main_dex_rule]
    417 
    418    if options.output_art_profile:
    419      cmd += [
    420          '--art-profile',
    421          options.input_art_profile,
    422          options.output_art_profile,
    423      ]
    424    if options.apply_startup_profile:
    425      cmd += [
    426          '--startup-profile',
    427          options.input_art_profile,
    428      ]
    429 
    430    for split_context in split_contexts_by_name.values():
    431      if split_context is base_context:
    432        continue
    433      for in_jar in sorted(split_context.input_jars):
    434        cmd += ['--feature', in_jar, split_context.staging_dir]
    435 
    436    cmd += sorted(base_context.input_jars)
    437 
    438    if options.verbose:
    439      stderr_filter = None
    440    else:
    441      filters = list(dex.DEFAULT_IGNORE_WARNINGS)
    442      filters += _IGNORE_WARNINGS
    443      if options.show_desugar_default_interface_warnings:
    444        filters += dex.INTERFACE_DESUGARING_WARNINGS
    445      stderr_filter = dex.CreateStderrFilter(filters)
    446 
    447    try:
    448      logging.debug('Running R8')
    449      build_utils.CheckOutput(cmd,
    450                              print_stdout=True,
    451                              stderr_filter=stderr_filter,
    452                              fail_on_output=options.warnings_as_errors)
    453    except build_utils.CalledProcessError as e:
    454      # Do not output command line because it is massive and makes the actual
    455      # error message hard to find.
    456      sys.stderr.write(e.output)
    457      sys.exit(1)
    458 
    459    logging.debug('Collecting ouputs')
    460    base_context.CreateOutput()
    461    for split_context in split_contexts_by_name.values():
    462      if split_context is not base_context:
    463        split_context.CreateOutput()
    464 
    465    shutil.move(tmp_mapping_path, options.mapping_output)
    466  return split_contexts_by_name
    467 
    468 
    469 def _OutputKeepRules(r8_path, input_paths, libraries, targets_re_string,
    470                     keep_rules_output):
    471 
    472  cmd = build_utils.JavaCmd(xmx='2G') + [
    473      '-cp', r8_path, 'com.android.tools.r8.tracereferences.TraceReferences',
    474      '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
    475      '--keep-rules', '--output', keep_rules_output
    476  ]
    477  targets_re = re.compile(targets_re_string)
    478  for path in input_paths:
    479    if targets_re.search(path):
    480      cmd += ['--target', path]
    481    else:
    482      cmd += ['--source', path]
    483  for path in libraries:
    484    cmd += ['--lib', path]
    485 
    486  build_utils.CheckOutput(cmd, print_stderr=False, fail_on_output=False)
    487 
    488 
    489 def _CombineConfigs(configs,
    490                    dynamic_config_data,
    491                    embedded_configs,
    492                    exclude_generated=False):
    493  # Sort in this way so //clank versions of the same libraries will sort
    494  # to the same spot in the file.
    495  def sort_key(path):
    496    return tuple(reversed(path.split(os.path.sep)))
    497 
    498  def format_config_contents(path, contents):
    499    formatted_contents = []
    500    # Ignore files that contain only comments (androidx has a lot of these).
    501    if all(l.isspace() or l.rstrip().startswith('#')
    502           for l in contents.splitlines()):
    503      return []
    504 
    505    # Fix up line endings (third_party configs can have windows endings).
    506    contents = contents.replace('\r', '')
    507    # Remove numbers from generated rule comments to make file more
    508    # diff'able.
    509    contents = re.sub(r' #generated:\d+', '', contents)
    510    formatted_contents.append('# File: ' + path)
    511    formatted_contents.append(contents)
    512    formatted_contents.append('')
    513    return formatted_contents
    514 
    515  ret = []
    516  for config in sorted(configs, key=sort_key):
    517    if exclude_generated and config.endswith('.resources.proguard.txt'):
    518      continue
    519 
    520    # Exclude some confs from expectations.
    521    if any(entry in config for entry in _BLOCKLISTED_EXPECTATION_PATHS):
    522      continue
    523 
    524    with open(config) as config_file:
    525      contents = config_file.read().rstrip()
    526 
    527    ret.extend(format_config_contents(config, contents))
    528 
    529  for path, contents in sorted(embedded_configs.items()):
    530    ret.extend(format_config_contents(path, contents))
    531 
    532 
    533  if dynamic_config_data:
    534    ret.append('# File: //build/android/gyp/proguard.py (generated rules)')
    535    ret.append(dynamic_config_data)
    536    ret.append('')
    537  return '\n'.join(ret)
    538 
    539 
    540 def _CreateDynamicConfig(options):
    541  ret = []
    542  if options.enable_obfuscation:
    543    ret.append(f"-repackageclasses '{options.repackage_classes}'")
    544  else:
    545    ret.append("-dontobfuscate")
    546 
    547  if options.apply_mapping:
    548    ret.append("-applymapping '%s'" % options.apply_mapping)
    549 
    550  return '\n'.join(ret)
    551 
    552 
    553 def _ExtractEmbeddedConfigs(jar_path, embedded_configs):
    554  with zipfile.ZipFile(jar_path) as z:
    555    proguard_names = []
    556    r8_names = []
    557    for info in z.infolist():
    558      if info.is_dir():
    559        continue
    560      if info.filename.startswith('META-INF/proguard/'):
    561        proguard_names.append(info.filename)
    562      elif info.filename.startswith('META-INF/com.android.tools/r8/'):
    563        r8_names.append(info.filename)
    564      elif info.filename.startswith('META-INF/com.android.tools/r8-from'):
    565        # Assume our version of R8 is always latest.
    566        if '-upto-' not in info.filename:
    567          r8_names.append(info.filename)
    568 
    569    # Give preference to r8-from-*, then r8/, then proguard/.
    570    active = r8_names or proguard_names
    571    for filename in active:
    572      config_path = '{}:{}'.format(jar_path, filename)
    573      embedded_configs[config_path] = z.read(filename).decode('utf-8').rstrip()
    574 
    575 
    576 def _MaybeWriteStampAndDepFile(options, inputs):
    577  output = options.output_path
    578  if options.stamp:
    579    build_utils.Touch(options.stamp)
    580    output = options.stamp
    581  if options.depfile:
    582    action_helpers.write_depfile(options.depfile, output, inputs=inputs)
    583 
    584 
    585 def _IterParentContexts(context_name, split_contexts_by_name):
    586  while context_name:
    587    context = split_contexts_by_name[context_name]
    588    yield context
    589    context_name = context.parent_name
    590 
    591 
    592 def _WriteTraceReferencesJson(options, split_contexts_by_name):
    593  # Set of all contexts that are a parent to another.
    594  parent_splits_context_names = {
    595      c.parent_name
    596      for c in split_contexts_by_name.values() if c.parent_name
    597  }
    598  context_sets = [
    599      list(_IterParentContexts(n, split_contexts_by_name))
    600      for n in parent_splits_context_names
    601  ]
    602  # Visit them in order of: base, base+chrome, base+chrome+thing.
    603  context_sets.sort(key=lambda x: (len(x), x[0].name))
    604 
    605  # Ensure there are no missing references when considering all dex files.
    606  dex_files = sorted(c.final_output_path
    607                     for c in split_contexts_by_name.values())
    608  payload = {
    609      'r8jar': options.r8_path,
    610      'libs': options.sdk_jars + options.sdk_extension_jars,
    611      'jobs': [],
    612  }
    613 
    614  # Ensure there are no missing references when considering all dex files.
    615  payload['jobs'].append({'name': '', 'jars': dex_files})
    616 
    617  # Ensure there are no references from base -> chrome module, or from
    618  # base+chrome -> feature modules.
    619  for context_set in context_sets:
    620    dex_files = [c.final_output_path for c in context_set]
    621    payload['jobs'].append({'name': context_set[0].name, 'jars': dex_files})
    622 
    623  with action_helpers.atomic_output(options.tracerefs_json_out, 'wt') as f:
    624    json.dump(payload, f, indent=2)
    625 
    626 
    627 def _Run(options):
    628  # ProGuard configs that are derived from flags.
    629  logging.debug('Preparing configs')
    630  dynamic_config_data = _CreateDynamicConfig(options)
    631 
    632  logging.debug('Looking for embedded configs')
    633  libraries = options.sdk_jars + options.sdk_extension_jars
    634 
    635  embedded_configs = {}
    636  for jar_path in options.input_paths:
    637    _ExtractEmbeddedConfigs(jar_path, embedded_configs)
    638 
    639  # ProGuard configs that are derived from flags.
    640  merged_configs = _CombineConfigs(options.proguard_configs,
    641                                   dynamic_config_data,
    642                                   embedded_configs,
    643                                   exclude_generated=True)
    644 
    645  depfile_inputs = options.proguard_configs + options.input_paths + libraries
    646  if options.expected_file:
    647    diff_utils.CheckExpectations(merged_configs, options)
    648    if options.only_verify_expectations:
    649      action_helpers.write_depfile(options.depfile,
    650                                   options.actual_file,
    651                                   inputs=depfile_inputs)
    652      return
    653 
    654  if options.keep_rules_output_path:
    655    _OutputKeepRules(options.r8_path, options.input_paths, libraries,
    656                     options.keep_rules_targets_regex,
    657                     options.keep_rules_output_path)
    658    return
    659 
    660  split_contexts_by_name = _OptimizeWithR8(options, options.proguard_configs,
    661                                           libraries, dynamic_config_data)
    662 
    663  if options.tracerefs_json_out:
    664    logging.debug('Writing TraceReferences .json')
    665    _WriteTraceReferencesJson(options, split_contexts_by_name)
    666 
    667  for output in options.extra_mapping_output_paths:
    668    shutil.copy(options.mapping_output, output)
    669 
    670  if options.apply_mapping:
    671    depfile_inputs.append(options.apply_mapping)
    672 
    673  _MaybeWriteStampAndDepFile(options, depfile_inputs)
    674 
    675 
    676 def main():
    677  build_utils.InitLogging('PROGUARD_DEBUG')
    678  options = _ParseOptions()
    679 
    680  if options.dump_inputs:
    681    # Dumping inputs causes output to be emitted, avoid failing due to stdout.
    682    options.warnings_as_errors = False
    683 
    684  _Run(options)
    685 
    686 
    687 if __name__ == '__main__':
    688  main()