Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion rules/aar_import/impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ load(
_utils = "utils",
)
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
load("//rules/flags:flags.bzl", _flags = "flags")
load("@rules_java//java/common:java_common.bzl", "java_common")
load("@rules_java//java/common:java_info.bzl", "JavaInfo")
load("@rules_java//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
Expand Down Expand Up @@ -432,10 +433,13 @@ def _collect_proguard(
ctx,
out_proguard,
aar,
aar_embedded_proguard_extractor):
aar_embedded_proguard_extractor,
extract_r8_rules = False):
args = ctx.actions.args()
args.add("--input_aar", aar)
args.add("--output_proguard_file", out_proguard)
if extract_r8_rules:
args.add("--extract_r8_rules")
ctx.actions.run(
executable = aar_embedded_proguard_extractor,
arguments = [args],
Expand Down Expand Up @@ -566,6 +570,7 @@ def impl(ctx):
proguard_spec,
aar,
_get_android_toolchain(ctx).aar_embedded_proguard_extractor.files_to_run,
extract_r8_rules = _flags.get(ctx).aar_import_extract_r8_rules,
))

lint_providers = _process_lint_rules(
Expand Down
19 changes: 19 additions & 0 deletions rules/android_binary/r8.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ load(
"utils",
)
load("//rules:visibility.bzl", "PROJECT_VISIBILITY")
load("//rules/flags:flags.bzl", _flags = "flags")

visibility(PROJECT_VISIBILITY)

Expand Down Expand Up @@ -82,6 +83,24 @@ def process_r8(ctx, validation_ctx, jvm_ctx, packaged_resources_ctx, build_info_
android_jar = get_android_sdk(ctx).android_jar
proguard_specs = proguard.get_proguard_specs(ctx, packaged_resources_ctx.resource_proguard_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.
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("--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,
)
proguard_specs = proguard_specs + [jar_embedded_proguard]

# Get min SDK version from attribute, manifest_values, or depot floor
effective_min_sdk = min_sdk_version.DEPOT_FLOOR
min_sdk_attr = getattr(ctx.attr, "min_sdk_version", 0)
Expand Down
14 changes: 14 additions & 0 deletions rules/flags/flag_defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,17 @@ def define_flags():
default = False,
description = "For testing/validation only. Use baseline profiles as startup profiles in optimized builds.",
)

flags.DEFINE_bool(
name = "r8_extract_embedded_proguard_specs",
default = False,
description = "When enabled, R8 extracts embedded proguard specs from META-INF/proguard/ " +
"and META-INF/com.android.tools/ in the deploy JAR and passes them to R8.",
)

flags.DEFINE_bool(
name = "aar_import_extract_r8_rules",
default = False,
description = "When enabled, aar_import extracts R8-targeted proguard rules from " +
"META-INF/com.android.tools/ inside classes.jar in addition to proguard.txt.",
)
6 changes: 6 additions & 0 deletions toolchains/android/toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 32 additions & 1 deletion tools/android/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,32 @@ py_binary(
],
)

py_library(
name = "proguard_extractor_lib",
srcs = ["proguard_extractor_lib.py"],
visibility = ["//visibility:private"],
)

py_binary(
name = "aar_embedded_proguard_extractor",
srcs = ["aar_embedded_proguard_extractor.py"],
visibility = ["//visibility:public"],
deps = [
":json_worker_wrapper",
":junction_lib",
":proguard_extractor_lib",
"@py_absl//absl:app",
],
)

py_binary(
name = "jar_embedded_proguard_extractor",
srcs = ["jar_embedded_proguard_extractor.py"],
visibility = ["//visibility:public"],
deps = [
":json_worker_wrapper",
":junction_lib",
":proguard_extractor_lib",
"@py_absl//absl:app",
],
)
Expand Down Expand Up @@ -476,7 +495,19 @@ py_test(
py_test(
name = "aar_embedded_proguard_extractor_test",
srcs = ["aar_embedded_proguard_extractor_test.py"],
deps = [":aar_embedded_proguard_extractor"],
deps = [
":aar_embedded_proguard_extractor",
":proguard_extractor_lib",
],
)

py_test(
name = "jar_embedded_proguard_extractor_test",
srcs = ["jar_embedded_proguard_extractor_test.py"],
deps = [
":jar_embedded_proguard_extractor",
":proguard_extractor_lib",
],
)

py_test(
Expand Down
22 changes: 13 additions & 9 deletions tools/android/aar_embedded_proguard_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from tools.android import json_worker_wrapper
from tools.android import junction
from tools.android import proguard_extractor_lib

FLAGS = flags.FLAGS

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


# 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"

if proguard_spec in aar.namelist():
output.write(aar.read(proguard_spec))
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):
def _Main(input_aar, output_proguard_file, extract_r8_rules):
with zipfile.ZipFile(input_aar, "r") as aar:
with open(output_proguard_file, "wb") as output:
ExtractEmbeddedProguard(aar, output)
ExtractEmbeddedProguard(aar, output, extract_r8_rules)


def main(unused_argv):
Expand All @@ -65,9 +68,10 @@ def main(unused_argv):
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(proguard_junc, os.path.basename(proguard_long)),
FLAGS.extract_r8_rules)
else:
_Main(FLAGS.input_aar, FLAGS.output_proguard_file)
_Main(FLAGS.input_aar, FLAGS.output_proguard_file, FLAGS.extract_r8_rules)


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

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


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)
class AarEmbeddedProguardExtractorLegacyTest(unittest.TestCase):
"""Unit tests for AAR proguard extraction (legacy behavior, extract_r8_rules=False)."""

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

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

def _makeClassesJar(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._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"", 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()
os.chdir(os.environ["TEST_TMPDIR"])

def _makeClassesJar(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 testNoProguardTxt(self):
aar = zipfile.ZipFile(io.BytesIO(), "w")
proguard_file = io.BytesIO()
proguard_extractor_lib.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()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_file.seek(0)
self.assertEqual(b"hello world", proguard_file.read())

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()
proguard_extractor_lib.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()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_file.seek(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()
proguard_extractor_lib.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()
proguard_extractor_lib.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()
proguard_extractor_lib.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()
proguard_extractor_lib.ExtractEmbeddedProguardFromAar(aar, proguard_file)
proguard_file.seek(0)
self.assertEqual(b"", proguard_file.read())


if __name__ == "__main__":
unittest.main()
Loading