xref: /aosp_15_r20/external/bazelbuild-rules_android/rules/proguard.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
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