diff --git a/rules/android_binary/r8.bzl b/rules/android_binary/r8.bzl index 2516c881c..6c04f6ea6 100644 --- a/rules/android_binary/r8.bzl +++ b/rules/android_binary/r8.bzl @@ -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, @@ -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] diff --git a/tools/android/BUILD b/tools/android/BUILD index b1b76ecf1..22b12576c 100644 --- a/tools/android/BUILD +++ b/tools/android/BUILD @@ -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, @@ -435,11 +442,13 @@ 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", ], ) @@ -447,11 +456,13 @@ py_binary( 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", ], ) @@ -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"], +) diff --git a/tools/android/R8Version.java b/tools/android/R8Version.java new file mode 100644 index 000000000..a5d8d6464 --- /dev/null +++ b/tools/android/R8Version.java @@ -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); + } +} diff --git a/tools/android/aar_embedded_proguard_extractor.py b/tools/android/aar_embedded_proguard_extractor.py index e9149f735..013d01f12 100644 --- a/tools/android/aar_embedded_proguard_extractor.py +++ b/tools/android/aar_embedded_proguard_extractor.py @@ -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 @@ -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 @@ -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__": diff --git a/tools/android/aar_embedded_proguard_extractor_test.py b/tools/android/aar_embedded_proguard_extractor_test.py index 440f872e2..b39d29e1a 100644 --- a/tools/android/aar_embedded_proguard_extractor_test.py +++ b/tools/android/aar_embedded_proguard_extractor_test.py @@ -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): @@ -83,7 +38,7 @@ 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()) @@ -91,74 +46,82 @@ 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()) diff --git a/tools/android/jar_embedded_proguard_extractor.py b/tools/android/jar_embedded_proguard_extractor.py index bdd93a116..28ace378c 100644 --- a/tools/android/jar_embedded_proguard_extractor.py +++ b/tools/android/jar_embedded_proguard_extractor.py @@ -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 @@ -31,40 +33,40 @@ FLAGS = flags.FLAGS -flags.DEFINE_string("input_jar", None, "Input JAR") -flags.mark_flag_as_required("input_jar") +flags.DEFINE_multi_string("input_jars", None, "Input JAR(s)") +flags.mark_flag_as_required("input_jars") flags.DEFINE_string( "output_proguard_file", None, "Output parameter file for proguard" ) flags.mark_flag_as_required("output_proguard_file") -def ExtractEmbeddedProguard(jar, output): - """Extract proguard specs from a JAR file.""" - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, output) - - -def _Main(input_jar, output_proguard_file): - with zipfile.ZipFile(input_jar, "r") as jar: - with open(output_proguard_file, "wb") as output: - ExtractEmbeddedProguard(jar, output) +def _Main(input_jars, output_proguard_file, r8_version = None): + with open(output_proguard_file, "wb") as output: + for input_jar in input_jars: + with zipfile.ZipFile(input_jar, "r") as jar: + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, 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": - jar_long = os.path.abspath(FLAGS.input_jar) proguard_long = os.path.abspath(FLAGS.output_proguard_file) - with junction.TempJunction(os.path.dirname(jar_long)) as jar_junc: - with junction.TempJunction( - os.path.dirname(proguard_long) - ) as proguard_junc: - _Main( - os.path.join(jar_junc, os.path.basename(jar_long)), - os.path.join(proguard_junc, os.path.basename(proguard_long)), - ) + with junction.TempJunction(os.path.dirname(proguard_long)) as proguard_junc: + _Main( + [os.path.abspath(j) for j in FLAGS.input_jars], + os.path.join(proguard_junc, os.path.basename(proguard_long)), + r8_version, + ) else: - _Main(FLAGS.input_jar, FLAGS.output_proguard_file) + _Main(FLAGS.input_jars, FLAGS.output_proguard_file, r8_version) if __name__ == "__main__": diff --git a/tools/android/jar_embedded_proguard_extractor_test.py b/tools/android/jar_embedded_proguard_extractor_test.py index 506a5b6c6..c84406d9a 100644 --- a/tools/android/jar_embedded_proguard_extractor_test.py +++ b/tools/android/jar_embedded_proguard_extractor_test.py @@ -31,7 +31,7 @@ def setUp(self): def testNoProguardSpecs(self): jar = zipfile.ZipFile(io.BytesIO(), "w") proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) self.assertEqual(b"", proguard_file.read()) @@ -39,7 +39,7 @@ def testLegacyMetaInfProguard(self): jar = zipfile.ZipFile(io.BytesIO(), "w") jar.writestr("META-INF/proguard/rules.pro", "-keep class A") proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) self.assertEqual(b"\n-keep class A", proguard_file.read()) @@ -48,48 +48,89 @@ def testMultipleLegacyFiles(self): jar.writestr("META-INF/proguard/rules1.pro", "-keep class A") jar.writestr("META-INF/proguard/rules2.pro", "-keep class B") proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) self.assertEqual(b"\n-keep class A\n-keep class B", proguard_file.read()) - def testR8Rules(self): + def testTargetedR8RulesMatchingVersion(self): jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr("META-INF/com.android.tools/r8/rules.pro", "-keep class C") + jar.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class C", + ) proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) self.assertEqual(b"\n-keep class C", proguard_file.read()) - def testR8RulesVersionedSubdirs(self): + def testTargetedR8RulesNotMatchingVersion(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr( + "META-INF/com.android.tools/r8-from-1.0.0-upto-2.0.0/rules.pro", + "-keep class C", + ) + jar.writestr("META-INF/proguard/rules.pro", "-keep class legacy") + proguard_file = io.BytesIO() + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") + proguard_file.seek(0) + self.assertEqual(b"\n-keep class legacy", proguard_file.read()) + + def testTargetedR8RulesPreferredOverLegacy(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class targeted", + ) + jar.writestr("META-INF/proguard/rules.pro", "-keep class legacy") + proguard_file = io.BytesIO() + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") + proguard_file.seek(0) + self.assertEqual(b"\n-keep class targeted", proguard_file.read()) + + def testVersionAtLowerBoundInclusive(self): jar = zipfile.ZipFile(io.BytesIO(), "w") jar.writestr( - "META-INF/com.android.tools/r8-from-8.0.0/rules.pro", "-keep class D" + "META-INF/com.android.tools/r8-from-8.9.35-upto-9.0.0/rules.pro", + "-keep class exact", ) + proguard_file = io.BytesIO() + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") + proguard_file.seek(0) + self.assertEqual(b"\n-keep class exact", proguard_file.read()) + + def testVersionAtUpperBoundExclusive(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") jar.writestr( - "META-INF/com.android.tools/r8-upto-8.0.0/rules.pro", "-keep class E" + "META-INF/com.android.tools/r8-from-8.0.0-upto-8.9.35/rules.pro", + "-keep class excluded", ) + jar.writestr("META-INF/proguard/rules.pro", "-keep class legacy") proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) - self.assertEqual(b"\n-keep class D\n-keep class E", proguard_file.read()) + self.assertEqual(b"\n-keep class legacy", proguard_file.read()) - def testLegacyAndR8RulesCombined(self): + def testMultipleVersionedDirsOnlyMatchingIncluded(self): jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr("META-INF/proguard/rules.pro", "-keep class F") - jar.writestr("META-INF/com.android.tools/r8/rules.pro", "-keep class G") + jar.writestr( + "META-INF/com.android.tools/r8-from-1.0.0-upto-2.0.0/rules.pro", + "-keep class old", + ) + jar.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class current", + ) proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) - self.assertEqual(b"\n-keep class G\n-keep class F", proguard_file.read()) + self.assertEqual(b"\n-keep class current", proguard_file.read()) def testIgnoresDirectoryEntries(self): jar = zipfile.ZipFile(io.BytesIO(), "w") jar.writestr("META-INF/proguard/", "") - jar.writestr("META-INF/com.android.tools/", "") - jar.writestr("META-INF/com.android.tools/r8/", "") - jar.writestr("META-INF/com.android.tools/r8/rules.pro", "-keep class H") + jar.writestr("META-INF/proguard/rules.pro", "-keep class H") proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) self.assertEqual(b"\n-keep class H", proguard_file.read()) @@ -99,10 +140,58 @@ def testIgnoresUnrelatedMetaInf(self): jar.writestr("META-INF/services/com.example.Spi", "com.example.SpiImpl") jar.writestr("com/example/Foo.class", "classdata") proguard_file = io.BytesIO() - proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") proguard_file.seek(0) self.assertEqual(b"", proguard_file.read()) + def testNoneR8VersionFallsBackToLegacy(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class targeted", + ) + jar.writestr("META-INF/proguard/rules.pro", "-keep class legacy") + proguard_file = io.BytesIO() + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, None) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class legacy", proguard_file.read()) + + def testMultipleJarsExtractedIndependently(self): + jar1 = zipfile.ZipFile(io.BytesIO(), "w") + jar1.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class A", + ) + + jar2 = zipfile.ZipFile(io.BytesIO(), "w") + jar2.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class B", + ) + + proguard_file = io.BytesIO() + for jar in [jar1, jar2]: + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") + proguard_file.seek(0) + self.assertEqual(b"\n-keep class A\n-keep class B", proguard_file.read()) + + def testMultipleJarsIndependentFallback(self): + jar1 = zipfile.ZipFile(io.BytesIO(), "w") + jar1.writestr( + "META-INF/com.android.tools/r8-from-8.0.0-upto-9.0.0/rules.pro", + "-keep class targeted", + ) + + jar2 = zipfile.ZipFile(io.BytesIO(), "w") + jar2.writestr("META-INF/proguard/rules.pro", "-keep class legacy") + + proguard_file = io.BytesIO() + for jar in [jar1, jar2]: + proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, proguard_file, "8.9.35") + proguard_file.seek(0) + self.assertEqual(b"\n-keep class targeted\n-keep class legacy", proguard_file.read()) + + if __name__ == "__main__": unittest.main() diff --git a/tools/android/proguard_extractor_lib.py b/tools/android/proguard_extractor_lib.py index ac24562b1..a6ae1e836 100644 --- a/tools/android/proguard_extractor_lib.py +++ b/tools/android/proguard_extractor_lib.py @@ -19,48 +19,58 @@ from __future__ import print_function import io +import re import zipfile -def ExtractR8Rules(jar, output): - """Extract R8 rules from META-INF/com.android.tools/ inside a JAR. +def _parse_version(ver:str): + return tuple(int(x) for x in ver.split(".")) - Handles subdirectories like r8-from-X-upto-Y/. All matching files are - concatenated into the output, sorted by path for determinism. - Args: - jar: The JAR file to extract from. - output: The output file to write to. - """ - meta_inf_prefix = "META-INF/com.android.tools/" +def _ExtractTargetedR8Rules(jar, output, r8_version): + """Extract version-targeted R8 rules from META-INF/com.android.tools/.""" + if not r8_version: + return + + r8_prefix = "META-INF/com.android.tools/" + + targeted_entries = [] for entry in sorted(jar.namelist()): - if entry.startswith(meta_inf_prefix) and not entry.endswith("/"): - output.write(b"\n") - output.write(jar.read(entry)) + if entry.startswith(r8_prefix) and re.match("r8-from-[^/]+-upto-[^/]+", entry[len(r8_prefix):]): + match = re.search("r8-from-([^/]+)-upto-([^/]+)", entry) + if match: + lower_bound, upper_bound = match.groups() + if _parse_version(lower_bound) <= _parse_version(r8_version) < _parse_version(upper_bound): + targeted_entries.append(entry) + + for out_entry in targeted_entries: + output.write(b"\n") + output.write(jar.read(out_entry)) -def ExtractEmbeddedProguardFromJar(jar, output): +def ExtractEmbeddedProguardFromJar(jar, output, r8_version): """Extract proguard specs from a JAR file. - Reads both legacy META-INF/proguard/ and R8-targeted - META-INF/com.android.tools/ entries. + Extracts version-targeted R8 rules from META-INF/com.android.tools/. + Falls back to legacy META-INF/proguard/ if no targeted rules match. Args: jar: The JAR file to extract from. output: The output file to write to. """ - legacy_prefix = "META-INF/proguard/" - r8_prefix = "META-INF/com.android.tools/" + pos = output.tell() + _ExtractTargetedR8Rules(jar, output, r8_version) + if output.tell() > pos: + return + legacy_prefix = "META-INF/proguard/" for entry in sorted(jar.namelist()): - if not entry.endswith("/") and ( - entry.startswith(legacy_prefix) or entry.startswith(r8_prefix) - ): + if entry.startswith(legacy_prefix) and not entry.endswith("/"): output.write(b"\n") output.write(jar.read(entry)) -def ExtractEmbeddedProguardFromAar(aar, output): +def ExtractEmbeddedProguardFromAar(aar, output, r8_version): """Extract proguard specs from an AAR file. Reads proguard.txt from the AAR root, and also extracts R8 rules @@ -73,26 +83,14 @@ def ExtractEmbeddedProguardFromAar(aar, output): proguard_spec = "proguard.txt" classes_jar = "classes.jar" - if proguard_spec in aar.namelist(): - output.write(aar.read(proguard_spec)) - - # For AARs, META-INF/com.android.tools/ lives inside classes.jar + # Try targeted R8 rules from classes.jar first if classes_jar in aar.namelist(): with zipfile.ZipFile(io.BytesIO(aar.read(classes_jar)), "r") as jar: - ExtractR8Rules(jar, output) - - -def ExtractEmbeddedProguardFromAarLegacy(aar, output): - """Extract proguard specs from an AAR file (legacy behavior). - - Only reads proguard.txt from the AAR root. Does not extract R8 rules - from classes.jar. - - Args: - aar: The AAR file to extract from. - output: The output file to write to. - """ - proguard_spec = "proguard.txt" + pos = output.tell() + _ExtractTargetedR8Rules(jar, output, r8_version) + if output.tell() > pos: + return + # Fall back to legacy proguard.txt if proguard_spec in aar.namelist(): output.write(aar.read(proguard_spec))