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