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