From 12bdf5aec5980c36923c25f95fd3c21966099d1c Mon Sep 17 00:00:00 2001 From: Sheroz Nazhmudinov Date: Thu, 12 Feb 2026 11:11:57 +0100 Subject: [PATCH 1/2] Extract embedded proguard specs from JARs and AARs during R8 Add jar_embedded_proguard_extractor to scan META-INF/proguard/ and META-INF/com.android.tools/ in the deploy JAR before R8, and extend aar_embedded_proguard_extractor to also read targeted R8 rules from META-INF/com.android.tools/ inside classes.jar. --- rules/android_binary/r8.bzl | 18 ++- toolchains/android/toolchain.bzl | 6 + tools/android/BUILD | 17 +++ .../aar_embedded_proguard_extractor.py | 18 +++ .../aar_embedded_proguard_extractor_test.py | 78 +++++++++++++ .../jar_embedded_proguard_extractor.py | 74 ++++++++++++ .../jar_embedded_proguard_extractor_test.py | 110 ++++++++++++++++++ 7 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 tools/android/jar_embedded_proguard_extractor.py create mode 100644 tools/android/jar_embedded_proguard_extractor_test.py diff --git a/rules/android_binary/r8.bzl b/rules/android_binary/r8.bzl index 624295d99..e77cef788 100644 --- a/rules/android_binary/r8.bzl +++ b/rules/android_binary/r8.bzl @@ -79,8 +79,24 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_ dexes_zip = ctx.actions.declare_file(ctx.label.name + "_dexes.zip") proguard_mappings_output_file = ctx.actions.declare_file(ctx.label.name + "_proguard.map") + # Extract proguard specs embedded in the deploy JAR (META-INF/proguard/ + # and META-INF/com.android.tools/) so they are passed to R8. + 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("--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], + outputs = [jar_embedded_proguard], + mnemonic = "JarEmbeddedProguardExtractor", + progress_message = "Extracting proguard specs from deploy jar for %{label}", + toolchain = None, + ) + android_jar = get_android_sdk(ctx).android_jar - proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_config) + proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_config) + [jar_embedded_proguard] # Get min SDK version from attribute, manifest_values, or depot floor effective_min_sdk = min_sdk_version.DEPOT_FLOOR diff --git a/toolchains/android/toolchain.bzl b/toolchains/android/toolchain.bzl index 98dd0f32a..98fb7773e 100644 --- a/toolchains/android/toolchain.bzl +++ b/toolchains/android/toolchain.bzl @@ -37,6 +37,12 @@ _ATTRS = dict( default = "//tools/android:aar_embedded_proguard_extractor", executable = True, ), + jar_embedded_proguard_extractor = attr.label( + allow_files = True, + cfg = "exec", + default = "//tools/android:jar_embedded_proguard_extractor", + executable = True, + ), aar_native_libs_zip_creator = attr.label( allow_files = True, cfg = "exec", diff --git a/tools/android/BUILD b/tools/android/BUILD index a67aa0f35..d72c67989 100644 --- a/tools/android/BUILD +++ b/tools/android/BUILD @@ -437,6 +437,17 @@ py_binary( ], ) +py_binary( + name = "jar_embedded_proguard_extractor", + srcs = ["jar_embedded_proguard_extractor.py"], + visibility = ["//visibility:public"], + deps = [ + ":json_worker_wrapper", + ":junction_lib", + "@py_absl//absl:app", + ], +) + py_binary( name = "aar_native_libs_zip_creator", srcs = [ @@ -479,6 +490,12 @@ py_test( deps = [":aar_embedded_proguard_extractor"], ) +py_test( + name = "jar_embedded_proguard_extractor_test", + srcs = ["jar_embedded_proguard_extractor_test.py"], + deps = [":jar_embedded_proguard_extractor"], +) + py_test( name = "aar_resources_extractor_test", srcs = ["aar_resources_extractor_test.py"], diff --git a/tools/android/aar_embedded_proguard_extractor.py b/tools/android/aar_embedded_proguard_extractor.py index c8f5c2c8e..257f77c23 100644 --- a/tools/android/aar_embedded_proguard_extractor.py +++ b/tools/android/aar_embedded_proguard_extractor.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import print_function +import io import os import zipfile @@ -36,15 +37,32 @@ "Output parameter file for proguard") flags.mark_flag_as_required("output_proguard_file") +def _ExtractR8Rules(jar, output): + """Extract R8 rules from META-INF/com.android.tools/ inside a JAR. + + Handles subdirectories like r8-from-X-upto-Y/. All matching files are + concatenated into the output, sorted by path for determinism. + """ + meta_inf_prefix = "META-INF/com.android.tools/" + 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)) # 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): 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 + if classes_jar in aar.namelist(): + with zipfile.ZipFile(io.BytesIO(aar.read(classes_jar)), "r") as jar: + _ExtractR8Rules(jar, output) + def _Main(input_aar, output_proguard_file): with zipfile.ZipFile(input_aar, "r") as aar: diff --git a/tools/android/aar_embedded_proguard_extractor_test.py b/tools/android/aar_embedded_proguard_extractor_test.py index 457520f67..24f6fe67e 100644 --- a/tools/android/aar_embedded_proguard_extractor_test.py +++ b/tools/android/aar_embedded_proguard_extractor_test.py @@ -49,6 +49,84 @@ def testWithProguardTxt(self): proguard_file.seek(0) self.assertEqual(b"hello world", proguard_file.read()) + def _makeClassesJar(self, entries): + """Create an in-memory classes.jar with the given {path: content} 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 testR8RulesFromClassesJar(self): + classes_jar = self._makeClassesJar({ + "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"\n-keep class A", proguard_file.read()) + + def testR8RulesFromVersionedSubdirs(self): + classes_jar = self._makeClassesJar({ + "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", + }) + 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) + # Sorted by path: r8-from-8.0.0 before r8-upto-8.0.0 + self.assertEqual( + b"\n-keep class B\n-keep class C", proguard_file.read()) + + def testR8RulesAndProguardTxtCombined(self): + classes_jar = self._makeClassesJar({ + "META-INF/com.android.tools/r8/rules.pro": "-keep class D", + }) + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("proguard.txt", "-keep class E") + 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"-keep class E\n-keep class D", proguard_file.read()) + + def testR8RulesIgnoresDirectoryEntries(self): + classes_jar = self._makeClassesJar({ + "META-INF/com.android.tools/": "", + "META-INF/com.android.tools/r8/": "", + "META-INF/com.android.tools/r8/rules.pro": "-keep class F", + }) + 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"\n-keep class F", proguard_file.read()) + + def testNoClassesJarNoR8Rules(self): + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("some_other_file.txt", "data") + proguard_file = io.BytesIO() + aar_embedded_proguard_extractor.ExtractEmbeddedProguard(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + def testClassesJarWithoutR8Rules(self): + classes_jar = self._makeClassesJar({ + "com/example/Foo.class": "classdata", + }) + 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()) + if __name__ == "__main__": unittest.main() diff --git a/tools/android/jar_embedded_proguard_extractor.py b/tools/android/jar_embedded_proguard_extractor.py new file mode 100644 index 000000000..f6a9b1645 --- /dev/null +++ b/tools/android/jar_embedded_proguard_extractor.py @@ -0,0 +1,74 @@ +# pylint: disable=g-direct-third-party-import +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""A tool for extracting proguard spec files from a JAR.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import zipfile + +# Do not edit this line. Copybara replaces it with PY2 migration helper. +from absl import app +from absl import flags + +from tools.android import json_worker_wrapper +from tools.android import junction + +FLAGS = flags.FLAGS + +flags.DEFINE_string("input_jar", None, "Input JAR") +flags.mark_flag_as_required("input_jar") +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.""" + legacy_prefix = "META-INF/proguard/" + r8_prefix = "META-INF/com.android.tools/" + + for entry in sorted(jar.namelist()): + if not entry.endswith("/") and ( + entry.startswith(legacy_prefix) or entry.startswith(r8_prefix)): + output.write(b"\n") + output.write(jar.read(entry)) + + +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(unused_argv): + 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))) + else: + _Main(FLAGS.input_jar, FLAGS.output_proguard_file) + + +if __name__ == "__main__": + json_worker_wrapper.wrap_worker(FLAGS, main, app.run) diff --git a/tools/android/jar_embedded_proguard_extractor_test.py b/tools/android/jar_embedded_proguard_extractor_test.py new file mode 100644 index 000000000..426261e9b --- /dev/null +++ b/tools/android/jar_embedded_proguard_extractor_test.py @@ -0,0 +1,110 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for jar_embedded_proguard_extractor.""" + +import io +import os +import unittest +import zipfile + +from tools.android import jar_embedded_proguard_extractor + + +class JarEmbeddedProguardExtractor(unittest.TestCase): + """Unit tests for jar_embedded_proguard_extractor.py.""" + + def setUp(self): + super(JarEmbeddedProguardExtractor, self).setUp() + os.chdir(os.environ["TEST_TMPDIR"]) + + def testNoProguardSpecs(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + def testLegacyMetaInfProguard(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/proguard/rules.pro", "-keep class A") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class A", proguard_file.read()) + + def testMultipleLegacyFiles(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/proguard/rules1.pro", "-keep class A") + jar.writestr("META-INF/proguard/rules2.pro", "-keep class B") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class A\n-keep class B", proguard_file.read()) + + def testR8Rules(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/com.android.tools/r8/rules.pro", "-keep class C") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class C", proguard_file.read()) + + def testR8RulesVersionedSubdirs(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr( + "META-INF/com.android.tools/r8-from-8.0.0/rules.pro", "-keep class D") + jar.writestr( + "META-INF/com.android.tools/r8-upto-8.0.0/rules.pro", "-keep class E") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + # Sorted by path: r8-from-8.0.0 before r8-upto-8.0.0 + self.assertEqual( + b"\n-keep class D\n-keep class E", proguard_file.read()) + + def testLegacyAndR8RulesCombined(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") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + # Sorted by path: META-INF/com.android.tools before META-INF/proguard + self.assertEqual( + b"\n-keep class G\n-keep class F", 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") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class H", proguard_file.read()) + + def testIgnoresUnrelatedMetaInf(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0") + jar.writestr("META-INF/services/com.example.Spi", "com.example.SpiImpl") + jar.writestr("com/example/Foo.class", "classdata") + proguard_file = io.BytesIO() + jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + +if __name__ == "__main__": + unittest.main() From f4b21c74f18713e606fef12311a16ebcab9df142 Mon Sep 17 00:00:00 2001 From: Sheroz Nazhmudinov Date: Thu, 12 Feb 2026 22:11:28 +0100 Subject: [PATCH 2/2] Unified the tooling to extract the embedded proguard specs --- rules/aar_import/impl.bzl | 5 +- rules/android_binary/r8.bzl | 5 +- toolchains/android/toolchain.bzl | 10 +- tools/android/BUILD | 27 +-- .../aar_embedded_proguard_extractor_test.py | 132 ----------- ...=> archive_embedded_proguard_extractor.py} | 51 ++-- ...rchive_embedded_proguard_extractor_test.py | 217 ++++++++++++++++++ .../jar_embedded_proguard_extractor.py | 74 ------ .../jar_embedded_proguard_extractor_test.py | 110 --------- 9 files changed, 264 insertions(+), 367 deletions(-) delete mode 100644 tools/android/aar_embedded_proguard_extractor_test.py rename tools/android/{aar_embedded_proguard_extractor.py => archive_embedded_proguard_extractor.py} (63%) create mode 100644 tools/android/archive_embedded_proguard_extractor_test.py delete mode 100644 tools/android/jar_embedded_proguard_extractor.py delete mode 100644 tools/android/jar_embedded_proguard_extractor_test.py diff --git a/rules/aar_import/impl.bzl b/rules/aar_import/impl.bzl index 527c5a587..e7bc5f60f 100644 --- a/rules/aar_import/impl.bzl +++ b/rules/aar_import/impl.bzl @@ -434,8 +434,9 @@ def _collect_proguard( aar, aar_embedded_proguard_extractor): args = ctx.actions.args() - args.add("--input_aar", aar) + args.add("--input_archive", aar) args.add("--output_proguard_file", out_proguard) + args.add("--archive_type", "aar") ctx.actions.run( executable = aar_embedded_proguard_extractor, arguments = [args], @@ -565,7 +566,7 @@ def impl(ctx): ctx, proguard_spec, aar, - _get_android_toolchain(ctx).aar_embedded_proguard_extractor.files_to_run, + _get_android_toolchain(ctx).archive_embedded_proguard_extractor.files_to_run, )) lint_providers = _process_lint_rules( diff --git a/rules/android_binary/r8.bzl b/rules/android_binary/r8.bzl index e77cef788..452be4908 100644 --- a/rules/android_binary/r8.bzl +++ b/rules/android_binary/r8.bzl @@ -83,10 +83,11 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_ # and META-INF/com.android.tools/) so they are passed to R8. 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("--input_archive", deploy_jar) jar_extractor_args.add("--output_proguard_file", jar_embedded_proguard) + jar_extractor_args.add("--archive_type", "jar") ctx.actions.run( - executable = get_android_toolchain(ctx).jar_embedded_proguard_extractor.files_to_run, + executable = get_android_toolchain(ctx).archive_embedded_proguard_extractor.files_to_run, arguments = [jar_extractor_args], inputs = [deploy_jar], outputs = [jar_embedded_proguard], diff --git a/toolchains/android/toolchain.bzl b/toolchains/android/toolchain.bzl index 98fb7773e..9e918f5cd 100644 --- a/toolchains/android/toolchain.bzl +++ b/toolchains/android/toolchain.bzl @@ -31,16 +31,10 @@ _ATTRS = dict( default = "//tools/android:aar_embedded_jars_extractor", executable = True, ), - aar_embedded_proguard_extractor = attr.label( + archive_embedded_proguard_extractor = attr.label( allow_files = True, cfg = "exec", - default = "//tools/android:aar_embedded_proguard_extractor", - executable = True, - ), - jar_embedded_proguard_extractor = attr.label( - allow_files = True, - cfg = "exec", - default = "//tools/android:jar_embedded_proguard_extractor", + default = "//tools/android:archive_embedded_proguard_extractor", executable = True, ), aar_native_libs_zip_creator = attr.label( diff --git a/tools/android/BUILD b/tools/android/BUILD index d72c67989..18ea237dc 100644 --- a/tools/android/BUILD +++ b/tools/android/BUILD @@ -427,19 +427,8 @@ py_binary( ) py_binary( - name = "aar_embedded_proguard_extractor", - srcs = ["aar_embedded_proguard_extractor.py"], - visibility = ["//visibility:public"], - deps = [ - ":json_worker_wrapper", - ":junction_lib", - "@py_absl//absl:app", - ], -) - -py_binary( - name = "jar_embedded_proguard_extractor", - srcs = ["jar_embedded_proguard_extractor.py"], + name = "archive_embedded_proguard_extractor", + srcs = ["archive_embedded_proguard_extractor.py"], visibility = ["//visibility:public"], deps = [ ":json_worker_wrapper", @@ -485,15 +474,9 @@ py_test( ) py_test( - name = "aar_embedded_proguard_extractor_test", - srcs = ["aar_embedded_proguard_extractor_test.py"], - deps = [":aar_embedded_proguard_extractor"], -) - -py_test( - name = "jar_embedded_proguard_extractor_test", - srcs = ["jar_embedded_proguard_extractor_test.py"], - deps = [":jar_embedded_proguard_extractor"], + name = "archive_embedded_proguard_extractor_test", + srcs = ["archive_embedded_proguard_extractor_test.py"], + deps = [":archive_embedded_proguard_extractor"], ) py_test( diff --git a/tools/android/aar_embedded_proguard_extractor_test.py b/tools/android/aar_embedded_proguard_extractor_test.py deleted file mode 100644 index 24f6fe67e..000000000 --- a/tools/android/aar_embedded_proguard_extractor_test.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright 2021 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests for aar_embedded_proguard_extractor.""" - -import io -import os -import unittest -import zipfile - -from tools.android import aar_embedded_proguard_extractor - - -class AarEmbeddedProguardExtractor(unittest.TestCase): - """Unit tests for aar_embedded_proguard_extractor.py.""" - - # Python 2 alias - if not hasattr(unittest.TestCase, "assertCountEqual"): - - def assertCountEqual(self, *args): - return self.assertItemsEqual(*args) - - def setUp(self): - super(AarEmbeddedProguardExtractor, 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 _makeClassesJar(self, entries): - """Create an in-memory classes.jar with the given {path: content} 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 testR8RulesFromClassesJar(self): - classes_jar = self._makeClassesJar({ - "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"\n-keep class A", proguard_file.read()) - - def testR8RulesFromVersionedSubdirs(self): - classes_jar = self._makeClassesJar({ - "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", - }) - 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) - # Sorted by path: r8-from-8.0.0 before r8-upto-8.0.0 - self.assertEqual( - b"\n-keep class B\n-keep class C", proguard_file.read()) - - def testR8RulesAndProguardTxtCombined(self): - classes_jar = self._makeClassesJar({ - "META-INF/com.android.tools/r8/rules.pro": "-keep class D", - }) - aar = zipfile.ZipFile(io.BytesIO(), "w") - aar.writestr("proguard.txt", "-keep class E") - 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"-keep class E\n-keep class D", proguard_file.read()) - - def testR8RulesIgnoresDirectoryEntries(self): - classes_jar = self._makeClassesJar({ - "META-INF/com.android.tools/": "", - "META-INF/com.android.tools/r8/": "", - "META-INF/com.android.tools/r8/rules.pro": "-keep class F", - }) - 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"\n-keep class F", proguard_file.read()) - - def testNoClassesJarNoR8Rules(self): - aar = zipfile.ZipFile(io.BytesIO(), "w") - aar.writestr("some_other_file.txt", "data") - proguard_file = io.BytesIO() - aar_embedded_proguard_extractor.ExtractEmbeddedProguard(aar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"", proguard_file.read()) - - def testClassesJarWithoutR8Rules(self): - classes_jar = self._makeClassesJar({ - "com/example/Foo.class": "classdata", - }) - 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()) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/android/aar_embedded_proguard_extractor.py b/tools/android/archive_embedded_proguard_extractor.py similarity index 63% rename from tools/android/aar_embedded_proguard_extractor.py rename to tools/android/archive_embedded_proguard_extractor.py index 257f77c23..50ab78caa 100644 --- a/tools/android/aar_embedded_proguard_extractor.py +++ b/tools/android/archive_embedded_proguard_extractor.py @@ -12,7 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""A tool for extracting the proguard spec file from an AAR.""" +"""A tool for extracting proguard spec files from a JAR or AAR.""" from __future__ import absolute_import from __future__ import division @@ -31,11 +31,15 @@ FLAGS = flags.FLAGS -flags.DEFINE_string("input_aar", None, "Input AAR") -flags.mark_flag_as_required("input_aar") +flags.DEFINE_string("input_archive", None, "Input JAR or AAR") +flags.mark_flag_as_required("input_archive") flags.DEFINE_string("output_proguard_file", None, "Output parameter file for proguard") flags.mark_flag_as_required("output_proguard_file") +flags.DEFINE_enum("archive_type", None, ["jar", "aar"], + "Type of archive: jar or aar") +flags.mark_flag_as_required("archive_type") + def _ExtractR8Rules(jar, output): """Extract R8 rules from META-INF/com.android.tools/ inside a JAR. @@ -49,9 +53,21 @@ def _ExtractR8Rules(jar, output): output.write(b"\n") output.write(jar.read(entry)) -# 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): + +def ExtractEmbeddedProguardFromJar(jar, output): + """Extract proguard specs from a JAR file.""" + legacy_prefix = "META-INF/proguard/" + r8_prefix = "META-INF/com.android.tools/" + + for entry in sorted(jar.namelist()): + if not entry.endswith("/") and ( + entry.startswith(legacy_prefix) or entry.startswith(r8_prefix)): + output.write(b"\n") + output.write(jar.read(entry)) + + +def ExtractEmbeddedProguardFromAar(aar, output): + """Extract proguard specs from an AAR file.""" proguard_spec = "proguard.txt" classes_jar = "classes.jar" @@ -64,28 +80,29 @@ def ExtractEmbeddedProguard(aar, output): _ExtractR8Rules(jar, output) -def _Main(input_aar, output_proguard_file): - with zipfile.ZipFile(input_aar, "r") as aar: +def _Main(input_archive, output_proguard_file, archive_type): + with zipfile.ZipFile(input_archive, "r") as archive: with open(output_proguard_file, "wb") as output: - ExtractEmbeddedProguard(aar, output) + if archive_type == "jar": + ExtractEmbeddedProguardFromJar(archive, output) + else: + ExtractEmbeddedProguardFromAar(archive, output) def main(unused_argv): if os.name == "nt": - # Shorten paths unconditionally, because the extracted paths in - # ExtractEmbeddedJars (which we cannot yet predict, because they depend on - # the names of the Zip entries) may be longer than MAX_PATH. - aar_long = os.path.abspath(FLAGS.input_aar) + archive_long = os.path.abspath(FLAGS.input_archive) proguard_long = os.path.abspath(FLAGS.output_proguard_file) - with junction.TempJunction(os.path.dirname(aar_long)) as aar_junc: + with junction.TempJunction(os.path.dirname(archive_long)) as archive_junc: with junction.TempJunction( os.path.dirname(proguard_long)) as proguard_junc: _Main( - os.path.join(aar_junc, os.path.basename(aar_long)), - os.path.join(proguard_junc, os.path.basename(proguard_long))) + os.path.join(archive_junc, os.path.basename(archive_long)), + os.path.join(proguard_junc, os.path.basename(proguard_long)), + FLAGS.archive_type) else: - _Main(FLAGS.input_aar, FLAGS.output_proguard_file) + _Main(FLAGS.input_archive, FLAGS.output_proguard_file, FLAGS.archive_type) if __name__ == "__main__": diff --git a/tools/android/archive_embedded_proguard_extractor_test.py b/tools/android/archive_embedded_proguard_extractor_test.py new file mode 100644 index 000000000..5ec3e785d --- /dev/null +++ b/tools/android/archive_embedded_proguard_extractor_test.py @@ -0,0 +1,217 @@ +# Copyright 2021 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for archive_embedded_proguard_extractor.""" + +import io +import os +import unittest +import zipfile + +from tools.android import archive_embedded_proguard_extractor + + +class JarEmbeddedProguardExtractor(unittest.TestCase): + """Unit tests for JAR extraction in archive_embedded_proguard_extractor.py.""" + + def setUp(self): + super(JarEmbeddedProguardExtractor, self).setUp() + os.chdir(os.environ["TEST_TMPDIR"]) + + def testNoProguardSpecs(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + def testLegacyMetaInfProguard(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/proguard/rules.pro", "-keep class A") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class A", proguard_file.read()) + + def testMultipleLegacyFiles(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/proguard/rules1.pro", "-keep class A") + jar.writestr("META-INF/proguard/rules2.pro", "-keep class B") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class A\n-keep class B", proguard_file.read()) + + def testR8Rules(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/com.android.tools/r8/rules.pro", "-keep class C") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class C", proguard_file.read()) + + def testR8RulesVersionedSubdirs(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr( + "META-INF/com.android.tools/r8-from-8.0.0/rules.pro", "-keep class D") + jar.writestr( + "META-INF/com.android.tools/r8-upto-8.0.0/rules.pro", "-keep class E") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + # Sorted by path: r8-from-8.0.0 before r8-upto-8.0.0 + self.assertEqual( + b"\n-keep class D\n-keep class E", proguard_file.read()) + + def testLegacyAndR8RulesCombined(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") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + # Sorted by path: META-INF/com.android.tools before META-INF/proguard + self.assertEqual( + b"\n-keep class G\n-keep class F", 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") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class H", proguard_file.read()) + + def testIgnoresUnrelatedMetaInf(self): + jar = zipfile.ZipFile(io.BytesIO(), "w") + jar.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0") + jar.writestr("META-INF/services/com.example.Spi", "com.example.SpiImpl") + jar.writestr("com/example/Foo.class", "classdata") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromJar(jar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + +class AarEmbeddedProguardExtractor(unittest.TestCase): + """Unit tests for AAR extraction in archive_embedded_proguard_extractor.py.""" + + # Python 2 alias + if not hasattr(unittest.TestCase, "assertCountEqual"): + + def assertCountEqual(self, *args): + return self.assertItemsEqual(*args) + + def setUp(self): + super(AarEmbeddedProguardExtractor, self).setUp() + os.chdir(os.environ["TEST_TMPDIR"]) + + def testNoProguardTxt(self): + aar = zipfile.ZipFile(io.BytesIO(), "w") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(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() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"hello world", proguard_file.read()) + + def _makeClassesJar(self, entries): + """Create an in-memory classes.jar with the given {path: content} 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 testR8RulesFromClassesJar(self): + classes_jar = self._makeClassesJar({ + "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() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class A", proguard_file.read()) + + def testR8RulesFromVersionedSubdirs(self): + classes_jar = self._makeClassesJar({ + "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", + }) + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("classes.jar", classes_jar) + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + # Sorted by path: r8-from-8.0.0 before r8-upto-8.0.0 + self.assertEqual( + b"\n-keep class B\n-keep class C", proguard_file.read()) + + def testR8RulesAndProguardTxtCombined(self): + classes_jar = self._makeClassesJar({ + "META-INF/com.android.tools/r8/rules.pro": "-keep class D", + }) + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("proguard.txt", "-keep class E") + aar.writestr("classes.jar", classes_jar) + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual( + b"-keep class E\n-keep class D", proguard_file.read()) + + def testR8RulesIgnoresDirectoryEntries(self): + classes_jar = self._makeClassesJar({ + "META-INF/com.android.tools/": "", + "META-INF/com.android.tools/r8/": "", + "META-INF/com.android.tools/r8/rules.pro": "-keep class F", + }) + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("classes.jar", classes_jar) + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"\n-keep class F", proguard_file.read()) + + def testNoClassesJarNoR8Rules(self): + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("some_other_file.txt", "data") + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + def testClassesJarWithoutR8Rules(self): + classes_jar = self._makeClassesJar({ + "com/example/Foo.class": "classdata", + }) + aar = zipfile.ZipFile(io.BytesIO(), "w") + aar.writestr("classes.jar", classes_jar) + proguard_file = io.BytesIO() + archive_embedded_proguard_extractor.ExtractEmbeddedProguardFromAar(aar, proguard_file) + proguard_file.seek(0) + self.assertEqual(b"", proguard_file.read()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/android/jar_embedded_proguard_extractor.py b/tools/android/jar_embedded_proguard_extractor.py deleted file mode 100644 index f6a9b1645..000000000 --- a/tools/android/jar_embedded_proguard_extractor.py +++ /dev/null @@ -1,74 +0,0 @@ -# pylint: disable=g-direct-third-party-import -# Copyright 2021 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""A tool for extracting proguard spec files from a JAR.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import zipfile - -# Do not edit this line. Copybara replaces it with PY2 migration helper. -from absl import app -from absl import flags - -from tools.android import json_worker_wrapper -from tools.android import junction - -FLAGS = flags.FLAGS - -flags.DEFINE_string("input_jar", None, "Input JAR") -flags.mark_flag_as_required("input_jar") -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.""" - legacy_prefix = "META-INF/proguard/" - r8_prefix = "META-INF/com.android.tools/" - - for entry in sorted(jar.namelist()): - if not entry.endswith("/") and ( - entry.startswith(legacy_prefix) or entry.startswith(r8_prefix)): - output.write(b"\n") - output.write(jar.read(entry)) - - -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(unused_argv): - 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))) - else: - _Main(FLAGS.input_jar, FLAGS.output_proguard_file) - - -if __name__ == "__main__": - json_worker_wrapper.wrap_worker(FLAGS, main, app.run) diff --git a/tools/android/jar_embedded_proguard_extractor_test.py b/tools/android/jar_embedded_proguard_extractor_test.py deleted file mode 100644 index 426261e9b..000000000 --- a/tools/android/jar_embedded_proguard_extractor_test.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2021 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tests for jar_embedded_proguard_extractor.""" - -import io -import os -import unittest -import zipfile - -from tools.android import jar_embedded_proguard_extractor - - -class JarEmbeddedProguardExtractor(unittest.TestCase): - """Unit tests for jar_embedded_proguard_extractor.py.""" - - def setUp(self): - super(JarEmbeddedProguardExtractor, self).setUp() - os.chdir(os.environ["TEST_TMPDIR"]) - - def testNoProguardSpecs(self): - jar = zipfile.ZipFile(io.BytesIO(), "w") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"", proguard_file.read()) - - def testLegacyMetaInfProguard(self): - jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr("META-INF/proguard/rules.pro", "-keep class A") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"\n-keep class A", proguard_file.read()) - - def testMultipleLegacyFiles(self): - jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr("META-INF/proguard/rules1.pro", "-keep class A") - jar.writestr("META-INF/proguard/rules2.pro", "-keep class B") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"\n-keep class A\n-keep class B", proguard_file.read()) - - def testR8Rules(self): - jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr("META-INF/com.android.tools/r8/rules.pro", "-keep class C") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"\n-keep class C", proguard_file.read()) - - def testR8RulesVersionedSubdirs(self): - jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr( - "META-INF/com.android.tools/r8-from-8.0.0/rules.pro", "-keep class D") - jar.writestr( - "META-INF/com.android.tools/r8-upto-8.0.0/rules.pro", "-keep class E") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - # Sorted by path: r8-from-8.0.0 before r8-upto-8.0.0 - self.assertEqual( - b"\n-keep class D\n-keep class E", proguard_file.read()) - - def testLegacyAndR8RulesCombined(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") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - # Sorted by path: META-INF/com.android.tools before META-INF/proguard - self.assertEqual( - b"\n-keep class G\n-keep class F", 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") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"\n-keep class H", proguard_file.read()) - - def testIgnoresUnrelatedMetaInf(self): - jar = zipfile.ZipFile(io.BytesIO(), "w") - jar.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0") - jar.writestr("META-INF/services/com.example.Spi", "com.example.SpiImpl") - jar.writestr("com/example/Foo.class", "classdata") - proguard_file = io.BytesIO() - jar_embedded_proguard_extractor.ExtractEmbeddedProguard(jar, proguard_file) - proguard_file.seek(0) - self.assertEqual(b"", proguard_file.read()) - - -if __name__ == "__main__": - unittest.main()