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