Skip to content

Commit 017e8e4

Browse files
committed
Export ProGuard specs from java_import.
Background: JAR files can bundle ProGuard specs under `META-INF/proguard/` [See https://developer.android.com/studio/build/shrink-code] Problem: Bazel previously erroniously ignored these ProGuard specs, leading to failures with, for example, androidx.annotation.Keep. Bad times. There was previously a parallel issue with aar_import. [Fixed in #12749] Solution: This change causes the previously ignored, embedded proguards to be extracted, validated, and then bubbled up correctly via the ProguardSpecProvider. There's also a minor fix to aar_import, adding proguard validation and slightly simplifying the resulting code. For reasoning behind why library proguards should be validated, see the module docstring of proguard_whitelister.py Remaining issues: JAR files brought down from Maven via rules_jvm_external bypass java_import in favor of rolling their own jvm_import, since java_import apparently been broken for Kotlin for years. That'll need a subsequent fix, since this only fixes java_import. For context on the Kotlin breakage, see #4549. For the status on fixes in rules_jvm_external, see bazel-contrib/rules_jvm_external#672
1 parent e33b54e commit 017e8e4

File tree

7 files changed

+118
-35
lines changed

7 files changed

+118
-35
lines changed

src/main/java/com/google/devtools/build/lib/rules/android/AarImport.java

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -256,44 +256,22 @@ private static NestedSet<Artifact> getCompileTimeJarsFromCollection(
256256
return isDirect ? provider.getDirectCompileTimeJars() : provider.getTransitiveCompileTimeJars();
257257
}
258258

259-
/**
260-
* Collect Proguard Specs from transitives and proguard.txt if it exists in the AAR file. In the
261-
* case the proguard.txt file does exists, we need to extract it from the AAR file
262-
*/
259+
/* Collects transitive and local ProGuard specs, including any embedded in the AAR. */
263260
private NestedSet<Artifact> extractProguardSpecs(RuleContext ruleContext, Artifact aar) {
264-
265-
NestedSet<Artifact> proguardSpecs =
266-
new ProguardLibrary(ruleContext).collectProguardSpecs(ImmutableSet.of("deps", "exports"));
267-
268-
Artifact proguardSpecArtifact = createAarArtifact(ruleContext, PROGUARD_SPEC);
269-
261+
Artifact extractedSpec = createAarArtifact(ruleContext, PROGUARD_SPEC);
270262
ruleContext.registerAction(
271-
createAarEmbeddedProguardExtractorActions(ruleContext, aar, proguardSpecArtifact));
272-
273-
NestedSetBuilder<Artifact> builder = NestedSetBuilder.naiveLinkOrder();
274-
return builder.addTransitive(proguardSpecs).add(proguardSpecArtifact).build();
275-
}
276-
277-
/**
278-
* Creates action to extract embedded Proguard.txt from an AAR. If the file is not found, an empty
279-
* file will be created
280-
*/
281-
private static SpawnAction createAarEmbeddedProguardExtractorActions(
282-
RuleContext ruleContext, Artifact aar, Artifact proguardSpecArtifact) {
283-
return new SpawnAction.Builder()
284-
.useDefaultShellEnvironment()
285-
.setExecutable(
286-
ruleContext.getExecutablePrerequisite(AarImportBaseRule.AAR_EMBEDDED_PROGUARD_EXTACTOR))
263+
new SpawnAction.Builder().useDefaultShellEnvironment()
264+
.setExecutable(ruleContext.getExecutablePrerequisite(
265+
AarImportBaseRule.AAR_EMBEDDED_PROGUARD_EXTACTOR))
287266
.setMnemonic("AarEmbeddedProguardExtractor")
288267
.setProgressMessage("Extracting proguard.txt from %s", aar.getFilename())
289268
.addInput(aar)
290-
.addOutput(proguardSpecArtifact)
291-
.addCommandLine(
292-
CustomCommandLine.builder()
293-
.addExecPath("--input_aar", aar)
294-
.addExecPath("--output_proguard_file", proguardSpecArtifact)
295-
.build())
296-
.build(ruleContext);
269+
.addOutput(extractedSpec)
270+
.addCommandLine(CustomCommandLine.builder()
271+
.addExecPath("--input_aar", aar)
272+
.addExecPath("--output_proguard_file", extractedSpec).build())
273+
.build(ruleContext));
274+
return new ProguardLibrary(ruleContext).collectProguardSpecs(extractedSpec);
297275
}
298276

299277
private NestedSet<Artifact> getBootclasspath(RuleContext ruleContext) {

src/main/java/com/google/devtools/build/lib/rules/java/JavaImport.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.google.common.collect.ImmutableMap;
2020
import com.google.devtools.build.lib.actions.Artifact;
2121
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
22+
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
23+
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
2224
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
2325
import com.google.devtools.build.lib.analysis.FileProvider;
2426
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
@@ -121,7 +123,7 @@ public ConfiguredTarget create(RuleContext ruleContext)
121123
.build());
122124
}
123125

124-
NestedSet<Artifact> proguardSpecs = new ProguardLibrary(ruleContext).collectProguardSpecs();
126+
NestedSet<Artifact> proguardSpecs = collectProguardSpecs(ruleContext, jars);
125127

126128
JavaRuleOutputJarsProvider ruleOutputJarsProvider = ruleOutputJarsProviderBuilder.build();
127129
JavaSourceJarsProvider sourceJarsProvider =
@@ -219,4 +221,23 @@ private ImmutableList<Artifact> processWithIjarIfNeeded(
219221
}
220222
return interfaceJarsBuilder.build();
221223
}
224+
225+
/* Collects transitive and local ProGuard specs, including any embedded in the JARs. */
226+
private NestedSet<Artifact> collectProguardSpecs(
227+
RuleContext ruleContext, ImmutableList<Artifact> jars) {
228+
Artifact extractedSpec = ruleContext.getUniqueDirectoryArtifact(
229+
"_jar_import", "proguard.txt", ruleContext.getBinOrGenfilesDirectory());
230+
ruleContext.registerAction(
231+
new SpawnAction.Builder().useDefaultShellEnvironment()
232+
.setMnemonic("JarEmbeddedProguardExtractor")
233+
.setProgressMessage("Extracting ProGuard specs from JARs in %s",
234+
ruleContext.getTarget().getLabel())
235+
.setExecutable(ruleContext.getExecutablePrerequisite("$jar_embedded_proguard_extractor"))
236+
.addCommandLine(
237+
CustomCommandLine.builder().addExecPaths(jars).addExecPath(extractedSpec).build())
238+
.addInputs(jars)
239+
.addOutput(extractedSpec)
240+
.build(ruleContext));
241+
return new ProguardLibrary(ruleContext).collectProguardSpecs(extractedSpec);
242+
}
222243
}

src/main/java/com/google/devtools/build/lib/rules/java/JavaImportBaseRule.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
2222

2323
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
24+
import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
2425
import com.google.devtools.build.lib.analysis.RuleDefinition;
2526
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
2627
import com.google.devtools.build.lib.packages.RuleClass;
@@ -63,6 +64,11 @@ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment envi
6364
.orderIndependent()
6465
.nonconfigurable(
6566
"used in Attribute.validityPredicate implementations (loading time)"))
67+
.add(
68+
attr("$jar_embedded_proguard_extractor", LABEL)
69+
.cfg(ExecutionTransitionFactory.create())
70+
.exec()
71+
.value(environment.getToolsLabel("//tools/jdk:jar_embedded_proguard_extractor")))
6672
.advertiseStarlarkProvider(StarlarkProviderIdentifier.forKey(JavaInfo.PROVIDER.getKey()))
6773
.build();
6874
}

src/main/java/com/google/devtools/build/lib/rules/java/ProguardLibrary.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.devtools.build.lib.collect.nestedset.Order;
2727
import com.google.devtools.build.lib.packages.BuildType;
2828
import java.util.Collection;
29+
import java.util.Collections;
2930

3031
/**
3132
* Helpers for implementing rules which export Proguard specs.
@@ -51,19 +52,47 @@ public NestedSet<Artifact> collectProguardSpecs() {
5152
return collectProguardSpecs(DEPENDENCY_ATTRIBUTES);
5253
}
5354

55+
/**
56+
* Collects the validated proguard specs exported by this rule and its dependencies, adding an
57+
* additional local spec from outside of the normal attributes, e.g., embedded in a JAR or AAR.
58+
*/
59+
public NestedSet<Artifact> collectProguardSpecs(Artifact additionalLocalSpec) {
60+
return collectProguardSpecs(DEPENDENCY_ATTRIBUTES, additionalLocalSpec);
61+
}
62+
5463
/**
5564
* Collects the validated proguard specs exported by this rule and its dependencies through the
5665
* given attributes.
5766
*/
5867
public NestedSet<Artifact> collectProguardSpecs(Iterable<String> attributes) {
68+
return collectProguardSpecs(attributes, Collections.emptySet());
69+
}
70+
71+
/**
72+
* Collects the validated proguard specs exported by this rule and its dependencies through the
73+
* given attributes, adding an additional local spec from outside of the normal attributes, e.g.,
74+
* embedded in a JAR or AAR.
75+
*/
76+
public NestedSet<Artifact> collectProguardSpecs(
77+
Iterable<String> attributes, Artifact additionalLocalSpec) {
78+
return collectProguardSpecs(attributes, Collections.singleton(additionalLocalSpec));
79+
}
80+
81+
/**
82+
* Collects the validated proguard specs exported by this rule and its dependencies through the
83+
* given attributes, adding additional local specs from outside of the normal attributes, e.g.,
84+
* embedded in a JAR or AAR.
85+
*/
86+
public NestedSet<Artifact> collectProguardSpecs(
87+
Iterable<String> attributes, Iterable<Artifact> additionalLocalSpecs) {
5988
NestedSetBuilder<Artifact> specsBuilder = NestedSetBuilder.naiveLinkOrder();
6089

6190
for (String attribute : attributes) {
6291
specsBuilder.addTransitive(collectProguardSpecsFromAttribute(attribute));
6392
}
6493

6594
Collection<Artifact> localSpecs = collectLocalProguardSpecs();
66-
if (!localSpecs.isEmpty()) {
95+
if (!localSpecs.isEmpty() || additionalLocalSpecs.iterator().hasNext()) {
6796
// Pass our local proguard configs through the validator, which checks an allowlist.
6897
FilesToRunProvider proguardAllowlister =
6998
JavaToolchainProvider.from(ruleContext).getProguardAllowlister();
@@ -75,6 +104,9 @@ public NestedSet<Artifact> collectProguardSpecs(Iterable<String> attributes) {
75104
for (Artifact specToValidate : localSpecs) {
76105
specsBuilder.add(validateProguardSpec(ruleContext, proguardAllowlister, specToValidate));
77106
}
107+
for (Artifact specToValidate : additionalLocalSpecs) {
108+
specsBuilder.add(validateProguardSpec(ruleContext, proguardAllowlister, specToValidate));
109+
}
78110
}
79111

80112
return specsBuilder.build();

tools/jdk/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ filegroup(
2020
"jdk.BUILD",
2121
"local_java_repository.bzl",
2222
"nosystemjdk/README",
23+
"jar_embedded_proguard_extractor.py",
2324
"proguard_whitelister.py",
2425
"proguard_whitelister_test.py",
2526
"proguard_whitelister_test_input.pgcfg",

tools/jdk/BUILD.tools

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ filegroup(
419419
visibility = ["//tools:__pkg__"],
420420
)
421421

422+
py_binary(
423+
name = "jar_embedded_proguard_extractor",
424+
srcs = ["jar_embedded_proguard_extractor.py"],
425+
)
426+
422427
py_binary(
423428
name = "proguard_whitelister",
424429
srcs = [
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2022 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A tool for extracting proguard specs from JARs.
16+
17+
Usage:
18+
Takes positional arguments: input_jar1 input_jar2 <...> output_proguard_spec
19+
20+
Background:
21+
A JAR may contain proguard specs under /META-INF/proguard/
22+
[https://developer.android.com/studio/build/shrink-code]
23+
24+
This tool extracts them all into a single spec for easy usage."""
25+
26+
27+
import sys
28+
import zipfile
29+
30+
31+
if __name__ == "__main__":
32+
with open(sys.argv[-1], 'wb') as output_proguard_spec:
33+
for jar_path in sys.argv[1:-1]:
34+
with zipfile.ZipFile(jar_path) as jar:
35+
for entry in jar.namelist():
36+
if entry.startswith('META-INF/proguard'):
37+
# zip directories are empty and therefore ok to output
38+
output_proguard_spec.write(jar.read(entry))
39+
# gracefully handle any lack of trailing newline
40+
output_proguard_spec.write(b'\n')

0 commit comments

Comments
 (0)