diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java index cf75b4943d3644..b3712a895b1388 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java @@ -14,6 +14,7 @@ package com.google.devtools.build.lib.rules.android; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; @@ -45,6 +46,8 @@ import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider; import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider; import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; +import com.google.devtools.build.lib.rules.java.ProguardLibrary; +import com.google.devtools.build.lib.rules.java.ProguardSpecProvider; import com.google.devtools.build.lib.starlarkbuildapi.android.DataBindingV2ProviderApi; import com.google.devtools.build.lib.vfs.PathFragment; import javax.annotation.Nullable; @@ -61,6 +64,7 @@ public class AarImport implements RuleConfiguredTargetFactory { private static final String ANDROID_MANIFEST = "AndroidManifest.xml"; private static final String MERGED_JAR = "classes_and_libs_merged.jar"; + private static final String PROGUARD_SPEC = "proguard.txt"; private final JavaSemantics javaSemantics; private final AndroidSemantics androidSemantics; @@ -242,6 +246,7 @@ public ConfiguredTarget create(RuleContext ruleContext) .setFilesToBuild(filesToBuild) .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY) .addNativeDeclaredProvider(dataBindingV2Provider) + .addNativeDeclaredProvider(new ProguardSpecProvider(extractProguardSpecs(ruleContext, aar))) .addNativeDeclaredProvider( new AndroidNativeLibsInfo( AndroidCommon.collectTransitiveNativeLibs(ruleContext).add(nativeLibs).build())) @@ -259,6 +264,46 @@ private static NestedSet getCompileTimeJarsFromCollection( return isDirect ? provider.getDirectCompileTimeJars() : provider.getTransitiveCompileTimeJars(); } + /** + * Collect Proguard Specs from transitives and proguard.txt if it exists in the AAR file. In the + * case the proguard.txt file does exists, we need to extract it from the AAR file + */ + private NestedSet extractProguardSpecs(RuleContext ruleContext, Artifact aar) { + + NestedSet proguardSpecs = + new ProguardLibrary(ruleContext).collectProguardSpecs(ImmutableSet.of("deps", "exports")); + + Artifact proguardSpecArtifact = createAarArtifact(ruleContext, PROGUARD_SPEC); + + ruleContext.registerAction( + createAarEmbeddedProguardExtractorActions(ruleContext, aar, proguardSpecArtifact)); + + NestedSetBuilder builder = NestedSetBuilder.naiveLinkOrder(); + return builder.addTransitive(proguardSpecs).add(proguardSpecArtifact).build(); + } + + /** + * Create action to extract embedded Proguard.txt from an AAR. If the file is not found, an empty + * file will be created + */ + private static Action[] createAarEmbeddedProguardExtractorActions( + RuleContext ruleContext, Artifact aar, Artifact proguardSpecArtifact) { + return new SpawnAction.Builder() + .useDefaultShellEnvironment() + .setExecutable( + ruleContext.getExecutablePrerequisite(AarImportBaseRule.AAR_EMBEDDED_PROGUARD_EXTACTOR)) + .setMnemonic("AarEmbeddedProguardExtractor") + .setProgressMessage("Extracting proguard.txt from %s", aar.getFilename()) + .addInput(aar) + .addOutput(proguardSpecArtifact) + .addCommandLine( + CustomCommandLine.builder() + .addExecPath("--input_aar", aar) + .addExecPath("--output_proguard_file", proguardSpecArtifact) + .build()) + .build(ruleContext); + } + private NestedSet getBootclasspath(RuleContext ruleContext) { if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) { return NestedSetBuilder.stableOrder() diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java index 071d36419a5775..b51c3f4a0f07ce 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AarImportBaseRule.java @@ -34,6 +34,7 @@ public class AarImportBaseRule implements RuleDefinition { static final String AAR_EMBEDDED_JARS_EXTACTOR = "$aar_embedded_jars_extractor"; + static final String AAR_EMBEDDED_PROGUARD_EXTACTOR = "$aar_embedded_proguard_extractor"; static final String AAR_NATIVE_LIBS_ZIP_CREATOR = "$aar_native_libs_zip_creator"; static final String AAR_RESOURCES_EXTRACTOR = "$aar_resources_extractor"; static final String ZIPPER = "$zipper"; @@ -66,6 +67,11 @@ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) .cfg(HostTransition.createFactory()) .exec() .value(env.getToolsLabel("//tools/android:aar_embedded_jars_extractor"))) + .add( + attr(AAR_EMBEDDED_PROGUARD_EXTACTOR, LABEL) + .cfg(HostTransition.createFactory()) + .exec() + .value(env.getToolsLabel("//tools/android:aar_embedded_proguard_extractor"))) .add( attr(AAR_NATIVE_LIBS_ZIP_CREATOR, LABEL) .cfg(HostTransition.createFactory()) diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java index 0d86709a7c7721..f631957ccb6df7 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java @@ -388,6 +388,7 @@ private ImmutableList createAndroidBuildContents() { .add(" jars = [ 'ZipFilterAction_deploy.jar' ])") .add("sh_binary(name = 'aar_resources_extractor', srcs = ['empty.sh'])") .add("sh_binary(name = 'aar_embedded_jars_extractor', srcs = ['empty.sh'])") + .add("sh_binary(name = 'aar_embedded_proguard_extractor', srcs = ['empty.sh'])") .add("java_import(name = 'idlclass_import',") .add(" jars = [ 'idlclass.jar' ])") .add("exports_files(['adb', 'adb_static'])") diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java index 5f3e92ace9a971..3389eecd5fb7d9 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AarImportTest.java @@ -40,6 +40,7 @@ import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.OutputJar; import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider; import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider; +import com.google.devtools.build.lib.rules.java.ProguardSpecProvider; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -140,6 +141,46 @@ public void setup() throws Exception { getAnalysisMock().ccSupport().setupCcToolchainConfigForCpu(mockToolsConfig, "armeabi-v7a"); } + @Test + public void proguardSpecsProvided() throws Exception { + ConfiguredTarget binaryTarget = getConfiguredTarget("//a:bar"); + + NestedSet transitiveProguardSpecs = + binaryTarget.get(ProguardSpecProvider.PROVIDER).getTransitiveProguardSpecs(); + + assertThat( + transitiveProguardSpecs.toSet().stream() + .map(Artifact::getRootRelativePathString) + .collect(Collectors.toSet())) + .containsExactly( + "a/_aar/bar/proguard.txt", "a/_aar/foo/proguard.txt", "a/_aar/baz/proguard.txt"); + } + + @Test + public void testProguardExtractor() throws Exception { + Artifact proguardSpecsAritfact = + getConfiguredTarget("//a:bar") + .get(ProguardSpecProvider.PROVIDER) + .getTransitiveProguardSpecs() + .toList() + .get(0); + + Artifact aarProguardExtractor = + getHostConfiguredTarget( + ruleClassProvider.getToolsRepository() + + "//tools/android:aar_embedded_proguard_extractor") + .getProvider(FilesToRunProvider.class) + .getExecutable(); + + assertThat(getGeneratingSpawnAction(proguardSpecsAritfact).getArguments()) + .containsExactly( + aarProguardExtractor.getExecPathString(), + "--input_aar", + "a/bar.aar", + "--output_proguard_file", + proguardSpecsAritfact.getExecPathString()); + } + @Test public void aapt2RTxtProvided() throws Exception { useConfiguration("--android_sdk=//aapt2/sdk:sdk"); diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/BUILD b/src/test/java/com/google/devtools/build/lib/rules/android/BUILD index 9d2cba0235e2fd..961859818c811e 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/BUILD +++ b/src/test/java/com/google/devtools/build/lib/rules/android/BUILD @@ -25,6 +25,7 @@ java_test( "//src/main/java/com/google/devtools/build/lib/collect/nestedset", "//src/main/java/com/google/devtools/build/lib/rules/android", "//src/main/java/com/google/devtools/build/lib/rules/java:java-compilation", + "//src/main/java/com/google/devtools/build/lib/rules/java:java-rules", "//src/test/java/com/google/devtools/build/lib/actions/util", "//src/test/java/com/google/devtools/build/lib/analysis/util", "//third_party:guava", diff --git a/tools/android/BUILD b/tools/android/BUILD index 44b782e3b363f5..22d20e5507b3c6 100644 --- a/tools/android/BUILD +++ b/tools/android/BUILD @@ -118,6 +118,21 @@ py_test( ], ) +py_binary( + name = "aar_embedded_proguard_extractor", + srcs = ["aar_embedded_proguard_extractor.py"], + deps = [ + ":junction_lib", + "//third_party/py/abseil", + ], +) + +py_test( + name = "aar_embedded_proguard_extractor_test", + srcs = ["aar_embedded_proguard_extractor_test.py"], + deps = [":aar_embedded_proguard_extractor"], +) + py_binary( name = "aar_embedded_jars_extractor", srcs = ["aar_embedded_jars_extractor.py"], diff --git a/tools/android/BUILD.tools b/tools/android/BUILD.tools index fb4b49d8fbbecd..01f29657c794dd 100644 --- a/tools/android/BUILD.tools +++ b/tools/android/BUILD.tools @@ -328,6 +328,16 @@ py_binary( ], ) +py_binary( + name = "aar_embedded_proguard_extractor", + srcs = ["aar_embedded_proguard_extractor.py"], + python_version = "PY3", + deps = [ + ":junction_lib", + "//third_party/py/abseil", + ], +) + py_binary( name = "aar_embedded_jars_extractor", srcs = ["aar_embedded_jars_extractor.py"], diff --git a/tools/android/aar_embedded_proguard_extractor.py b/tools/android/aar_embedded_proguard_extractor.py new file mode 100644 index 00000000000000..f39e5e5d03449e --- /dev/null +++ b/tools/android/aar_embedded_proguard_extractor.py @@ -0,0 +1,76 @@ +# Lint as: python2, python3 +# 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 the proguard spec file from an AAR.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +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 junction + +FLAGS = flags.FLAGS + +flags.DEFINE_string("input_aar", None, "Input AAR") +flags.mark_flag_as_required("input_aar") +flags.DEFINE_string("output_proguard_file", None, + "Output parameter file for proguard") +flags.mark_flag_as_required("output_proguard_file") + + +# 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 _Main(input_aar, output_proguard_file): + with zipfile.ZipFile(input_aar, "r") as aar: + with open(output_proguard_file, "wb") as output: + ExtractEmbeddedProguard(aar, 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) + 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(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))) + else: + _Main(FLAGS.input_aar, FLAGS.output_proguard_file) + + +if __name__ == "__main__": + FLAGS(sys.argv) + app.run(main) diff --git a/tools/android/aar_embedded_proguard_extractor_test.py b/tools/android/aar_embedded_proguard_extractor_test.py new file mode 100644 index 00000000000000..457520f670c3f4 --- /dev/null +++ b/tools/android/aar_embedded_proguard_extractor_test.py @@ -0,0 +1,54 @@ +# 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()) + + +if __name__ == "__main__": + unittest.main()