Skip to content
21 changes: 12 additions & 9 deletions rules/android_binary/r8.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_
# The deploy jar from the deploy_jar processor is not used because as of now, whether it
# actually produces a deploy jar is determinted by a separate set of ACLs, and also does
# desugaring differently than with R8.
runtime_jars = depset(
direct = jvm_ctx.java_info.runtime_output_jars + [packaged_resources_ctx.class_jar],
transitive = [jvm_ctx.java_info.transitive_runtime_jars],
)

deploy_jar = ctx.actions.declare_file(ctx.label.name + "_deploy.jar")
java.create_deploy_jar(
ctx,
output = deploy_jar,
runtime_jars = depset(
direct = jvm_ctx.java_info.runtime_output_jars + [packaged_resources_ctx.class_jar],
transitive = [jvm_ctx.java_info.transitive_runtime_jars],
),
runtime_jars = runtime_jars,
java_toolchain = common.get_java_toolchain(ctx),
build_target = ctx.label.name,
deploy_manifest_lines = build_info_ctx.deploy_manifest_lines,
Expand All @@ -84,20 +86,21 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_
proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_config)
desugared_lib_config = ctx.file._desugared_lib_config

# Optionally extract proguard specs embedded in the deploy JAR (META-INF/proguard/
# and META-INF/com.android.tools/) so they are passed to R8.
# Optionally extract proguard specs embedded in runtime JARs (META-INF/proguard/
# and META-INF/com.android.tools/) so they are passed to R8. Each JAR is processed
# independently to avoid META-INF clobbering from the deploy JAR merge.
if _flags.get(ctx).r8_extract_embedded_proguard_specs:
jar_embedded_proguard = ctx.actions.declare_file(ctx.label.name + "_jar_embedded_proguard.pro")
jar_extractor_args = ctx.actions.args()
jar_extractor_args.add("--input_jar", deploy_jar)
jar_extractor_args.add_all("--input_jars", runtime_jars)
jar_extractor_args.add("--output_proguard_file", jar_embedded_proguard)
ctx.actions.run(
executable = get_android_toolchain(ctx).jar_embedded_proguard_extractor.files_to_run,
arguments = [jar_extractor_args],
inputs = [deploy_jar],
inputs = runtime_jars,
outputs = [jar_embedded_proguard],
mnemonic = "JarEmbeddedProguardExtractor",
progress_message = "Extracting proguard specs from deploy jar for %{label}",
progress_message = "Extracting proguard specs from runtime jars for %{label}",
toolchain = None,
)
proguard_specs = proguard_specs + [jar_embedded_proguard]
Expand Down
20 changes: 20 additions & 0 deletions tools/android/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ java_binary(
runtime_deps = ["@rules_android_maven//:com_android_tools_r8"],
)

java_binary(
name = "r8_version_bin",
srcs = ["R8Version.java"],
main_class = "tools.android.R8Version",
deps = ["@rules_android_maven//:com_android_tools_r8"],
)

java_binary(
name = "tracereferences",
jvm_flags = _JVM_FLAGS,
Expand Down Expand Up @@ -435,23 +442,27 @@ py_library(
py_binary(
name = "aar_embedded_proguard_extractor",
srcs = ["aar_embedded_proguard_extractor.py"],
data = [":r8_version"],
visibility = ["//visibility:public"],
deps = [
":json_worker_wrapper",
":junction_lib",
":proguard_extractor_lib",
"@bazel_tools//tools/python/runfiles",
"@py_absl//absl:app",
],
)

py_binary(
name = "jar_embedded_proguard_extractor",
srcs = ["jar_embedded_proguard_extractor.py"],
data = [":r8_version"],
visibility = ["//visibility:public"],
deps = [
":json_worker_wrapper",
":junction_lib",
":proguard_extractor_lib",
"@bazel_tools//tools/python/runfiles",
"@py_absl//absl:app",
],
)
Expand Down Expand Up @@ -660,3 +671,12 @@ genrule(
""",
visibility = ["//visibility:public"],
)

genrule(
name = "r8_version",
outs = [
"r8.version",
],
cmd = "$(location :r8_version_bin) >$(OUTS)",
tools = [":r8_version_bin"],
)
7 changes: 7 additions & 0 deletions tools/android/R8Version.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tools.android;

public class R8Version {
public static void main(String[] args) {
System.out.println(com.android.tools.r8.Version.LABEL);
}
}
26 changes: 13 additions & 13 deletions tools/android/aar_embedded_proguard_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from __future__ import division
from __future__ import print_function

from bazel_tools.tools.python.runfiles import runfiles

import os
import zipfile

Expand All @@ -41,22 +43,20 @@
)


# Attempt to extract proguard spec from AAR. If the file doesn't exist, an empty
# proguard spec file will be created
def ExtractEmbeddedProguard(aar, output, extract_r8_rules=False):
if extract_r8_rules:
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, output)
else:
proguard_extractor_lib.ExtractEmbeddedProguardFromAarLegacy(aar, output)


def _Main(input_aar, output_proguard_file, extract_r8_rules):
def _Main(input_aar, output_proguard_file, r8_version = None):
with zipfile.ZipFile(input_aar, "r") as aar:
with open(output_proguard_file, "wb") as output:
ExtractEmbeddedProguard(aar, output, extract_r8_rules)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, output, r8_version)


def main(unused_argv):
r = runfiles.Create()
r8_version = None
with open(r.Rlocation("rules_android/tools/android/r8.version"), "r") as file:
runfile_lines = file.readlines()
if runfile_lines:
r8_version = runfile_lines[0].strip()

if os.name == "nt":
# Shorten paths unconditionally, because the extracted paths in
# ExtractEmbeddedJars (which we cannot yet predict, because they depend on
Expand All @@ -70,10 +70,10 @@ def main(unused_argv):
_Main(
os.path.join(aar_junc, os.path.basename(aar_long)),
os.path.join(proguard_junc, os.path.basename(proguard_long)),
FLAGS.extract_r8_rules,
r8_version
)
else:
_Main(FLAGS.input_aar, FLAGS.output_proguard_file, FLAGS.extract_r8_rules)
_Main(FLAGS.input_aar, FLAGS.output_proguard_file, r8_version)


if __name__ == "__main__":
Expand Down
121 changes: 42 additions & 79 deletions tools/android/aar_embedded_proguard_extractor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,59 +18,14 @@
import unittest
import zipfile

from tools.android import aar_embedded_proguard_extractor
from tools.android import proguard_extractor_lib


class AarEmbeddedProguardExtractorLegacyTest(unittest.TestCase):
"""Unit tests for AAR proguard extraction.

Legacy behavior, i.e. extract_r8_rules=False.
"""
class AarEmbeddedProguardExtractorTest(unittest.TestCase):
"""Unit tests for AAR proguard extraction."""

def setUp(self):
super(AarEmbeddedProguardExtractorLegacyTest, self).setUp()
os.chdir(os.environ["TEST_TMPDIR"])

def testNoProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
proguard_file = io.BytesIO()
aar_embedded_proguard_extractor.ExtractEmbeddedProguard(aar, proguard_file)
proguard_file.seek(0)
self.assertEqual(b"", proguard_file.read())

def testWithProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("proguard.txt", "hello world")
proguard_file = io.BytesIO()
aar_embedded_proguard_extractor.ExtractEmbeddedProguard(aar, proguard_file)
proguard_file.seek(0)
self.assertEqual(b"hello world", proguard_file.read())

def make_classes_jar(self, entries):
jar_buf = io.BytesIO()
with zipfile.ZipFile(jar_buf, "w") as jar:
for path, content in entries.items():
jar.writestr(path, content)
return jar_buf.getvalue()

def testR8RulesFromClassesJarIgnoredByDefault(self):
classes_jar = self.make_classes_jar({
"META-INF/com.android.tools/r8/rules.pro": "-keep class A",
})
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("classes.jar", classes_jar)
proguard_file = io.BytesIO()
aar_embedded_proguard_extractor.ExtractEmbeddedProguard(aar, proguard_file)
proguard_file.seek(0)
self.assertEqual(b"", proguard_file.read())


class AarEmbeddedProguardExtractorWithR8RulesTest(unittest.TestCase):
"""Unit tests for AAR proguard extraction with extract_r8_rules=True."""

def setUp(self):
super(AarEmbeddedProguardExtractorWithR8RulesTest, self).setUp()
super(AarEmbeddedProguardExtractorTest, self).setUp()
os.chdir(os.environ["TEST_TMPDIR"])

def make_classes_jar(self, entries):
Expand All @@ -83,82 +38,90 @@ def make_classes_jar(self, entries):
def testNoProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"", proguard_file.read())

def testWithProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("proguard.txt", "hello world")
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"hello world", proguard_file.read())

def testR8RulesFromClassesJar(self):
def testTargetedR8RulesFromClassesJar(self):
classes_jar = self.make_classes_jar({
"META-INF/com.android.tools/r8/rules.pro": "-keep class A",
"META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro": "-keep class A",
})
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("classes.jar", classes_jar)
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"\n-keep class A", proguard_file.read())

def testR8RulesFromVersionedSubdirs(self):
def testTargetedR8RulesPreferredOverProguardTxt(self):
classes_jar = self.make_classes_jar({
"META-INF/com.android.tools/r8-from-8.0.0/rules.pro": "-keep class B",
"META-INF/com.android.tools/r8-upto-8.0.0/rules.pro": "-keep class C",
"META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro": "-keep class targeted",
})
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("proguard.txt", "-keep class legacy")
aar.writestr("classes.jar", classes_jar)
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"\n-keep class B\n-keep class C", proguard_file.read())
self.assertEqual(b"\n-keep class targeted", proguard_file.read())

def testR8RulesAndProguardTxtCombined(self):
def testFallsBackToProguardTxtWhenNoVersionMatch(self):
classes_jar = self.make_classes_jar({
"META-INF/com.android.tools/r8/rules.pro": "-keep class D",
"META-INF/com.android.tools/r8-from-1.0.0-upto-2.0.0/rules.pro": "-keep class old",
})
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("proguard.txt", "-keep class E")
aar.writestr("proguard.txt", "-keep class legacy")
aar.writestr("classes.jar", classes_jar)
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"-keep class E\n-keep class D", proguard_file.read())
self.assertEqual(b"-keep class legacy", proguard_file.read())

def testR8RulesIgnoresDirectoryEntries(self):
classes_jar = self.make_classes_jar({
"META-INF/com.android.tools/": "",
"META-INF/com.android.tools/r8/": "",
"META-INF/com.android.tools/r8/rules.pro": "-keep class F",
})
def testNoClassesJarFallsBackToProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("classes.jar", classes_jar)
aar.writestr("proguard.txt", "-keep class legacy")
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"\n-keep class F", proguard_file.read())
self.assertEqual(b"-keep class legacy", proguard_file.read())

def testNoClassesJarNoR8Rules(self):
def testClassesJarWithoutR8Rules(self):
classes_jar = self.make_classes_jar({
"com/example/Foo.class": "classdata",
})
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("some_other_file.txt", "data")
aar.writestr("proguard.txt", "-keep class legacy")
aar.writestr("classes.jar", classes_jar)
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"", proguard_file.read())
self.assertEqual(b"-keep class legacy", proguard_file.read())

def testClassesJarWithoutR8Rules(self):
def testNoneR8VersionFallsBackToProguardTxt(self):
classes_jar = self.make_classes_jar({
"com/example/Foo.class": "classdata",
"META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro": "-keep class targeted",
})
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("proguard.txt", "-keep class legacy")
aar.writestr("classes.jar", classes_jar)
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, None)
proguard_file.seek(0)
self.assertEqual(b"-keep class legacy", proguard_file.read())

def testNoClassesJarNoProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
aar.writestr("some_other_file.txt", "data")
proguard_file = io.BytesIO()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file, "8.9.35")
proguard_file.seek(0)
self.assertEqual(b"", proguard_file.read())

Expand Down
Loading