• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7import argparse
8import logging
9import os
10import pathlib
11import re
12import shutil
13import sys
14import zipfile
15
16import dex
17from util import build_utils
18from util import diff_utils
19import action_helpers  # build_utils adds //build to sys.path.
20import zip_helpers
21
22_IGNORE_WARNINGS = (
23    # E.g. Triggers for weblayer_instrumentation_test_apk since both it and its
24    # apk_under_test have no shared_libraries.
25    # https://crbug.com/1364192 << To fix this in a better way.
26    r'Missing class org.chromium.build.NativeLibraries',
27    # Caused by protobuf runtime using -identifiernamestring in a way that
28    # doesn't work with R8. Looks like:
29    # Rule matches the static final field `...`, which may have been inlined...
30    # com.google.protobuf.*GeneratedExtensionRegistryLite {
31    #   static java.lang.String CONTAINING_TYPE_*;
32    # }
33    r'GeneratedExtensionRegistryLite\.CONTAINING_TYPE_',
34    # Relevant for R8 when optimizing an app that doesn't use protobuf.
35    r'Ignoring -shrinkunusedprotofields since the protobuf-lite runtime is',
36    # Ignore Unused Rule Warnings in third_party libraries.
37    r'/third_party/.*Proguard configuration rule does not match anything',
38    # Ignore cronet's test rules (low priority to fix).
39    r'cronet/android/test/proguard.cfg.*Proguard configuration rule does not',
40    r'Proguard configuration rule does not match anything:.*(?:' + '|'.join([
41        # aapt2 generates keeps for these.
42        r'class android\.',
43        # Used internally.
44        r'com.no.real.class.needed.receiver',
45        # Ignore Unused Rule Warnings for annotations.
46        r'@',
47        # Ignore Unused Rule Warnings for * implements Foo (androidx has these).
48        r'class \*+ implements',
49        # Ignore rules that opt out of this check.
50        r'!cr_allowunused',
51        # https://crbug.com/1441225
52        r'EditorDialogToolbar',
53        # https://crbug.com/1441226
54        r'PaymentRequest[BH]',
55    ]) + ')',
56    # TODO(agrieve): Remove once we update to U SDK.
57    r'OnBackAnimationCallback',
58    # This class was added only in the U PrivacySandbox SDK: crbug.com/333713111
59    r'Missing class android.adservices.common.AdServicesOutcomeReceiver',
60    # We enforce that this class is removed via -checkdiscard.
61    r'FastServiceLoader\.class:.*Could not inline ServiceLoader\.load',
62
63    # Ignore MethodParameter attribute count isn't matching in espresso.
64    # This is a banner warning and each individual file affected will have
65    # its own warning.
66    r'Warning: Invalid parameter counts in MethodParameter attributes',
67    # Full error: "Warning: InnerClasses attribute has entries missing a
68    # corresponding EnclosingMethod attribute. Such InnerClasses attribute
69    # entries are ignored."
70    r'Warning: InnerClasses attribute has entries missing a corresponding EnclosingMethod attribute',  # pylint: disable=line-too-long
71    r'Warning in obj/third_party/androidx/androidx_test_espresso_espresso_core_java',  # pylint: disable=line-too-long
72    r'Warning in obj/third_party/androidx/androidx_test_espresso_espresso_web_java',  # pylint: disable=line-too-long
73
74    # We are following up in b/290389974
75    r'AppSearchDocumentClassMap\.class:.*Could not inline ServiceLoader\.load',
76)
77
78_BLOCKLISTED_EXPECTATION_PATHS = [
79    # A separate expectation file is created for these files.
80    'clank/third_party/google3/pg_confs/',
81]
82
83_DUMP_DIR_NAME = 'r8inputs_dir'
84
85
86def _ParseOptions():
87  args = build_utils.ExpandFileArgs(sys.argv[1:])
88  parser = argparse.ArgumentParser()
89  action_helpers.add_depfile_arg(parser)
90  parser.add_argument('--r8-path',
91                      required=True,
92                      help='Path to the R8.jar to use.')
93  parser.add_argument('--custom-r8-path',
94                      required=True,
95                      help='Path to our custom R8 wrapepr to use.')
96  parser.add_argument('--input-paths',
97                      action='append',
98                      required=True,
99                      help='GN-list of .jar files to optimize.')
100  parser.add_argument('--output-path', help='Path to the generated .jar file.')
101  parser.add_argument(
102      '--proguard-configs',
103      action='append',
104      required=True,
105      help='GN-list of configuration files.')
106  parser.add_argument(
107      '--apply-mapping', help='Path to ProGuard mapping to apply.')
108  parser.add_argument(
109      '--mapping-output',
110      required=True,
111      help='Path for ProGuard to output mapping file to.')
112  parser.add_argument(
113      '--extra-mapping-output-paths',
114      help='GN-list of additional paths to copy output mapping file to.')
115  parser.add_argument(
116      '--classpath',
117      action='append',
118      help='GN-list of .jar files to include as libraries.')
119  parser.add_argument('--main-dex-rules-path',
120                      action='append',
121                      help='Path to main dex rules for multidex.')
122  parser.add_argument(
123      '--min-api', help='Minimum Android API level compatibility.')
124  parser.add_argument('--enable-obfuscation',
125                      action='store_true',
126                      help='Minify symbol names')
127  parser.add_argument(
128      '--verbose', '-v', action='store_true', help='Print all ProGuard output')
129  parser.add_argument('--repackage-classes',
130                      default='',
131                      help='Value for -repackageclasses.')
132  parser.add_argument(
133    '--disable-checks',
134    action='store_true',
135    help='Disable -checkdiscard directives and missing symbols check')
136  parser.add_argument('--source-file', help='Value for source file attribute.')
137  parser.add_argument('--package-name',
138                      help='Goes into a comment in the mapping file.')
139  parser.add_argument(
140      '--force-enable-assertions',
141      action='store_true',
142      help='Forcefully enable javac generated assertion code.')
143  parser.add_argument('--assertion-handler',
144                      help='The class name of the assertion handler class.')
145  parser.add_argument(
146      '--feature-jars',
147      action='append',
148      help='GN list of path to jars which comprise the corresponding feature.')
149  parser.add_argument(
150      '--dex-dest',
151      action='append',
152      dest='dex_dests',
153      help='Destination for dex file of the corresponding feature.')
154  parser.add_argument(
155      '--feature-name',
156      action='append',
157      dest='feature_names',
158      help='The name of the feature module.')
159  parser.add_argument(
160      '--uses-split',
161      action='append',
162      help='List of name pairs separated by : mapping a feature module to a '
163      'dependent feature module.')
164  parser.add_argument('--input-art-profile',
165                      help='Path to the input unobfuscated ART profile.')
166  parser.add_argument('--output-art-profile',
167                      help='Path to the output obfuscated ART profile.')
168  parser.add_argument(
169      '--apply-startup-profile',
170      action='store_true',
171      help='Whether to pass --input-art-profile as a startup profile to R8.')
172  parser.add_argument(
173      '--keep-rules-targets-regex',
174      metavar='KEEP_RULES_REGEX',
175      help='If passed outputs keep rules for references from all other inputs '
176      'to the subset of inputs that satisfy the KEEP_RULES_REGEX.')
177  parser.add_argument(
178      '--keep-rules-output-path',
179      help='Output path to the keep rules for references to the '
180      '--keep-rules-targets-regex inputs from the rest of the inputs.')
181  parser.add_argument('--warnings-as-errors',
182                      action='store_true',
183                      help='Treat all warnings as errors.')
184  parser.add_argument('--show-desugar-default-interface-warnings',
185                      action='store_true',
186                      help='Enable desugaring warnings.')
187  parser.add_argument('--dump-inputs',
188                      action='store_true',
189                      help='Use when filing R8 bugs to capture inputs.'
190                      ' Stores inputs to r8inputs.zip')
191  parser.add_argument(
192      '--dump-unknown-refs',
193      action='store_true',
194      help='Log all reasons why API modelling cannot determine API level')
195  parser.add_argument(
196      '--stamp',
197      help='File to touch upon success. Mutually exclusive with --output-path')
198  parser.add_argument('--desugared-library-keep-rule-output',
199                      help='Path to desugared library keep rule output file.')
200
201  diff_utils.AddCommandLineFlags(parser)
202  options = parser.parse_args(args)
203
204  if options.feature_names:
205    if options.output_path:
206      parser.error('Feature splits cannot specify an output in GN.')
207    if not options.actual_file and not options.stamp:
208      parser.error('Feature splits require a stamp file as output.')
209  elif not options.output_path:
210    parser.error('Output path required when feature splits aren\'t used')
211
212  if bool(options.keep_rules_targets_regex) != bool(
213      options.keep_rules_output_path):
214    parser.error('You must path both --keep-rules-targets-regex and '
215                 '--keep-rules-output-path')
216
217  if options.output_art_profile and not options.input_art_profile:
218    parser.error('--output-art-profile requires --input-art-profile')
219  if options.apply_startup_profile and not options.input_art_profile:
220    parser.error('--apply-startup-profile requires --input-art-profile')
221
222  if options.force_enable_assertions and options.assertion_handler:
223    parser.error('Cannot use both --force-enable-assertions and '
224                 '--assertion-handler')
225
226  options.classpath = action_helpers.parse_gn_list(options.classpath)
227  options.proguard_configs = action_helpers.parse_gn_list(
228      options.proguard_configs)
229  options.input_paths = action_helpers.parse_gn_list(options.input_paths)
230  options.extra_mapping_output_paths = action_helpers.parse_gn_list(
231      options.extra_mapping_output_paths)
232  if os.environ.get('R8_VERBOSE') == '1':
233    options.verbose = True
234
235  if options.feature_names:
236    if 'base' not in options.feature_names:
237      parser.error('"base" feature required when feature arguments are used.')
238    if len(options.feature_names) != len(options.feature_jars) or len(
239        options.feature_names) != len(options.dex_dests):
240      parser.error('Invalid feature argument lengths.')
241
242    options.feature_jars = [
243        action_helpers.parse_gn_list(x) for x in options.feature_jars
244    ]
245
246  split_map = {}
247  if options.uses_split:
248    for split_pair in options.uses_split:
249      child, parent = split_pair.split(':')
250      for name in (child, parent):
251        if name not in options.feature_names:
252          parser.error('"%s" referenced in --uses-split not present.' % name)
253      split_map[child] = parent
254  options.uses_split = split_map
255
256  return options
257
258
259class _SplitContext:
260  def __init__(self, name, output_path, input_jars, work_dir, parent_name=None):
261    self.name = name
262    self.parent_name = parent_name
263    self.input_jars = set(input_jars)
264    self.final_output_path = output_path
265    self.staging_dir = os.path.join(work_dir, name)
266    os.mkdir(self.staging_dir)
267
268  def CreateOutput(self):
269    found_files = build_utils.FindInDirectory(self.staging_dir)
270    if not found_files:
271      raise Exception('Missing dex outputs in {}'.format(self.staging_dir))
272
273    if self.final_output_path.endswith('.dex'):
274      if len(found_files) != 1:
275        raise Exception('Expected exactly 1 dex file output, found: {}'.format(
276            '\t'.join(found_files)))
277      shutil.move(found_files[0], self.final_output_path)
278      return
279
280    # Add to .jar using Python rather than having R8 output to a .zip directly
281    # in order to disable compression of the .jar, saving ~500ms.
282    tmp_jar_output = self.staging_dir + '.jar'
283    zip_helpers.add_files_to_zip(found_files,
284                                 tmp_jar_output,
285                                 base_dir=self.staging_dir)
286    shutil.move(tmp_jar_output, self.final_output_path)
287
288
289def _OptimizeWithR8(options, config_paths, libraries, dynamic_config_data):
290  with build_utils.TempDir() as tmp_dir:
291    if dynamic_config_data:
292      dynamic_config_path = os.path.join(tmp_dir, 'dynamic_config.flags')
293      with open(dynamic_config_path, 'w') as f:
294        f.write(dynamic_config_data)
295      config_paths = config_paths + [dynamic_config_path]
296
297    tmp_mapping_path = os.path.join(tmp_dir, 'mapping.txt')
298    # If there is no output (no classes are kept), this prevents this script
299    # from failing.
300    build_utils.Touch(tmp_mapping_path)
301
302    tmp_output = os.path.join(tmp_dir, 'r8out')
303    os.mkdir(tmp_output)
304
305    split_contexts_by_name = {}
306    if options.feature_names:
307      for name, dest_dex, input_jars in zip(options.feature_names,
308                                            options.dex_dests,
309                                            options.feature_jars):
310        parent_name = options.uses_split.get(name)
311        if parent_name is None and name != 'base':
312          parent_name = 'base'
313        split_context = _SplitContext(name,
314                                      dest_dex,
315                                      input_jars,
316                                      tmp_output,
317                                      parent_name=parent_name)
318        split_contexts_by_name[name] = split_context
319    else:
320      # Base context will get populated via "extra_jars" below.
321      split_contexts_by_name['base'] = _SplitContext('base',
322                                                     options.output_path, [],
323                                                     tmp_output)
324    base_context = split_contexts_by_name['base']
325
326    # List of packages that R8 does not currently have in its API database:
327    # https://b/326252366
328    extension_packages = [
329        'android.car',
330        'android.car.hardware.property',
331        'android.car.input',
332        'android.car.media',
333        'android.car.remoteaccess',
334        'android.car.watchdog',
335        'androidx.window.extensions',
336        'androidx.window.extensions.area',
337        'androidx.window.extensions.core',
338        'androidx.window.extensions.core.util',
339        'androidx.window.extensions.core.util.function',
340        'androidx.window.extensions.layout',
341        'androidx.window.extensions.embedding',
342        'androidx.window.layout.adapter.extensions',
343    ]
344
345    # R8 OOMs with the default xmx=1G.
346    cmd = build_utils.JavaCmd(xmx='2G') + [
347        # Allows -whyareyounotinlining, which we don't have by default, but
348        # which is useful for one-off queries.
349        '-Dcom.android.tools.r8.experimental.enablewhyareyounotinlining=1',
350        # Restricts horizontal class merging to apply only to classes that
351        # share a .java file (nested classes). https://crbug.com/1363709
352        '-Dcom.android.tools.r8.enableSameFilePolicy=1',
353        # Enable API modelling for OS extensions.
354        '-Dcom.android.tools.r8.androidApiExtensionPackages=' +
355        ','.join(extension_packages),
356    ]
357    if options.dump_inputs:
358      cmd += [f'-Dcom.android.tools.r8.dumpinputtodirectory={_DUMP_DIR_NAME}']
359    if options.dump_unknown_refs:
360      cmd += ['-Dcom.android.tools.r8.reportUnknownApiReferences=1']
361    cmd += [
362        '-cp',
363        '{}:{}'.format(options.r8_path, options.custom_r8_path),
364        'org.chromium.build.CustomR8',
365        '--no-data-resources',
366        '--map-id-template',
367        f'{options.source_file} ({options.package_name})',
368        '--source-file-template',
369        options.source_file,
370        '--output',
371        base_context.staging_dir,
372        '--pg-map-output',
373        tmp_mapping_path,
374    ]
375
376    if options.uses_split:
377      cmd += ['--isolated-splits']
378
379    if options.disable_checks:
380      cmd += ['--map-diagnostics:CheckDiscardDiagnostic', 'error', 'none']
381    cmd += ['--map-diagnostics', 'info', 'warning']
382    # An "error" level diagnostic causes r8 to return an error exit code. Doing
383    # this allows our filter to decide what should/shouldn't break our build.
384    cmd += ['--map-diagnostics', 'error', 'warning']
385
386    if options.min_api:
387      cmd += ['--min-api', options.min_api]
388
389    if options.assertion_handler:
390      cmd += ['--force-assertions-handler:' + options.assertion_handler]
391    elif options.force_enable_assertions:
392      cmd += ['--force-enable-assertions']
393
394    for lib in libraries:
395      cmd += ['--lib', lib]
396
397    for config_file in config_paths:
398      cmd += ['--pg-conf', config_file]
399
400    if options.main_dex_rules_path:
401      for main_dex_rule in options.main_dex_rules_path:
402        cmd += ['--main-dex-rules', main_dex_rule]
403
404    if options.output_art_profile:
405      cmd += [
406          '--art-profile',
407          options.input_art_profile,
408          options.output_art_profile,
409      ]
410    if options.apply_startup_profile:
411      cmd += [
412          '--startup-profile',
413          options.input_art_profile,
414      ]
415
416    # Add any extra inputs to the base context (e.g. desugar runtime).
417    extra_jars = set(options.input_paths)
418    for split_context in split_contexts_by_name.values():
419      extra_jars -= split_context.input_jars
420    base_context.input_jars.update(extra_jars)
421
422    for split_context in split_contexts_by_name.values():
423      if split_context is base_context:
424        continue
425      for in_jar in sorted(split_context.input_jars):
426        cmd += ['--feature', in_jar, split_context.staging_dir]
427
428    cmd += sorted(base_context.input_jars)
429
430    if options.verbose:
431      stderr_filter = None
432    else:
433      filters = list(dex.DEFAULT_IGNORE_WARNINGS)
434      filters += _IGNORE_WARNINGS
435      if options.show_desugar_default_interface_warnings:
436        filters += dex.INTERFACE_DESUGARING_WARNINGS
437      stderr_filter = dex.CreateStderrFilter(filters)
438
439    try:
440      logging.debug('Running R8')
441      build_utils.CheckOutput(cmd,
442                              print_stdout=True,
443                              stderr_filter=stderr_filter,
444                              fail_on_output=options.warnings_as_errors)
445    except build_utils.CalledProcessError as e:
446      # Do not output command line because it is massive and makes the actual
447      # error message hard to find.
448      sys.stderr.write(e.output)
449      sys.exit(1)
450
451    logging.debug('Collecting ouputs')
452    base_context.CreateOutput()
453    for split_context in split_contexts_by_name.values():
454      if split_context is not base_context:
455        split_context.CreateOutput()
456
457    shutil.move(tmp_mapping_path, options.mapping_output)
458  return split_contexts_by_name
459
460
461def _OutputKeepRules(r8_path, input_paths, classpath, targets_re_string,
462                     keep_rules_output):
463
464  cmd = build_utils.JavaCmd() + [
465      '-cp', r8_path, 'com.android.tools.r8.tracereferences.TraceReferences',
466      '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
467      '--keep-rules', '--output', keep_rules_output
468  ]
469  targets_re = re.compile(targets_re_string)
470  for path in input_paths:
471    if targets_re.search(path):
472      cmd += ['--target', path]
473    else:
474      cmd += ['--source', path]
475  for path in classpath:
476    cmd += ['--lib', path]
477
478  build_utils.CheckOutput(cmd, print_stderr=False, fail_on_output=False)
479
480
481def _CheckForMissingSymbols(options, dex_files, error_title):
482  cmd = build_utils.JavaCmd()
483
484  if options.dump_inputs:
485    cmd += [f'-Dcom.android.tools.r8.dumpinputtodirectory={_DUMP_DIR_NAME}']
486
487  cmd += [
488      '-cp', options.r8_path,
489      'com.android.tools.r8.tracereferences.TraceReferences',
490      '--map-diagnostics:MissingDefinitionsDiagnostic', 'error', 'warning',
491      '--check'
492  ]
493
494  for path in options.classpath:
495    cmd += ['--lib', path]
496  for path in dex_files:
497    cmd += ['--source', path]
498
499  failed_holder = [False]
500
501  def stderr_filter(stderr):
502    ignored_lines = [
503        # Summary contains warning count, which our filtering makes wrong.
504        'Warning: Tracereferences found',
505
506        # TODO(agrieve): Create interface jars for these missing classes rather
507        #     than allowlisting here.
508        'dalvik.system',
509        'libcore.io',
510        'sun.misc.Unsafe',
511
512        # Found in: com/facebook/fbui/textlayoutbuilder/StaticLayoutHelper
513        'android.text.StaticLayout.<init>',
514        # TODO(crbug.com/40261573): Remove once chrome builds with Android U
515        # SDK.
516        ' android.',
517
518        # Explicictly guarded by try (NoClassDefFoundError) in Flogger's
519        # PlatformProvider.
520        'com.google.common.flogger.backend.google.GooglePlatform',
521        'com.google.common.flogger.backend.system.DefaultPlatform',
522
523        # TODO(agrieve): Exclude these only when use_jacoco_coverage=true.
524        'java.lang.instrument.ClassFileTransformer',
525        'java.lang.instrument.IllegalClassFormatException',
526        'java.lang.instrument.Instrumentation',
527        'java.lang.management.ManagementFactory',
528        'javax.management.MBeanServer',
529        'javax.management.ObjectInstance',
530        'javax.management.ObjectName',
531        'javax.management.StandardMBean',
532
533        # Explicitly guarded by try (NoClassDefFoundError) in Firebase's
534        # KotlinDetector: com.google.firebase.platforminfo.KotlinDetector.
535        'kotlin.KotlinVersion',
536    ]
537
538    had_unfiltered_items = '  ' in stderr
539    stderr = build_utils.FilterLines(
540        stderr, '|'.join(re.escape(x) for x in ignored_lines))
541    if stderr:
542      if 'Missing' in stderr:
543        failed_holder[0] = True
544        stderr = 'TraceReferences failed: ' + error_title + """
545Tip: Build with:
546        is_java_debug=false
547        treat_warnings_as_errors=false
548        enable_proguard_obfuscation=false
549     and then use dexdump to see which class(s) reference them.
550
551     E.g.:
552       third_party/android_sdk/public/build-tools/*/dexdump -d \
553out/Release/apks/YourApk.apk > dex.txt
554""" + stderr
555      elif had_unfiltered_items:
556        # Left only with empty headings. All indented items filtered out.
557        stderr = ''
558    return stderr
559
560  try:
561    if options.verbose:
562      stderr_filter = None
563    build_utils.CheckOutput(cmd,
564                            print_stdout=True,
565                            stderr_filter=stderr_filter,
566                            fail_on_output=options.warnings_as_errors)
567  except build_utils.CalledProcessError as e:
568    # Do not output command line because it is massive and makes the actual
569    # error message hard to find.
570    sys.stderr.write(e.output)
571    sys.exit(1)
572  return failed_holder[0]
573
574
575def _CombineConfigs(configs,
576                    dynamic_config_data,
577                    embedded_configs,
578                    exclude_generated=False):
579  # Sort in this way so //clank versions of the same libraries will sort
580  # to the same spot in the file.
581  def sort_key(path):
582    return tuple(reversed(path.split(os.path.sep)))
583
584  def format_config_contents(path, contents):
585    formatted_contents = []
586    if not contents.strip():
587      return []
588
589    # Fix up line endings (third_party configs can have windows endings).
590    contents = contents.replace('\r', '')
591    # Remove numbers from generated rule comments to make file more
592    # diff'able.
593    contents = re.sub(r' #generated:\d+', '', contents)
594    formatted_contents.append('# File: ' + path)
595    formatted_contents.append(contents)
596    formatted_contents.append('')
597    return formatted_contents
598
599  ret = []
600  for config in sorted(configs, key=sort_key):
601    if exclude_generated and config.endswith('.resources.proguard.txt'):
602      continue
603
604    # Exclude some confs from expectations.
605    if any(entry in config for entry in _BLOCKLISTED_EXPECTATION_PATHS):
606      continue
607
608    with open(config) as config_file:
609      contents = config_file.read().rstrip()
610
611    ret.extend(format_config_contents(config, contents))
612
613  for path, contents in sorted(embedded_configs.items()):
614    ret.extend(format_config_contents(path, contents))
615
616
617  if dynamic_config_data:
618    ret.append('# File: //build/android/gyp/proguard.py (generated rules)')
619    ret.append(dynamic_config_data)
620    ret.append('')
621  return '\n'.join(ret)
622
623
624def _CreateDynamicConfig(options):
625  ret = []
626  if options.enable_obfuscation:
627    ret.append(f"-repackageclasses '{options.repackage_classes}'")
628  else:
629    ret.append("-dontobfuscate")
630
631  if options.apply_mapping:
632    ret.append("-applymapping '%s'" % options.apply_mapping)
633
634  return '\n'.join(ret)
635
636
637def _ExtractEmbeddedConfigs(jar_path, embedded_configs):
638  with zipfile.ZipFile(jar_path) as z:
639    proguard_names = []
640    r8_names = []
641    for info in z.infolist():
642      if info.is_dir():
643        continue
644      if info.filename.startswith('META-INF/proguard/'):
645        proguard_names.append(info.filename)
646      elif info.filename.startswith('META-INF/com.android.tools/r8/'):
647        r8_names.append(info.filename)
648      elif info.filename.startswith('META-INF/com.android.tools/r8-from'):
649        # Assume our version of R8 is always latest.
650        if '-upto-' not in info.filename:
651          r8_names.append(info.filename)
652
653    # Give preference to r8-from-*, then r8/, then proguard/.
654    active = r8_names or proguard_names
655    for filename in active:
656      config_path = '{}:{}'.format(jar_path, filename)
657      embedded_configs[config_path] = z.read(filename).decode('utf-8').rstrip()
658
659
660def _MaybeWriteStampAndDepFile(options, inputs):
661  output = options.output_path
662  if options.stamp:
663    build_utils.Touch(options.stamp)
664    output = options.stamp
665  if options.depfile:
666    action_helpers.write_depfile(options.depfile, output, inputs=inputs)
667
668
669def _IterParentContexts(context_name, split_contexts_by_name):
670  while context_name:
671    context = split_contexts_by_name[context_name]
672    yield context
673    context_name = context.parent_name
674
675
676def _DoTraceReferencesChecks(options, split_contexts_by_name):
677  # Set of all contexts that are a parent to another.
678  parent_splits_context_names = {
679      c.parent_name
680      for c in split_contexts_by_name.values() if c.parent_name
681  }
682  context_sets = [
683      list(_IterParentContexts(n, split_contexts_by_name))
684      for n in parent_splits_context_names
685  ]
686  # Visit them in order of: base, base+chrome, base+chrome+thing.
687  context_sets.sort(key=lambda x: (len(x), x[0].name))
688
689  # Ensure there are no missing references when considering all dex files.
690  error_title = 'DEX contains references to non-existent symbols after R8.'
691  dex_files = sorted(c.final_output_path
692                     for c in split_contexts_by_name.values())
693  if _CheckForMissingSymbols(options, dex_files, error_title):
694    # Failed but didn't raise due to warnings_as_errors=False
695    return
696
697  for context_set in context_sets:
698    # Ensure there are no references from base -> chrome module, or from
699    # chrome -> feature modules.
700    error_title = (f'DEX within module "{context_set[0].name}" contains '
701                   'reference(s) to symbols within child splits')
702    dex_files = [c.final_output_path for c in context_set]
703    # Each check currently takes about 3 seconds on a fast dev machine, and we
704    # run 3 of them (all, base, base+chrome).
705    # We could run them concurrently, to shave off 5-6 seconds, but would need
706    # to make sure that the order is maintained.
707    if _CheckForMissingSymbols(options, dex_files, error_title):
708      # Failed but didn't raise due to warnings_as_errors=False
709      return
710
711
712def _Run(options):
713  # ProGuard configs that are derived from flags.
714  logging.debug('Preparing configs')
715  dynamic_config_data = _CreateDynamicConfig(options)
716
717  logging.debug('Looking for embedded configs')
718  # If a jar is part of input no need to include it as library jar.
719  libraries = [p for p in options.classpath if p not in options.input_paths]
720
721  embedded_configs = {}
722  for jar_path in options.input_paths + libraries:
723    _ExtractEmbeddedConfigs(jar_path, embedded_configs)
724
725  # ProGuard configs that are derived from flags.
726  merged_configs = _CombineConfigs(options.proguard_configs,
727                                   dynamic_config_data,
728                                   embedded_configs,
729                                   exclude_generated=True)
730
731  depfile_inputs = options.proguard_configs + options.input_paths + libraries
732  if options.expected_file:
733    diff_utils.CheckExpectations(merged_configs, options)
734    if options.only_verify_expectations:
735      action_helpers.write_depfile(options.depfile,
736                                   options.actual_file,
737                                   inputs=depfile_inputs)
738      return
739
740  if options.keep_rules_output_path:
741    _OutputKeepRules(options.r8_path, options.input_paths, options.classpath,
742                     options.keep_rules_targets_regex,
743                     options.keep_rules_output_path)
744    return
745
746  split_contexts_by_name = _OptimizeWithR8(options, options.proguard_configs,
747                                           libraries, dynamic_config_data)
748
749  if not options.disable_checks:
750    logging.debug('Running tracereferences')
751    _DoTraceReferencesChecks(options, split_contexts_by_name)
752
753  for output in options.extra_mapping_output_paths:
754    shutil.copy(options.mapping_output, output)
755
756  if options.apply_mapping:
757    depfile_inputs.append(options.apply_mapping)
758
759  _MaybeWriteStampAndDepFile(options, depfile_inputs)
760
761
762def main():
763  build_utils.InitLogging('PROGUARD_DEBUG')
764  options = _ParseOptions()
765
766  if options.dump_inputs:
767    # Dumping inputs causes output to be emitted, avoid failing due to stdout.
768    options.warnings_as_errors = False
769    # Use dumpinputtodirectory instead of dumpinputtofile to avoid failing the
770    # build and keep running tracereferences.
771    dump_dir_name = _DUMP_DIR_NAME
772    dump_dir_path = pathlib.Path(dump_dir_name)
773    if dump_dir_path.exists():
774      shutil.rmtree(dump_dir_path)
775    # The directory needs to exist before r8 adds the zip files in it.
776    dump_dir_path.mkdir()
777
778  # This ensure that the final outputs are zipped and easily uploaded to a bug.
779  try:
780    _Run(options)
781  finally:
782    if options.dump_inputs:
783      zip_helpers.zip_directory('r8inputs.zip', _DUMP_DIR_NAME)
784
785
786if __name__ == '__main__':
787  main()
788