Skip to content

Commit 7289309

Browse files
committed
Extract embedded proguard specs from JARs and AARs during R8
1 parent cbcf83c commit 7289309

File tree

10 files changed

+468
-20
lines changed

10 files changed

+468
-20
lines changed

rules/aar_import/impl.bzl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ load(
3939
_utils = "utils",
4040
)
4141
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
42+
load("//rules/flags:flags.bzl", _flags = "flags")
4243
load("@rules_java//java/common:java_common.bzl", "java_common")
4344
load("@rules_java//java/common:java_info.bzl", "JavaInfo")
4445
load("@rules_java//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
@@ -432,10 +433,13 @@ def _collect_proguard(
432433
ctx,
433434
out_proguard,
434435
aar,
435-
aar_embedded_proguard_extractor):
436+
aar_embedded_proguard_extractor,
437+
extract_r8_rules = False):
436438
args = ctx.actions.args()
437439
args.add("--input_aar", aar)
438440
args.add("--output_proguard_file", out_proguard)
441+
if extract_r8_rules:
442+
args.add("--extract_r8_rules")
439443
ctx.actions.run(
440444
executable = aar_embedded_proguard_extractor,
441445
arguments = [args],
@@ -566,6 +570,7 @@ def impl(ctx):
566570
proguard_spec,
567571
aar,
568572
_get_android_toolchain(ctx).aar_embedded_proguard_extractor.files_to_run,
573+
extract_r8_rules = _flags.get(ctx).aar_import_extract_r8_rules,
569574
))
570575

571576
lint_providers = _process_lint_rules(

rules/android_binary/r8.bzl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ load(
3333
"utils",
3434
)
3535
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
36+
load("//rules/flags:flags.bzl", _flags = "flags")
3637

3738
visibility(PROJECT_VISIBILITY)
3839

@@ -82,6 +83,24 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_
8283
android_jar = get_android_sdk(ctx).android_jar
8384
proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_config)
8485

86+
# Optionally extract proguard specs embedded in the deploy JAR (META-INF/proguard/
87+
# and META-INF/com.android.tools/) so they are passed to R8.
88+
if _flags.get(ctx).r8_extract_embedded_proguard_specs:
89+
jar_embedded_proguard = ctx.actions.declare_file(ctx.label.name + "_jar_embedded_proguard.pro")
90+
jar_extractor_args = ctx.actions.args()
91+
jar_extractor_args.add("--input_jar", deploy_jar)
92+
jar_extractor_args.add("--output_proguard_file", jar_embedded_proguard)
93+
ctx.actions.run(
94+
executable = get_android_toolchain(ctx).jar_embedded_proguard_extractor.files_to_run,
95+
arguments = [jar_extractor_args],
96+
inputs = [deploy_jar],
97+
outputs = [jar_embedded_proguard],
98+
mnemonic = "JarEmbeddedProguardExtractor",
99+
progress_message = "Extracting proguard specs from deploy jar for %{label}",
100+
toolchain = None,
101+
)
102+
proguard_specs = proguard_specs + [jar_embedded_proguard]
103+
85104
# Get min SDK version from attribute, manifest_values, or depot floor
86105
effective_min_sdk = min_sdk_version.DEPOT_FLOOR
87106
min_sdk_attr = getattr(ctx.attr, "min_sdk_version", 0)

rules/flags/flag_defs.bzl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,17 @@ def define_flags():
9494
default = False,
9595
description = "For testing/validation only. Use baseline profiles as startup profiles in optimized builds.",
9696
)
97+
98+
flags.DEFINE_bool(
99+
name = "r8_extract_embedded_proguard_specs",
100+
default = False,
101+
description = "When enabled, R8 extracts embedded proguard specs from META-INF/proguard/ " +
102+
"and META-INF/com.android.tools/ in the deploy JAR and passes them to R8.",
103+
)
104+
105+
flags.DEFINE_bool(
106+
name = "aar_import_extract_r8_rules",
107+
default = False,
108+
description = "When enabled, aar_import extracts R8-targeted proguard rules from " +
109+
"META-INF/com.android.tools/ inside classes.jar in addition to proguard.txt.",
110+
)

toolchains/android/toolchain.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ _ATTRS = dict(
3737
default = "//tools/android:aar_embedded_proguard_extractor",
3838
executable = True,
3939
),
40+
jar_embedded_proguard_extractor = attr.label(
41+
allow_files = True,
42+
cfg = "exec",
43+
default = "//tools/android:jar_embedded_proguard_extractor",
44+
executable = True,
45+
),
4046
aar_native_libs_zip_creator = attr.label(
4147
allow_files = True,
4248
cfg = "exec",

tools/android/BUILD

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,13 +426,32 @@ py_binary(
426426
],
427427
)
428428

429+
py_library(
430+
name = "proguard_extractor_lib",
431+
srcs = ["proguard_extractor_lib.py"],
432+
visibility = ["//visibility:private"],
433+
)
434+
429435
py_binary(
430436
name = "aar_embedded_proguard_extractor",
431437
srcs = ["aar_embedded_proguard_extractor.py"],
432438
visibility = ["//visibility:public"],
433439
deps = [
434440
":json_worker_wrapper",
435441
":junction_lib",
442+
":proguard_extractor_lib",
443+
"@py_absl//absl:app",
444+
],
445+
)
446+
447+
py_binary(
448+
name = "jar_embedded_proguard_extractor",
449+
srcs = ["jar_embedded_proguard_extractor.py"],
450+
visibility = ["//visibility:public"],
451+
deps = [
452+
":json_worker_wrapper",
453+
":junction_lib",
454+
":proguard_extractor_lib",
436455
"@py_absl//absl:app",
437456
],
438457
)
@@ -476,7 +495,19 @@ py_test(
476495
py_test(
477496
name = "aar_embedded_proguard_extractor_test",
478497
srcs = ["aar_embedded_proguard_extractor_test.py"],
479-
deps = [":aar_embedded_proguard_extractor"],
498+
deps = [
499+
":aar_embedded_proguard_extractor",
500+
":proguard_extractor_lib",
501+
],
502+
)
503+
504+
py_test(
505+
name = "jar_embedded_proguard_extractor_test",
506+
srcs = ["jar_embedded_proguard_extractor_test.py"],
507+
deps = [
508+
":jar_embedded_proguard_extractor",
509+
":proguard_extractor_lib",
510+
],
480511
)
481512

482513
py_test(

tools/android/aar_embedded_proguard_extractor.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from tools.android import json_worker_wrapper
2929
from tools.android import junction
30+
from tools.android import proguard_extractor_lib
3031

3132
FLAGS = flags.FLAGS
3233

@@ -35,21 +36,23 @@
3536
flags.DEFINE_string("output_proguard_file", None,
3637
"Output parameter file for proguard")
3738
flags.mark_flag_as_required("output_proguard_file")
39+
flags.DEFINE_boolean("extract_r8_rules", False,
40+
"Also extract R8-targeted rules from classes.jar")
3841

3942

4043
# Attempt to extract proguard spec from AAR. If the file doesn't exist, an empty
4144
# proguard spec file will be created
42-
def ExtractEmbeddedProguard(aar, output):
43-
proguard_spec = "proguard.txt"
44-
45-
if proguard_spec in aar.namelist():
46-
output.write(aar.read(proguard_spec))
45+
def ExtractEmbeddedProguard(aar, output, extract_r8_rules=False):
46+
if extract_r8_rules:
47+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, output)
48+
else:
49+
proguard_extractor_lib.ExtractEmbeddedProguardFromAarLegacy(aar, output)
4750

4851

49-
def _Main(input_aar, output_proguard_file):
52+
def _Main(input_aar, output_proguard_file, extract_r8_rules):
5053
with zipfile.ZipFile(input_aar, "r") as aar:
5154
with open(output_proguard_file, "wb") as output:
52-
ExtractEmbeddedProguard(aar, output)
55+
ExtractEmbeddedProguard(aar, output, extract_r8_rules)
5356

5457

5558
def main(unused_argv):
@@ -65,9 +68,10 @@ def main(unused_argv):
6568
os.path.dirname(proguard_long)) as proguard_junc:
6669
_Main(
6770
os.path.join(aar_junc, os.path.basename(aar_long)),
68-
os.path.join(proguard_junc, os.path.basename(proguard_long)))
71+
os.path.join(proguard_junc, os.path.basename(proguard_long)),
72+
FLAGS.extract_r8_rules)
6973
else:
70-
_Main(FLAGS.input_aar, FLAGS.output_proguard_file)
74+
_Main(FLAGS.input_aar, FLAGS.output_proguard_file, FLAGS.extract_r8_rules)
7175

7276

7377
if __name__ == "__main__":

tools/android/aar_embedded_proguard_extractor_test.py

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,14 @@
1919
import zipfile
2020

2121
from tools.android import aar_embedded_proguard_extractor
22+
from tools.android import proguard_extractor_lib
2223

2324

24-
class AarEmbeddedProguardExtractor(unittest.TestCase):
25-
"""Unit tests for aar_embedded_proguard_extractor.py."""
26-
27-
# Python 2 alias
28-
if not hasattr(unittest.TestCase, "assertCountEqual"):
29-
30-
def assertCountEqual(self, *args):
31-
return self.assertItemsEqual(*args)
25+
class AarEmbeddedProguardExtractorLegacyTest(unittest.TestCase):
26+
"""Unit tests for AAR proguard extraction (legacy behavior, extract_r8_rules=False)."""
3227

3328
def setUp(self):
34-
super(AarEmbeddedProguardExtractor, self).setUp()
29+
super(AarEmbeddedProguardExtractorLegacyTest, self).setUp()
3530
os.chdir(os.environ["TEST_TMPDIR"])
3631

3732
def testNoProguardTxt(self):
@@ -49,6 +44,123 @@ def testWithProguardTxt(self):
4944
proguard_file.seek(0)
5045
self.assertEqual(b"hello world", proguard_file.read())
5146

47+
def _makeClassesJar(self, entries):
48+
jar_buf = io.BytesIO()
49+
with zipfile.ZipFile(jar_buf, "w") as jar:
50+
for path, content in entries.items():
51+
jar.writestr(path, content)
52+
return jar_buf.getvalue()
53+
54+
def testR8RulesFromClassesJarIgnoredByDefault(self):
55+
classes_jar = self._makeClassesJar({
56+
"META-INF/com.android.tools/r8/rules.pro": "-keep class A",
57+
})
58+
aar = zipfile.ZipFile(io.BytesIO(), "w")
59+
aar.writestr("classes.jar", classes_jar)
60+
proguard_file = io.BytesIO()
61+
aar_embedded_proguard_extractor.ExtractEmbeddedProguard(aar, proguard_file)
62+
proguard_file.seek(0)
63+
self.assertEqual(b"", proguard_file.read())
64+
65+
66+
class AarEmbeddedProguardExtractorWithR8RulesTest(unittest.TestCase):
67+
"""Unit tests for AAR proguard extraction with extract_r8_rules=True."""
68+
69+
def setUp(self):
70+
super(AarEmbeddedProguardExtractorWithR8RulesTest, self).setUp()
71+
os.chdir(os.environ["TEST_TMPDIR"])
72+
73+
def _makeClassesJar(self, entries):
74+
jar_buf = io.BytesIO()
75+
with zipfile.ZipFile(jar_buf, "w") as jar:
76+
for path, content in entries.items():
77+
jar.writestr(path, content)
78+
return jar_buf.getvalue()
79+
80+
def testNoProguardTxt(self):
81+
aar = zipfile.ZipFile(io.BytesIO(), "w")
82+
proguard_file = io.BytesIO()
83+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
84+
proguard_file.seek(0)
85+
self.assertEqual(b"", proguard_file.read())
86+
87+
def testWithProguardTxt(self):
88+
aar = zipfile.ZipFile(io.BytesIO(), "w")
89+
aar.writestr("proguard.txt", "hello world")
90+
proguard_file = io.BytesIO()
91+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
92+
proguard_file.seek(0)
93+
self.assertEqual(b"hello world", proguard_file.read())
94+
95+
def testR8RulesFromClassesJar(self):
96+
classes_jar = self._makeClassesJar({
97+
"META-INF/com.android.tools/r8/rules.pro": "-keep class A",
98+
})
99+
aar = zipfile.ZipFile(io.BytesIO(), "w")
100+
aar.writestr("classes.jar", classes_jar)
101+
proguard_file = io.BytesIO()
102+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
103+
proguard_file.seek(0)
104+
self.assertEqual(b"\n-keep class A", proguard_file.read())
105+
106+
def testR8RulesFromVersionedSubdirs(self):
107+
classes_jar = self._makeClassesJar({
108+
"META-INF/com.android.tools/r8-from-8.0.0/rules.pro": "-keep class B",
109+
"META-INF/com.android.tools/r8-upto-8.0.0/rules.pro": "-keep class C",
110+
})
111+
aar = zipfile.ZipFile(io.BytesIO(), "w")
112+
aar.writestr("classes.jar", classes_jar)
113+
proguard_file = io.BytesIO()
114+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
115+
proguard_file.seek(0)
116+
self.assertEqual(
117+
b"\n-keep class B\n-keep class C", proguard_file.read())
118+
119+
def testR8RulesAndProguardTxtCombined(self):
120+
classes_jar = self._makeClassesJar({
121+
"META-INF/com.android.tools/r8/rules.pro": "-keep class D",
122+
})
123+
aar = zipfile.ZipFile(io.BytesIO(), "w")
124+
aar.writestr("proguard.txt", "-keep class E")
125+
aar.writestr("classes.jar", classes_jar)
126+
proguard_file = io.BytesIO()
127+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
128+
proguard_file.seek(0)
129+
self.assertEqual(
130+
b"-keep class E\n-keep class D", proguard_file.read())
131+
132+
def testR8RulesIgnoresDirectoryEntries(self):
133+
classes_jar = self._makeClassesJar({
134+
"META-INF/com.android.tools/": "",
135+
"META-INF/com.android.tools/r8/": "",
136+
"META-INF/com.android.tools/r8/rules.pro": "-keep class F",
137+
})
138+
aar = zipfile.ZipFile(io.BytesIO(), "w")
139+
aar.writestr("classes.jar", classes_jar)
140+
proguard_file = io.BytesIO()
141+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
142+
proguard_file.seek(0)
143+
self.assertEqual(b"\n-keep class F", proguard_file.read())
144+
145+
def testNoClassesJarNoR8Rules(self):
146+
aar = zipfile.ZipFile(io.BytesIO(), "w")
147+
aar.writestr("some_other_file.txt", "data")
148+
proguard_file = io.BytesIO()
149+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
150+
proguard_file.seek(0)
151+
self.assertEqual(b"", proguard_file.read())
152+
153+
def testClassesJarWithoutR8Rules(self):
154+
classes_jar = self._makeClassesJar({
155+
"com/example/Foo.class": "classdata",
156+
})
157+
aar = zipfile.ZipFile(io.BytesIO(), "w")
158+
aar.writestr("classes.jar", classes_jar)
159+
proguard_file = io.BytesIO()
160+
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
161+
proguard_file.seek(0)
162+
self.assertEqual(b"", proguard_file.read())
163+
52164

53165
if __name__ == "__main__":
54166
unittest.main()

0 commit comments

Comments
 (0)