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