// Copyright 2015 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.
package com.google.devtools.build.lib.rules.android;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.rules.java.JavaConfiguration;
import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaOptimizationMode;
import com.google.devtools.build.lib.rules.java.ProguardSpecProvider;
import javax.annotation.Nullable;
/**
* Common code for proguarding Android binaries.
*/
public class ProguardHelper {
static final String PROGUARD_SPECS = "proguard_specs";
@Immutable
static final class ProguardOutput {
private final Artifact outputJar;
@Nullable private final Artifact mapping;
ProguardOutput(Artifact outputJar, @Nullable Artifact mapping) {
this.outputJar = outputJar;
this.mapping = mapping;
}
public Artifact getOutputJar() {
return outputJar;
}
@Nullable
public Artifact getMapping() {
return mapping;
}
}
private ProguardHelper() {}
/**
* Retrieves the full set of proguard specs that should be applied to this binary, including the
* specs passed in, if Proguard should run on the given rule. {@link #createProguardAction}
* relies on this method returning an empty list if the given rule doesn't declare specs in
* --java_optimization_mode=legacy.
*
*
If Proguard shouldn't be applied, or the legacy link mode is used and there are no
* proguard_specs on this rule, an empty list will be returned, regardless of any given specs or
* specs from dependencies. {@link AndroidBinary#createAndroidBinary} relies on that behavior.
*/
public static ImmutableList collectTransitiveProguardSpecs(RuleContext ruleContext,
Artifact... specsToInclude) {
JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext);
if (optMode == JavaOptimizationMode.NOOP) {
return ImmutableList.of();
}
ImmutableList proguardSpecs =
ruleContext.attributes().has(PROGUARD_SPECS, BuildType.LABEL_LIST)
? ruleContext.getPrerequisiteArtifacts(PROGUARD_SPECS, Mode.TARGET).list()
: ImmutableList.of();
if (optMode == JavaOptimizationMode.LEGACY && proguardSpecs.isEmpty()) {
return ImmutableList.of();
}
// TODO(bazel-team): In modes except LEGACY verify that proguard specs don't include -dont...
// flags since those flags would override the desired optMode
ImmutableSortedSet.Builder builder =
ImmutableSortedSet.orderedBy(Artifact.EXEC_PATH_COMPARATOR)
.addAll(proguardSpecs)
.add(specsToInclude)
.addAll(ruleContext
.getPrerequisiteArtifacts(":extra_proguard_specs", Mode.TARGET)
.list());
for (ProguardSpecProvider dep :
ruleContext.getPrerequisites("deps", Mode.TARGET, ProguardSpecProvider.class)) {
builder.addAll(dep.getTransitiveProguardSpecs());
}
// Generate and include implicit Proguard spec for requested mode.
if (!optMode.getImplicitProguardDirectives().isEmpty()) {
Artifact implicitDirectives =
getProguardConfigArtifact(ruleContext, optMode.name().toLowerCase());
ruleContext.registerAction(
new FileWriteAction(
ruleContext.getActionOwner(),
implicitDirectives,
optMode.getImplicitProguardDirectives(),
/*executable*/ false));
builder.add(implicitDirectives);
}
return builder.build().asList();
}
/**
* Creates an action to run Proguard over the given {@code programJar} with various other given
* inputs to produce {@code proguardOutputJar}. If requested explicitly, or implicitly with
* --java_optimization_mode, the action also produces a mapping file (which shows what methods and
* classes in the output Jar correspond to which methods and classes in the input). The "pair"
* returned by this method indicates whether a mapping is being produced.
*
* See the Proguard manual for the meaning of the various artifacts in play.
*
* @param proguard Proguard executable to use
* @param proguardSpecs Proguard specification files to pass to Proguard
* @param proguardMapping optional mapping file for Proguard to apply
* @param libraryJars any other Jar files that the {@code programJar} will run against
* @param mappingRequested whether to ask Proguard to output a mapping file (a mapping will be
* produced anyway if --java_optimization_mode includes obfuscation)
* @param filesBuilder all artifacts produced by this rule will be added to this builder
*/
public static ProguardOutput createProguardAction(RuleContext ruleContext,
FilesToRunProvider proguard,
Artifact programJar,
ImmutableList proguardSpecs,
@Nullable Artifact proguardMapping,
NestedSet libraryJars,
Artifact proguardOutputJar,
boolean mappingRequested,
NestedSetBuilder filesBuilder) throws InterruptedException {
JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext);
Preconditions.checkArgument(optMode != JavaOptimizationMode.NOOP);
Preconditions.checkArgument(optMode != JavaOptimizationMode.LEGACY || !proguardSpecs.isEmpty());
Builder builder = new SpawnAction.Builder()
.addInput(programJar)
.addInputs(libraryJars)
.addInputs(proguardSpecs)
.addOutput(proguardOutputJar)
.setExecutable(proguard)
.setProgressMessage("Trimming binary with Proguard")
.setMnemonic("Proguard")
.addArgument("-injars")
.addArgument(programJar.getExecPathString());
for (Artifact libraryJar : libraryJars) {
builder.addArgument("-libraryjars")
.addArgument(libraryJar.getExecPathString());
}
filesBuilder.add(proguardOutputJar);
if (proguardMapping != null) {
builder.addInput(proguardMapping)
.addArgument("-applymapping")
.addArgument(proguardMapping.getExecPathString());
}
builder.addArgument("-outjars")
.addArgument(proguardOutputJar.getExecPathString());
for (Artifact proguardSpec : proguardSpecs) {
builder.addArgument("@" + proguardSpec.getExecPathString());
}
Artifact proguardOutputMap = null;
if (mappingRequested || optMode.alwaysGenerateOutputMapping()) {
// TODO(bazel-team): Verify that proguard spec files don't contain -printmapping directions
// which this -printmapping command line flag will override.
proguardOutputMap = ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.ANDROID_BINARY_PROGUARD_MAP);
builder.addOutput(proguardOutputMap)
.addArgument("-printmapping")
.addArgument(proguardOutputMap.getExecPathString());
filesBuilder.add(proguardOutputMap);
}
ruleContext.registerAction(builder.build(ruleContext));
return new ProguardOutput(proguardOutputJar, proguardOutputMap);
}
/**
* Returns an intermediate artifact used to run Proguard.
*/
public static Artifact getProguardConfigArtifact(RuleContext ruleContext, String prefix) {
// TODO(bazel-team): Remove the redundant inclusion of the rule name, as getUniqueDirectory
// includes the rulename as well.
return Preconditions.checkNotNull(ruleContext.getUniqueDirectoryArtifact(
"proguard",
Joiner.on("_").join(prefix, ruleContext.getLabel().getName(), "proguard.cfg"),
ruleContext.getBinOrGenfilesDirectory()));
}
private static JavaOptimizationMode getJavaOptimizationMode(RuleContext ruleContext) {
return ruleContext.getConfiguration().getFragment(JavaConfiguration.class)
.getJavaOptimizationMode();
}
}