Implement R8's config analyzer as an output group option.

https://github.com/bazelbuild/rules_android/issues/542

PiperOrigin-RevId: 976008760
Change-Id: Ifaea5ec11e444492d69f325d7b3d025529a7a112
diff --git a/providers/providers.bzl b/providers/providers.bzl
index 45f88b7..e62b3ca 100644
--- a/providers/providers.bzl
+++ b/providers/providers.bzl
@@ -224,6 +224,7 @@
     fields = dict(
         d8_optimization_info = "Metadata produced by D8 about the applied optimizations in a JSON format.",
         r8_optimization_info = "Metadata produced by R8 about the applied optimizations in a JSON format.",
+        optimization_analysis_report = "HTML report produced by the optimization configuration analyzer.",
     ),
 )
 
diff --git a/rules/android_application/android_application_rule.bzl b/rules/android_application/android_application_rule.bzl
index eda37ed..76fc8b8 100644
--- a/rules/android_application/android_application_rule.bzl
+++ b/rules/android_application/android_application_rule.bzl
@@ -375,7 +375,7 @@
         is_executable = True,
     )
 
-    return [
+    providers = [
         ctx.attr.base_module[ApkInfo],
         ctx.attr.base_module[AndroidPreDexJarInfo],
         AndroidBundleInfo(unsigned_aab = ctx.outputs.unsigned_aab),
@@ -388,6 +388,12 @@
         ),
     ]
 
+    # Pass through AndroidOptimizationInfo from base module, if optimization analysis was performed.
+    if AndroidOptimizationInfo in ctx.attr.base_module:
+        providers.append(ctx.attr.base_module[AndroidOptimizationInfo])
+
+    return providers
+
 android_application = rule(
     attrs = ANDROID_APPLICATION_ATTRS,
     cfg = android_platforms_transition,
diff --git a/rules/android_binary/impl.bzl b/rules/android_binary/impl.bzl
index ab79f58..841c8ed 100644
--- a/rules/android_binary/impl.bzl
+++ b/rules/android_binary/impl.bzl
@@ -401,8 +401,13 @@
     )
     providers.append(AndroidPreDexJarInfo(pre_dex_jar = binary_jar))
 
-    if build_metadata_output != None:
-        providers.append(AndroidOptimizationInfo(d8_optimization_info = build_metadata_output))
+    if build_metadata_output != None or optimize_ctx.html_report != None:
+        providers.append(
+            AndroidOptimizationInfo(
+                d8_optimization_info = build_metadata_output,
+                optimization_analysis_report = optimize_ctx.html_report,
+            ),
+        )
 
     if postprocessing_output_map:
         providers.append(ProguardMappingInfo(proguard_mapping = postprocessing_output_map))
@@ -719,7 +724,10 @@
         return ProviderInfo(
             name = "optimize_ctx",
             value = struct(
-                proguard_output = proguard.create_empty_proguard_output(ctx, proguard_output_jar),
+                proguard_output = proguard.create_empty_proguard_output(
+                    ctx,
+                    proguard_output_jar = proguard_output_jar,
+                ),
                 resources_apk = None,
                 providers = [],
             ),
@@ -776,7 +784,7 @@
     else:
         proguard_output_jar = ctx.actions.declare_file(ctx.label.name + "_proguard.jar")
         proguard_output_config = ctx.actions.declare_file(ctx.label.name + "_proguard.config")
-    proguard_seeds = ctx.actions.declare_file(ctx.label.name + "_proguard.seeds")
+    keep_radius = ctx.actions.declare_file(ctx.label.name + "_keep_radius.pb")
     proguard_usage = ctx.actions.declare_file(ctx.label.name + "_proguard.usage")
     proguard_why_keeping_file = ctx.actions.declare_file(ctx.label.name + "_proguard.why_keeping")
 
@@ -815,7 +823,7 @@
         proguard_output_config = proguard_output_config,
         proguard_mapping = ctx.file.proguard_apply_mapping,
         proguard_output_map = proguard_output_map,
-        proguard_seeds = proguard_seeds,
+        keep_radius = keep_radius,
         proguard_usage = proguard_usage,
         proguard_why_keeping_file = proguard_why_keeping_file,
         startup_profile = startup_profile,
@@ -830,7 +838,7 @@
             [
                 proguard_output.output_jar,
                 proguard_output.config,
-                proguard_output.seeds,
+                proguard_output.keep_radius,
                 proguard_output.usage,
                 proguard_output.why_keeping_file,
             ],
@@ -881,6 +889,18 @@
     if not optimized_resources_apk and enable_resource_shrinking:
         optimized_resources_apk = shrunk_resource_output.resources_apk
 
+    report_generator = getattr(get_android_toolchain(ctx), "optimization_config_analyzer_generator", None)
+    html_report = None
+    if proguard_output.keep_radius and report_generator:
+        html_report = ctx.actions.declare_file(ctx.label.name + "_optimization_report.html")
+        proguard.generate_report(
+            ctx,
+            out = html_report,
+            keep_radius = proguard_output.keep_radius,
+            generator_tool = report_generator.files_to_run,
+            toolchain_type = ANDROID_TOOLCHAIN_TYPE,
+        )
+
     return ProviderInfo(
         name = "optimize_ctx",
         value = struct(
@@ -888,6 +908,10 @@
             resources_apk = optimized_resources_apk,
             providers = [],
             implicit_outputs = implicit_outputs,
+            html_report = html_report,
+            output_groups = {
+                "optimization_config_analyzer": depset([html_report]) if html_report else depset(),
+            },
         ),
     )
 
diff --git a/rules/android_binary/r8.bzl b/rules/android_binary/r8.bzl
index a5814a2..f2d7193 100644
--- a/rules/android_binary/r8.bzl
+++ b/rules/android_binary/r8.bzl
@@ -80,6 +80,7 @@
     dexes_zip = ctx.actions.declare_file(ctx.label.name + "_dexes.zip")
     proguard_mappings_output_file = ctx.actions.declare_file(ctx.label.name + "_proguard.map")
     build_metadata_output = ctx.actions.declare_file(ctx.label.name + "_r8_optimization_info.json")
+    keep_radius = ctx.actions.declare_file(ctx.label.name + "_keep_radius.pb")
 
     android_jar = get_android_sdk(ctx).android_jar
     proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_config)
@@ -141,9 +142,17 @@
         executable = get_android_toolchain(ctx).r8.files_to_run,
         arguments = [args],
         inputs = depset(r8_inputs, transitive = [neverlink_jars]),
-        outputs = [dexes_zip, proguard_mappings_output_file, build_metadata_output],
+        outputs = [
+            dexes_zip,
+            proguard_mappings_output_file,
+            build_metadata_output,
+            keep_radius,
+        ],
         mnemonic = "AndroidR8",
-        jvm_flags = ["-Xmx8G"],
+        jvm_flags = [
+            "-Xmx8G",
+            "-Dcom.android.tools.r8.dumpkeepradiustofile=" + keep_radius.path,
+        ],
         progress_message = "R8 Optimizing, Desugaring, and Dexing %{label}",
     )
 
@@ -183,16 +192,35 @@
         java_resource_jar = java_resource_jar,
     )
 
+    report_generator = getattr(get_android_toolchain(ctx), "optimization_config_analyzer_generator", None)
+    html_report = None
+    if report_generator:
+        html_report = ctx.actions.declare_file(ctx.label.name + "_optimization_report.html")
+        proguard.generate_report(
+            ctx,
+            out = html_report,
+            keep_radius = keep_radius,
+            generator_tool = report_generator.files_to_run,
+            toolchain_type = ANDROID_TOOLCHAIN_TYPE,
+        )
+
     return ProviderInfo(
         name = "r8_ctx",
         value = struct(
             final_classes_dex_zip = final_classes_dex_zip,
             dex_info = android_dex_info,
             implicit_outputs = [proguard_mappings_output_file],
+            html_report = html_report,
+            output_groups = {
+                "optimization_config_analyzer": depset([html_report]) if html_report else depset(),
+            },
             providers = [
                 android_dex_info,
                 AndroidPreDexJarInfo(pre_dex_jar = deploy_jar),
-                AndroidOptimizationInfo(r8_optimization_info = build_metadata_output),
+                AndroidOptimizationInfo(
+                    r8_optimization_info = build_metadata_output,
+                    optimization_analysis_report = html_report,
+                ),
             ],
         ),
     )
diff --git a/rules/proguard.bzl b/rules/proguard.bzl
index 08146a3..774901c 100644
--- a/rules/proguard.bzl
+++ b/rules/proguard.bzl
@@ -180,7 +180,7 @@
         proguard_specs,
         proguard_mapping = None,
         proguard_output_map = None,
-        proguard_seeds = None,
+        keep_radius = None,
         proguard_usage = None,
         proguard_why_keeping_file = None,
         proguard_config_output = None,
@@ -219,8 +219,8 @@
         generated by proguard_generate_mapping to be re-used to apply the same map to a new build.
       proguard_output_map: File. Optional file to be used to write the output map of obfuscated
         class and member names.
-      proguard_seeds: File. Optional file used to write the "seeds", which is a list of all
-        classes and members which match a keep rule.
+      keep_radius: File. Optional file used to write the "keep radius" proto, which is a mapping of
+        keep rules to their kept classes and members.
       proguard_usage: File. Optional file used to write all classes and members that are removed
         during shrinking (i.e. unused code).
       proguard_why_keeping_file: File. Optional file used to write whyareyoukeeping relationship graph.
@@ -276,9 +276,9 @@
         args.add("-printmapping", proguard_output_map)
         outputs.append(proguard_output_map)
 
-    if proguard_seeds:
-        args.add("-printseeds", proguard_seeds)
-        outputs.append(proguard_seeds)
+    if keep_radius:
+        args.add("-printseeds", keep_radius)
+        outputs.append(keep_radius)
 
     if proguard_usage:
         args.add("-printusage", proguard_usage)
@@ -356,7 +356,7 @@
         proguard_output_jar,
         proguard_output_config = None,
         proguard_output_map = None,
-        proguard_seeds = None,
+        keep_radius = None,
         proguard_usage = None,
         proguard_why_keeping_file = None):
     """ Creates empty proguard outputs.
@@ -370,7 +370,7 @@
       proguard_output_jar: File. The output optimized jar.
       proguard_output_config: File. The output proguard config.
       proguard_output_map: File. The output proguard map.
-      proguard_seeds: File. The output proguard seeds.
+      keep_radius: File. The output keep radius proto.
       proguard_usage: File. The output proguard usage.
       proguard_why_keeping_file: File. Optional file used to write whyareyoukeeping relationship graph.
 
@@ -382,7 +382,7 @@
         ctx,
         proguard_output_jar = proguard_output_jar,
         proguard_output_config = proguard_output_config,
-        proguard_seeds = proguard_seeds,
+        keep_radius = keep_radius,
         proguard_usage = proguard_usage,
         proguard_why_keeping_file = proguard_why_keeping_file,
         proguard_output_map = proguard_output_map,
@@ -400,7 +400,7 @@
         outputs.output_jar,
         outputs.mapping,
         outputs.config,
-        outputs.seeds,
+        outputs.keep_radius,
         outputs.usage,
         outputs.why_keeping_file,
     )
@@ -415,7 +415,7 @@
         proguard_output_jar = None,
         proguard_output_config = None,
         proguard_output_map = None,
-        proguard_seeds = None,
+        keep_radius = None,
         proguard_usage = None,
         proguard_why_keeping_file = None,
         startup_profile = None,
@@ -434,7 +434,7 @@
       proguard_output_jar: File. The output optimized jar.
       proguard_output_config: File. The output proguard config.
       proguard_output_map: File. The output proguard map.
-      proguard_seeds: File. The output proguard seeds.
+      keep_radius: File. The output keep radius proto.
       proguard_usage: File. The output proguard usage.
       proguard_why_keeping_file: File. Optional file used to write whyareyoukeeping relationship graph.
       startup_profile: File. The input merged startup profile to be optimized.
@@ -452,7 +452,7 @@
             proguard_output_jar,
             proguard_output_config,
             proguard_output_map,
-            proguard_seeds,
+            keep_radius,
             proguard_usage,
             proguard_why_keeping_file,
         )
@@ -468,7 +468,7 @@
     return _create_optimization_actions(
         ctx,
         proguard_specs,
-        proguard_seeds,
+        keep_radius,
         proguard_usage,
         proguard_why_keeping_file,
         proguard_mapping,
@@ -490,7 +490,7 @@
         ctx,
         proguard_output_jar,
         proguard_output_config,
-        proguard_seeds,
+        keep_radius,
         proguard_usage,
         proguard_why_keeping_file,
         proguard_output_map,
@@ -510,7 +510,7 @@
         output_jar = proguard_output_jar,
         mapping = proguard_output_map,
         proto_mapping = proguard_output_proto_map,
-        seeds = proguard_seeds,
+        keep_radius = keep_radius,
         usage = proguard_usage,
         why_keeping_file = proguard_why_keeping_file,
         library_jar = combined_library_jar,
@@ -523,7 +523,7 @@
 def _create_optimization_actions(
         ctx,
         proguard_specs = None,
-        proguard_seeds = None,
+        keep_radius = None,
         proguard_usage = None,
         proguard_why_keeping_file = None,
         proguard_mapping = None,
@@ -575,7 +575,7 @@
         ctx,
         proguard_output_jar,
         proguard_output_config,
-        proguard_seeds,
+        keep_radius,
         proguard_usage,
         proguard_why_keeping_file,
         proguard_output_map,
@@ -599,7 +599,7 @@
             proguard_specs,
             proguard_mapping = proguard_mapping,
             proguard_output_map = outputs.mapping,
-            proguard_seeds = outputs.seeds,
+            keep_radius = outputs.keep_radius,
             proguard_usage = outputs.usage,
             proguard_why_keeping_file = outputs.why_keeping_file,
             proguard_config_output = outputs.config,
@@ -630,7 +630,7 @@
         proguard_specs,
         proguard_mapping = proguard_mapping,
         proguard_output_map = None,
-        proguard_seeds = outputs.seeds,
+        keep_radius = outputs.keep_radius,
         proguard_usage = None,
         proguard_why_keeping_file = None,
         proguard_config_output = None,
@@ -703,7 +703,7 @@
         proguard_specs,
         proguard_mapping = proguard_mapping,
         proguard_output_map = outputs.mapping,
-        proguard_seeds = None,
+        keep_radius = None,
         proguard_usage = outputs.usage,
         proguard_why_keeping_file = outputs.why_keeping_file,
         proguard_config_output = outputs.config,
@@ -784,9 +784,30 @@
         command = "echo \"Unable to run proguard without \\`proguard_specs\\`\"; exit 1;",
     )
 
+def _generate_report(
+        ctx,
+        out,
+        keep_radius,
+        generator_tool,
+        toolchain_type = None):
+    args = ctx.actions.args()
+    args.add(keep_radius)
+    args.add(out)
+
+    ctx.actions.run(
+        outputs = [out],
+        inputs = [keep_radius],
+        executable = generator_tool,
+        arguments = [args],
+        mnemonic = "GenerateOptimizationReport",
+        progress_message = "Generating optimization report for %s" % ctx.label,
+        toolchain = toolchain_type,
+    )
+
 proguard = struct(
     apply_proguard = _apply_proguard,
     create_empty_proguard_output = _create_empty_proguard_output,
+    generate_report = _generate_report,
     process_specs = _process_specs,
     generate_min_sdk_version_assumevalues = _generate_min_sdk_version_assumevalues,
     get_proguard_specs = _get_proguard_specs,
diff --git a/test/rules/android_binary/r8_integration/BUILD b/test/rules/android_binary/r8_integration/BUILD
index 7e444a5..63a2a72 100644
--- a/test/rules/android_binary/r8_integration/BUILD
+++ b/test/rules/android_binary/r8_integration/BUILD
@@ -66,3 +66,14 @@
         "@rules_shell//shell/runfiles",
     ],
 )
+
+filegroup(
+    name = "optimization_config_analyzer_report",
+    srcs = ["//test/rules/android_binary/r8_integration/java/com/basicapp:basic_app_R8_shrink"],
+    output_group = "optimization_config_analyzer",
+)
+
+build_test(
+    name = "optimization_config_analyzer_report_test",
+    targets = [":optimization_config_analyzer_report"],
+)
diff --git a/toolchains/android/toolchain.bzl b/toolchains/android/toolchain.bzl
index ce8b61c..bf06e5b 100644
--- a/toolchains/android/toolchain.bzl
+++ b/toolchains/android/toolchain.bzl
@@ -215,6 +215,12 @@
         default = "@bazel_tools//tools/jdk:proguard_whitelister",
         executable = True,
     ),
+    optimization_config_analyzer_generator = attr.label(
+        cfg = "exec",
+        default = "//tools/android:keep_radius_html_report_generator",
+        executable = True,
+        allow_files = True,
+    ),
     profgen = attr.label(
         default = "@androidsdk//:fail",
         cfg = "exec",
diff --git a/tools/android/BUILD b/tools/android/BUILD
index 4aa929d..467ba3e 100644
--- a/tools/android/BUILD
+++ b/tools/android/BUILD
@@ -284,6 +284,14 @@
 )
 
 java_binary(
+    name = "keep_radius_html_report_generator",
+    jvm_flags = _JVM_FLAGS,
+    main_class = "com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator",
+    visibility = ["//visibility:public"],
+    runtime_deps = ["@rules_android_maven//:com_android_tools_r8"],
+)
+
+java_binary(
     name = "resource_shrinker",
     main_class = "com.android.build.shrinker.ResourceShrinkerCli",
     visibility = ["//visibility:public"],