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