1# Copyright 2020 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Bazel Android Proguard library for the Android rules.""" 16 17load(":android_neverlink_aspect.bzl", "StarlarkAndroidNeverlinkInfo") 18load(":baseline_profiles.bzl", _baseline_profiles = "baseline_profiles") 19load(":common.bzl", "common") 20load(":java.bzl", "java") 21load(":utils.bzl", "ANDROID_TOOLCHAIN_TYPE", "get_android_sdk", "utils") 22 23_ProguardSpecContextInfo = provider( 24 doc = "Contains data from processing Proguard specs.", 25 fields = dict( 26 proguard_configs = "The direct proguard configs", 27 transitive_proguard_configs = 28 "The proguard configs within the transitive closure of the target", 29 providers = "The list of all providers to propagate.", 30 ), 31) 32 33def _validate_proguard_spec( 34 ctx, 35 out_validated_proguard_spec, 36 proguard_spec, 37 proguard_allowlister): 38 args = ctx.actions.args() 39 args.add("--path", proguard_spec) 40 args.add("--output", out_validated_proguard_spec) 41 42 ctx.actions.run( 43 executable = proguard_allowlister, 44 arguments = [args], 45 inputs = [proguard_spec], 46 outputs = [out_validated_proguard_spec], 47 mnemonic = "ValidateProguard", 48 progress_message = ( 49 "Validating proguard configuration %s" % proguard_spec.short_path 50 ), 51 toolchain = ANDROID_TOOLCHAIN_TYPE, 52 ) 53 54def _process_specs( 55 ctx, 56 proguard_configs = [], 57 proguard_spec_providers = [], 58 proguard_allowlister = None): 59 """Processes Proguard Specs 60 61 Args: 62 ctx: The context. 63 proguard_configs: sequence of Files. A list of proguard config files to be 64 processed. Optional. 65 proguard_spec_providers: sequence of ProguardSpecProvider providers. A 66 list of providers from the dependencies, exports, plugins, 67 exported_plugins, etc. Optional. 68 proguard_allowlister: The proguard_allowlister exeutable provider. 69 70 Returns: 71 A _ProguardSpecContextInfo provider. 72 """ 73 74 # TODO(djwhang): Look to see if this can be just a validation action and the 75 # proguard_spec provided by the rule can be propagated. 76 validated_proguard_configs = [] 77 for proguard_spec in proguard_configs: 78 validated_proguard_spec = ctx.actions.declare_file( 79 "validated_proguard/%s/%s_valid" % 80 (ctx.label.name, proguard_spec.path), 81 ) 82 _validate_proguard_spec( 83 ctx, 84 validated_proguard_spec, 85 proguard_spec, 86 proguard_allowlister, 87 ) 88 validated_proguard_configs.append(validated_proguard_spec) 89 90 transitive_validated_proguard_configs = [] 91 for info in proguard_spec_providers: 92 transitive_validated_proguard_configs.append(info.specs) 93 94 transitive_proguard_configs = depset( 95 validated_proguard_configs, 96 transitive = transitive_validated_proguard_configs, 97 order = "preorder", 98 ) 99 return _ProguardSpecContextInfo( 100 proguard_configs = proguard_configs, 101 transitive_proguard_configs = transitive_proguard_configs, 102 providers = [ 103 ProguardSpecProvider(transitive_proguard_configs), 104 # TODO(b/152659272): Remove this once the android_archive rule is 105 # able to process a transitive closure of deps to produce an aar. 106 AndroidProguardInfo(proguard_configs), 107 ], 108 ) 109 110def _collect_transitive_proguard_specs( 111 specs_to_include, 112 local_proguard_specs, 113 proguard_deps): 114 if len(local_proguard_specs) == 0: 115 return [] 116 117 proguard_specs = depset( 118 local_proguard_specs + specs_to_include, 119 transitive = [dep.specs for dep in proguard_deps], 120 ) 121 return sorted(proguard_specs.to_list()) 122 123def _get_proguard_specs( 124 ctx, 125 resource_proguard_config, 126 proguard_specs_for_manifest = []): 127 proguard_deps = utils.collect_providers(ProguardSpecProvider, utils.dedupe_split_attr(ctx.split_attr.deps)) 128 if ctx.configuration.coverage_enabled and hasattr(ctx.attr, "_jacoco_runtime"): 129 proguard_deps.append(ctx.attr._jacoco_runtime[ProguardSpecProvider]) 130 131 local_proguard_specs = [] 132 if ctx.files.proguard_specs: 133 local_proguard_specs = ctx.files.proguard_specs 134 proguard_specs = _collect_transitive_proguard_specs( 135 [resource_proguard_config], 136 local_proguard_specs, 137 proguard_deps, 138 ) 139 140 if len(proguard_specs) > 0 and ctx.fragments.android.assume_min_sdk_version: 141 # NB: Order here is important. We're including generated Proguard specs before the user's 142 # specs so that they can override values. 143 proguard_specs = proguard_specs_for_manifest + proguard_specs 144 145 return proguard_specs 146 147def _generate_min_sdk_version_assumevalues( 148 ctx, 149 output = None, 150 manifest = None, 151 generate_exec = None): 152 """Reads the minSdkVersion from an AndroidManifest to generate Proguard specs.""" 153 args = ctx.actions.args() 154 inputs = [] 155 outputs = [] 156 157 args.add("--manifest", manifest) 158 inputs.append(manifest) 159 160 args.add("--output", output) 161 outputs.append(output) 162 163 ctx.actions.run( 164 inputs = inputs, 165 outputs = outputs, 166 executable = generate_exec, 167 arguments = [args], 168 mnemonic = "MinSdkVersionAssumeValuesProguardSpecGenerator", 169 progress_message = "Adding -assumevalues spec for minSdkVersion", 170 ) 171 172def _optimization_action( 173 ctx, 174 output_jar, 175 program_jar, 176 library_jar, 177 proguard_specs, 178 proguard_mapping = None, 179 proguard_output_map = None, 180 proguard_seeds = None, 181 proguard_usage = None, 182 proguard_config_output = None, 183 startup_profile = None, 184 startup_profile_rewritten = None, 185 baseline_profile = None, 186 baseline_profile_rewritten = None, 187 runtype = None, 188 last_stage_output = None, 189 next_stage_output = None, 190 final = False, 191 mnemonic = None, 192 progress_message = None, 193 proguard_tool = None): 194 """Creates a Proguard optimization action. 195 196 This method is expected to be called one or more times to create Proguard optimization actions. 197 Most outputs will only be generated by the final optimization action, and should otherwise be 198 set to None. For the final action set `final = True` which will register the output_jar as an 199 output of the action. 200 201 TODO(b/286955442): Support baseline profiles. 202 203 Args: 204 ctx: The context. 205 output_jar: File. The final output jar. 206 program_jar: File. The jar to be optimized. 207 library_jar: File. The merged library jar. While the underlying tooling supports multiple 208 library jars, we merge these into a single jar before processing. 209 proguard_specs: Sequence of files. A list of proguard specs to use for the optimization. 210 proguard_mapping: File. Optional file to be used as a mapping for proguard. A mapping file 211 generated by proguard_generate_mapping to be re-used to apply the same map to a new build. 212 proguard_output_map: File. Optional file to be used to write the output map of obfuscated 213 class and member names. 214 proguard_seeds: File. Optional file used to write the "seeds", which is a list of all 215 classes and members which match a keep rule. 216 proguard_usage: File. Optional file used to write all classes and members that are removed 217 during shrinking (i.e. unused code). 218 proguard_config_output: File. Optional file used to write the entire configuration that has 219 been parsed, included files and replaced variables. Useful for debugging. 220 startup_profile: File. Optional. The merged startup profile to be optimized. 221 startup_profile_rewritten: File. Optional file used to write the optimized startup profile. 222 baseline_profile: File. Optional. The merged baseline profile to be optimized. 223 baseline_profile_rewritten: File. Optional file used to write the optimized profile rules. 224 runtype: String. Optional string identifying this run. One of [INITIAL, OPTIMIZATION, FINAL] 225 last_stage_output: File. Optional input file to this optimization stage, which was output by 226 the previous optimization stage. 227 next_stage_output: File. Optional output file from this optimization stage, which will be 228 consunmed by the next optimization stage. 229 final: Boolean. Whether this is the final optimization stage, which will register output_jar 230 as an output of this action. 231 mnemonic: String. Action mnemonic. 232 progress_message: String. Action progress message. 233 proguard_tool: FilesToRunProvider. The proguard tool to execute. 234 235 Returns: 236 None 237 """ 238 239 inputs = [] 240 outputs = [] 241 args = ctx.actions.args() 242 243 args.add("-forceprocessing") 244 245 args.add("-injars", program_jar) 246 inputs.append(program_jar) 247 248 args.add("-outjars", output_jar) 249 if final: 250 outputs.append(output_jar) 251 252 args.add("-libraryjars", library_jar) 253 inputs.append(library_jar) 254 255 if proguard_mapping: 256 args.add("-applymapping", proguard_mapping) 257 inputs.append(proguard_mapping) 258 259 args.add_all(proguard_specs, format_each = "@%s") 260 inputs.extend(proguard_specs) 261 262 if proguard_output_map: 263 args.add("-printmapping", proguard_output_map) 264 outputs.append(proguard_output_map) 265 266 if proguard_seeds: 267 args.add("-printseeds", proguard_seeds) 268 outputs.append(proguard_seeds) 269 270 if proguard_usage: 271 args.add("-printusage", proguard_usage) 272 outputs.append(proguard_usage) 273 274 if proguard_config_output: 275 args.add("-printconfiguration", proguard_config_output) 276 outputs.append(proguard_config_output) 277 278 if startup_profile: 279 args.add("-startupprofile", startup_profile) 280 inputs.append(startup_profile) 281 282 if startup_profile_rewritten: 283 args.add("-printstartupprofile", startup_profile) 284 outputs.append(startup_profile_rewritten) 285 286 if baseline_profile: 287 args.add("-baselineprofile", baseline_profile) 288 inputs.append(baseline_profile) 289 290 if baseline_profile_rewritten: 291 args.add("-printbaselineprofile", baseline_profile_rewritten) 292 outputs.append(baseline_profile_rewritten) 293 294 if runtype: 295 args.add("-runtype " + runtype) 296 297 if last_stage_output: 298 args.add("-laststageoutput", last_stage_output) 299 inputs.append(last_stage_output) 300 301 if next_stage_output: 302 args.add("-nextstageoutput", next_stage_output) 303 outputs.append(next_stage_output) 304 305 ctx.actions.run( 306 outputs = outputs, 307 inputs = inputs, 308 executable = proguard_tool, 309 arguments = [args], 310 mnemonic = mnemonic, 311 progress_message = progress_message, 312 toolchain = None, # TODO(timpeut): correctly set this based off which optimizer is selected 313 ) 314 315def _get_proguard_temp_artifact_with_prefix(ctx, label, prefix, name): 316 native_label_name = label.name.removesuffix(common.PACKAGED_RESOURCES_SUFFIX) 317 return ctx.actions.declare_file("proguard/" + native_label_name + "/" + prefix + "_" + native_label_name + "_" + name) 318 319def _get_proguard_temp_artifact(ctx, name): 320 return _get_proguard_temp_artifact_with_prefix(ctx, ctx.label, "MIGRATED", name) 321 322def _get_proguard_output_map(ctx): 323 return ctx.actions.declare_file(ctx.label.name.removesuffix(common.PACKAGED_RESOURCES_SUFFIX) + "_proguard_MIGRATED_.map") 324 325def _apply_proguard( 326 ctx, 327 input_jar = None, 328 proguard_specs = [], 329 proguard_optimization_passes = None, 330 proguard_mapping = None, 331 proguard_output_jar = None, 332 proguard_output_map = None, 333 proguard_seeds = None, 334 proguard_usage = None, 335 startup_profile = None, 336 baseline_profile = None, 337 proguard_tool = None): 338 """Top-level method to apply proguard to a jar. 339 340 Args: 341 ctx: The context 342 input_jar: File. The input jar to optimized. 343 proguard_specs: List of Files. The proguard specs to use for optimization. 344 proguard_optimization_passes: Integer. The number of proguard passes to apply. 345 proguard_mapping: File. The proguard mapping to apply. 346 proguard_output_jar: File. The output optimized jar. 347 proguard_output_map: File. The output proguard map. 348 proguard_seeds: File. The output proguard seeds. 349 proguard_usage: File. The output proguard usage. 350 startup_profile: File. The input merged startup profile to be optimized. 351 baseline_profile: File. The input merged baseline profile to be optimized. 352 proguard_tool: FilesToRun. The proguard executable. 353 354 Returns: 355 A struct of proguard outputs. 356 """ 357 if not proguard_specs: 358 outputs = _get_proguard_output( 359 ctx, 360 proguard_output_jar = proguard_output_jar, 361 proguard_seeds = None, 362 proguard_usage = None, 363 proguard_output_map = proguard_output_map, 364 combined_library_jar = None, 365 startup_profile_rewritten = None, 366 baseline_profile_rewritten = None, 367 ) 368 369 # Fail at execution time if these artifacts are requested, to avoid issue where outputs are 370 # declared without having any proguard specs. This can happen if specs is a select() that 371 # resolves to an empty list. 372 _fail_action( 373 ctx, 374 outputs.output_jar, 375 outputs.mapping, 376 outputs.config, 377 proguard_seeds, 378 proguard_usage, 379 ) 380 return outputs 381 382 library_jar_list = [get_android_sdk(ctx).android_jar] 383 if ctx.fragments.android.desugar_java8: 384 library_jar_list.append(ctx.file._desugared_java8_legacy_apis) 385 neverlink_infos = utils.collect_providers(StarlarkAndroidNeverlinkInfo, ctx.attr.deps) 386 library_jars = depset(library_jar_list, transitive = [info.transitive_neverlink_libraries for info in neverlink_infos]) 387 388 return _create_optimization_actions( 389 ctx, 390 proguard_specs, 391 proguard_seeds, 392 proguard_usage, 393 proguard_mapping, 394 proguard_output_jar, 395 proguard_optimization_passes, 396 proguard_output_map, 397 input_jar, 398 library_jars, 399 startup_profile, 400 baseline_profile, 401 proguard_tool, 402 ) 403 404def _get_proguard_output( 405 ctx, 406 proguard_output_jar, 407 proguard_seeds, 408 proguard_usage, 409 proguard_output_map, 410 combined_library_jar, 411 startup_profile_rewritten, 412 baseline_profile_rewritten): 413 """Helper method to get a struct of all proguard outputs.""" 414 415 # Proto Output Map is currently empty from ProGuard. 416 proguard_output_proto_map = None 417 if proguard_output_map: 418 proguard_output_proto_map = _get_proguard_temp_artifact(ctx, "_proguard.pbmap") 419 ctx.actions.write(proguard_output_proto_map, content = "") 420 421 config_output = _get_proguard_temp_artifact(ctx, "_proguard.config") 422 423 return struct( 424 output_jar = proguard_output_jar, 425 mapping = proguard_output_map, 426 proto_mapping = proguard_output_proto_map, 427 seeds = proguard_seeds, 428 usage = proguard_usage, 429 library_jar = combined_library_jar, 430 config = config_output, 431 startup_profile_rewritten = startup_profile_rewritten, 432 baseline_profile_rewritten = baseline_profile_rewritten, 433 ) 434 435def _create_optimization_actions( 436 ctx, 437 proguard_specs = None, 438 proguard_seeds = None, 439 proguard_usage = None, 440 proguard_mapping = None, 441 proguard_output_jar = None, 442 num_passes = None, 443 proguard_output_map = None, 444 input_jar = None, 445 library_jars = depset(), 446 startup_profile = None, 447 baseline_profile = None, 448 proguard_tool = None): 449 """Helper method to create all optimizaction actions based on the target configuration.""" 450 if not proguard_specs: 451 fail("Missing proguard_specs in create_optimization_actions") 452 453 # Merge all library jars into a single jar 454 combined_library_jar = _get_proguard_temp_artifact(ctx, "_migrated_combined_library_jars.jar") 455 java.singlejar( 456 ctx, 457 library_jars, 458 combined_library_jar, 459 java_toolchain = common.get_java_toolchain(ctx), 460 ) 461 462 # Filter library jar with program jar 463 filtered_library_jar = _get_proguard_temp_artifact(ctx, "_migrated_combined_library_jars_filtered.jar") 464 common.filter_zip_exclude( 465 ctx, 466 filtered_library_jar, 467 combined_library_jar, 468 filter_zips = [input_jar], 469 ) 470 471 startup_profile_rewritten = None 472 baseline_profile_rewritten = None 473 if startup_profile: 474 startup_profile_rewritten = _baseline_profiles.get_profile_artifact(ctx, "rewritten-startup-prof.txt") 475 if baseline_profile and startup_profile: 476 baseline_profile_rewritten = _baseline_profiles.get_profile_artifact(ctx, "rewritten-merged-prof.txt") 477 478 outputs = _get_proguard_output( 479 ctx, 480 proguard_output_jar, 481 proguard_seeds, 482 proguard_usage, 483 proguard_output_map, 484 combined_library_jar, 485 startup_profile_rewritten, 486 baseline_profile_rewritten, 487 ) 488 489 # TODO(timpeut): Validate that optimizer target selection is correct 490 mnemonic = ctx.fragments.java.bytecode_optimizer_mnemonic 491 optimizer_target = ctx.executable._bytecode_optimizer 492 493 # If num_passes is not specified run a single optimization action 494 if not num_passes: 495 _optimization_action( 496 ctx, 497 outputs.output_jar, 498 input_jar, 499 filtered_library_jar, 500 proguard_specs, 501 proguard_mapping = proguard_mapping, 502 proguard_output_map = outputs.mapping, 503 proguard_seeds = outputs.seeds, 504 proguard_usage = outputs.usage, 505 proguard_config_output = outputs.config, 506 startup_profile = startup_profile, 507 startup_profile_rewritten = outputs.startup_profile_rewritten, 508 baseline_profile = baseline_profile, 509 baseline_profile_rewritten = outputs.baseline_profile_rewritten, 510 final = True, 511 mnemonic = mnemonic, 512 progress_message = "Trimming binary with %s: %s" % (mnemonic, ctx.label), 513 proguard_tool = proguard_tool, 514 ) 515 return outputs 516 517 # num_passes has been specified, create multiple proguard actions 518 split_bytecode_optimization_passes = ctx.fragments.java.split_bytecode_optimization_pass 519 bytecode_optimization_pass_actions = ctx.fragments.java.bytecode_optimization_pass_actions 520 last_stage_output = _get_proguard_temp_artifact(ctx, "_proguard_preoptimization.jar") 521 _optimization_action( 522 ctx, 523 outputs.output_jar, 524 input_jar, 525 filtered_library_jar, 526 proguard_specs, 527 proguard_mapping = proguard_mapping, 528 proguard_output_map = None, 529 proguard_seeds = outputs.seeds, 530 proguard_usage = None, 531 proguard_config_output = None, 532 startup_profile = startup_profile, 533 startup_profile_rewritten = None, 534 baseline_profile = baseline_profile, 535 baseline_profile_rewritten = None, 536 final = False, 537 runtype = "INITIAL", 538 next_stage_output = last_stage_output, 539 mnemonic = mnemonic, 540 progress_message = "Trimming binary with %s: Verification/Shrinking Pass" % mnemonic, 541 proguard_tool = proguard_tool, 542 ) 543 for i in range(1, num_passes + 1): 544 if split_bytecode_optimization_passes and bytecode_optimization_pass_actions < 2: 545 last_stage_output = _create_single_optimization_action( 546 ctx, 547 outputs.output_jar, 548 input_jar, 549 filtered_library_jar, 550 proguard_specs, 551 proguard_mapping, 552 i, 553 "_INITIAL", 554 mnemonic, 555 last_stage_output, 556 optimizer_target, 557 ) 558 last_stage_output = _create_single_optimization_action( 559 ctx, 560 outputs.output_jar, 561 input_jar, 562 filtered_library_jar, 563 proguard_specs, 564 proguard_mapping, 565 i, 566 "_FINAL", 567 mnemonic, 568 last_stage_output, 569 optimizer_target, 570 ) 571 else: 572 for j in range(1, bytecode_optimization_pass_actions + 1): 573 last_stage_output = _create_single_optimization_action( 574 ctx, 575 outputs.output_jar, 576 input_jar, 577 filtered_library_jar, 578 proguard_specs, 579 proguard_mapping, 580 i, 581 "_ACTION_%s_OF_%s" % (j, bytecode_optimization_pass_actions), 582 mnemonic, 583 last_stage_output, 584 optimizer_target, 585 ) 586 587 _optimization_action( 588 ctx, 589 outputs.output_jar, 590 input_jar, 591 filtered_library_jar, 592 proguard_specs, 593 proguard_mapping = proguard_mapping, 594 proguard_output_map = outputs.mapping, 595 proguard_seeds = None, 596 proguard_usage = outputs.usage, 597 proguard_config_output = outputs.config, 598 startup_profile = None, 599 startup_profile_rewritten = outputs.startup_profile_rewritten, 600 baseline_profile = None, 601 baseline_profile_rewritten = outputs.baseline_profile_rewritten, 602 final = True, 603 runtype = "FINAL", 604 last_stage_output = last_stage_output, 605 mnemonic = mnemonic, 606 progress_message = "Trimming binary with %s: Obfuscation and Final Output Pass" % mnemonic, 607 proguard_tool = proguard_tool, 608 ) 609 return outputs 610 611def _create_single_optimization_action( 612 ctx, 613 output_jar, 614 program_jar, 615 library_jar, 616 proguard_specs, 617 proguard_mapping, 618 optimization_pass_num, 619 runtype_suffix, 620 mnemonic, 621 last_stage_output, 622 proguard_tool): 623 next_stage_output = _get_proguard_temp_artifact(ctx, "_%s_optimization%s_%s.jar" % (mnemonic, runtype_suffix, optimization_pass_num)) 624 _optimization_action( 625 ctx, 626 output_jar, 627 program_jar, 628 library_jar, 629 proguard_specs, 630 proguard_mapping = proguard_mapping, 631 mnemonic = mnemonic, 632 final = False, 633 runtype = "OPTIMIZATION" + runtype_suffix, 634 last_stage_output = last_stage_output, 635 next_stage_output = next_stage_output, 636 progress_message = "Trimming binary with %s: Optimization%s Pass %d" % (mnemonic, runtype_suffix, optimization_pass_num), 637 proguard_tool = proguard_tool, 638 ) 639 return next_stage_output 640 641def _merge_proguard_maps( 642 ctx, 643 output, 644 inputs = [], 645 proguard_maps_merger = None, 646 toolchain_type = None): 647 args = ctx.actions.args() 648 args.add_all(inputs, before_each = "--pg-map") 649 args.add("--pg-map-output", output) 650 651 ctx.actions.run( 652 outputs = [output], 653 executable = proguard_maps_merger, 654 inputs = inputs, 655 arguments = [args], 656 mnemonic = "MergeProguardMaps", 657 progress_message = "Merging app and desugared library Proguard maps for %s" % ctx.label, 658 use_default_shell_env = True, 659 toolchain = toolchain_type, 660 ) 661 662def _fail_action(ctx, *outputs): 663 ctx.actions.run_shell( 664 outputs = [output for output in outputs if output != None], 665 command = "echo \"Unable to run proguard without `proguard_specs`\"; exit 1;", 666 ) 667 668proguard = struct( 669 apply_proguard = _apply_proguard, 670 process_specs = _process_specs, 671 generate_min_sdk_version_assumevalues = _generate_min_sdk_version_assumevalues, 672 get_proguard_output_map = _get_proguard_output_map, 673 get_proguard_specs = _get_proguard_specs, 674 get_proguard_temp_artifact = _get_proguard_temp_artifact, 675 get_proguard_temp_artifact_with_prefix = _get_proguard_temp_artifact_with_prefix, 676 merge_proguard_maps = _merge_proguard_maps, 677) 678 679testing = struct( 680 validate_proguard_spec = _validate_proguard_spec, 681 collect_transitive_proguard_specs = _collect_transitive_proguard_specs, 682 optimization_action = _optimization_action, 683 ProguardSpecContextInfo = _ProguardSpecContextInfo, 684) 685