Skip to content
13 changes: 13 additions & 0 deletions tools/android/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -435,23 +435,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 +664,12 @@ genrule(
""",
visibility = ["//visibility:public"],
)

genrule(
name = "r8_version",
outs = [
"r8.version",
],
cmd = "$(location :r8) --version | awk -F' ' '{ print $$2 }' >$(OUTS)",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't very robust - it assumes the version format is R8 8.9.35 (build ...) (which is the case today, but can change in the future).

@damianw damianw May 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe could use the r8-version.properties resource?

e.g.

unzip -p $(location r8_jar) r8-version.properties | sed -En 's/^version.version=(.+)$/\1/p'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result looks a bit odd to me:

version.sha=afec818d6658b003ba394e10107b7dfc565dd94b
releaser=go/r8bot (luci-r8-custom-ci-archive-0-stqp)
version-file.version.code=1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Came up with this: 5e30c57

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. In my case there was also a version.version property key with the specific version. I suppose when it's built from an untagged version that doesn't exist.

tools = [":r8"],
)
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
20 changes: 15 additions & 5 deletions tools/android/jar_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 @@ -39,18 +41,25 @@
flags.mark_flag_as_required("output_proguard_file")


def ExtractEmbeddedProguard(jar, output):
def ExtractEmbeddedProguard(jar, output, r8_version):
"""Extract proguard specs from a JAR file."""
proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, output)
proguard_extractor_lib.ExtractEmbeddedProguardFromJar(jar, output, r8_version)


def _Main(input_jar, output_proguard_file):
def _Main(input_jar, output_proguard_file, r8_version = None):
with zipfile.ZipFile(input_jar, "r") as jar:
with open(output_proguard_file, "wb") as output:
ExtractEmbeddedProguard(jar, output)
ExtractEmbeddedProguard(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)
Expand All @@ -62,9 +71,10 @@ def main(unused_argv):
_Main(
os.path.join(jar_junc, os.path.basename(jar_long)),
os.path.join(proguard_junc, os.path.basename(proguard_long)),
r8_version
)
else:
_Main(FLAGS.input_jar, FLAGS.output_proguard_file)
_Main(FLAGS.input_jar, FLAGS.output_proguard_file, r8_version)


if __name__ == "__main__":
Expand Down
Loading