Skip to content

Commit

Permalink
Export proguard specs from aar_import
Browse files Browse the repository at this point in the history
**Background**
#3778

proguard specs from the `aar_import` rule do not get bubbled up to `android_binary`. In this PR, I wire up a `ProguardSpecProvider` from this rule that exports the `proguard.txt` within an AAR if it exists and any transitive proguard specs from the `exports` attribute.

**Changes**
* Add an `aar_embedded_proguard_extractor` script to extract `proguard.txt` from an AAR if it exists otherwise generate an empty proguard specs file
* In AarImport, wire up the proguard extractor action and export results through a `ProguardSpecProvider`.

Once this lands, the android rules would need to be bumped.

**Test Plan**
* Added tests for the extraction python script
* Added tests for the `aar_import` rule changes

Closes #12749.

PiperOrigin-RevId: 359667674
  • Loading branch information
benjaminRomano authored and philwo committed Feb 26, 2021
1 parent f917dc1 commit c8c0d94
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 0 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -239,6 +243,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()))
Expand All @@ -256,6 +261,46 @@ private static NestedSet<Artifact> 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<Artifact> extractProguardSpecs(RuleContext ruleContext, Artifact aar) {

NestedSet<Artifact> proguardSpecs =
new ProguardLibrary(ruleContext).collectProguardSpecs(ImmutableSet.of("deps", "exports"));

Artifact proguardSpecArtifact = createAarArtifact(ruleContext, PROGUARD_SPEC);

ruleContext.registerAction(
createAarEmbeddedProguardExtractorActions(ruleContext, aar, proguardSpecArtifact));

NestedSetBuilder<Artifact> 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<Artifact> getBootclasspath(RuleContext ruleContext) {
if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) {
return NestedSetBuilder.<Artifact>stableOrder()
Expand Down
Expand Up @@ -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";
Expand Down Expand Up @@ -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())
Expand Down
Expand Up @@ -448,6 +448,7 @@ private ImmutableList<String> 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'])")
Expand Down
Expand Up @@ -41,6 +41,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;
Expand Down Expand Up @@ -156,6 +157,46 @@ public void setup() throws Exception {
getAnalysisMock().ccSupport().setupCcToolchainConfigForCpu(mockToolsConfig, "armeabi-v7a");
}

@Test
public void proguardSpecsProvided() throws Exception {
ConfiguredTarget binaryTarget = getConfiguredTarget("//a:bar");

NestedSet<Artifact> 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");
Expand Down
Expand Up @@ -26,6 +26,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",
Expand Down
15 changes: 15 additions & 0 deletions tools/android/BUILD
Expand Up @@ -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"],
Expand Down
10 changes: 10 additions & 0 deletions tools/android/BUILD.tools
Expand Up @@ -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"],
Expand Down
76 changes: 76 additions & 0 deletions 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)
54 changes: 54 additions & 0 deletions 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()

3 comments on commit c8c0d94

@cpsauer
Copy link
Contributor

@cpsauer cpsauer commented on c8c0d94 Jul 8, 2021

Choose a reason for hiding this comment

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

@benjaminRomano and @philwo, any chance this could get cherrypicked into 4.2, or is it too tightly coupled?

@philwo
Copy link
Member

@philwo philwo commented on c8c0d94 Jul 9, 2021

Choose a reason for hiding this comment

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

@cpsauer Can you add a comment to the 4.2 release issue so that we don't miss this request? The release manager will have a look whether it can be cherry-picked or not then. 😊

@cpsauer
Copy link
Contributor

@cpsauer cpsauer commented on c8c0d94 Jul 9, 2021

Choose a reason for hiding this comment

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

Donezo. (But figured I should ask you two, first.)

Thanks, both of you, for all you do!

Please sign in to comment.