blob: 40b48dfd5e86c3e72c195d6fb9f072701cc41b74 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.build.gradle.internal.transforms;
import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
import static com.android.utils.FileUtils.mkdirs;
import static com.google.common.base.Preconditions.checkNotNull;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.Format;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.QualifiedContent.ContentType;
import com.android.build.api.transform.QualifiedContent.DefaultContentType;
import com.android.build.api.transform.QualifiedContent.Scope;
import com.android.build.api.transform.SecondaryFile;
import com.android.build.api.transform.TransformException;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.android.build.api.transform.TransformOutputProvider;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.build.gradle.internal.scope.GlobalScope;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.tasks.SimpleWorkQueue;
import com.android.builder.tasks.Job;
import com.android.builder.tasks.JobContext;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.SettableFuture;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.gradle.api.file.FileCollection;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import proguard.ClassPath;
/**
* ProGuard support as a transform
*/
public class ProGuardTransform extends BaseProguardAction {
private static final Logger LOG = Logging.getLogger(ProGuardTransform.class);
private final VariantScope variantScope;
private final File proguardOut;
private final File printMapping;
private final File dump;
private final File printSeeds;
private final File printUsage;
private final ImmutableList<File> secondaryFileOutputs;
private File testedMappingFile = null;
private FileCollection testMappingConfiguration = null;
public ProGuardTransform(@NonNull VariantScope variantScope) {
super(variantScope);
this.variantScope = variantScope;
GlobalScope globalScope = variantScope.getGlobalScope();
proguardOut = new File(Joiner.on(File.separatorChar).join(
String.valueOf(globalScope.getBuildDir()),
FD_OUTPUTS,
"mapping",
variantScope.getVariantConfiguration().getDirName()));
printMapping = new File(proguardOut, "mapping.txt");
dump = new File(proguardOut, "dump.txt");
printSeeds = new File(proguardOut, "seeds.txt");
printUsage = new File(proguardOut, "usage.txt");
secondaryFileOutputs = ImmutableList.of(printMapping, dump, printSeeds, printUsage);
}
@Nullable
public File getMappingFile() {
return printMapping;
}
public void applyTestedMapping(@Nullable File testedMappingFile) {
this.testedMappingFile = testedMappingFile;
}
public void applyTestedMapping(@Nullable FileCollection testMappingConfiguration) {
this.testMappingConfiguration = testMappingConfiguration;
}
@NonNull
@Override
public String getName() {
return "proguard";
}
@NonNull
@Override
public Set<ContentType> getInputTypes() {
return TransformManager.CONTENT_JARS;
}
@NonNull
@Override
public Collection<SecondaryFile> getSecondaryFiles() {
final List<SecondaryFile> files = Lists.newArrayList();
if (testedMappingFile != null && testedMappingFile.isFile()) {
files.add(SecondaryFile.nonIncremental(testedMappingFile));
} else if (testMappingConfiguration != null) {
files.add(SecondaryFile.nonIncremental(testMappingConfiguration));
}
// the config files
files.add(SecondaryFile.nonIncremental(getAllConfigurationFiles()));
return files;
}
@NonNull
@Override
public Collection<File> getSecondaryFileOutputs() {
return secondaryFileOutputs;
}
@NonNull
@Override
public Map<String, Object> getParameterInputs() {
return ImmutableMap.of(
"shrink", configuration.shrink,
"obfuscate", configuration.obfuscate,
"optimize", configuration.optimize);
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public boolean isCacheable() {
return true;
}
@Override
public void transform(@NonNull final TransformInvocation invocation) throws TransformException {
// only run one minification at a time (across projects)
SettableFuture<TransformOutputProvider> resultFuture = SettableFuture.create();
final Job<Void> job = new Job<>(getName(),
new com.android.builder.tasks.Task<Void>() {
@Override
public void run(@NonNull Job<Void> job,
@NonNull JobContext<Void> context) throws IOException {
doMinification(
invocation.getInputs(),
invocation.getReferencedInputs(),
invocation.getOutputProvider());
}
@Override
public void finished() {
resultFuture.set(invocation.getOutputProvider());
}
@Override
public void error(Throwable e) {
resultFuture.setException(e);
}
}, resultFuture);
try {
SimpleWorkQueue.push(job);
// wait for the task completion.
try {
job.awaitRethrowExceptions();
} catch (ExecutionException e) {
throw new RuntimeException("Job failed, see logs for details", e.getCause());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
private void doMinification(
@NonNull Collection<TransformInput> inputs,
@NonNull Collection<TransformInput> referencedInputs,
@Nullable TransformOutputProvider output)
throws IOException {
try {
checkNotNull(output, "Missing output object for transform " + getName());
Set<ContentType> outputTypes = getOutputTypes();
Set<? super Scope> scopes = getScopes();
File outFile =
output.getContentLocation(
"combined_res_and_classes", outputTypes, scopes, Format.JAR);
mkdirs(outFile.getParentFile());
GlobalScope globalScope = variantScope.getGlobalScope();
// set the mapping file if there is one.
File testedMappingFile = computeMappingFile();
if (testedMappingFile != null) {
applyMapping(testedMappingFile);
}
// --- InJars / LibraryJars ---
addInputsToConfiguration(inputs, false);
addInputsToConfiguration(referencedInputs, true);
// libraryJars: the runtime jars, with all optional libraries.
for (File runtimeJar : globalScope.getAndroidBuilder().getBootClasspath(true)) {
libraryJar(runtimeJar);
}
// --- Out files ---
outJar(outFile);
// proguard doesn't verify that the seed/mapping/usage folders exist and will fail
// if they don't so create them.
mkdirs(proguardOut);
for (File configFile : getAllConfigurationFiles()) {
LOG.info("Applying ProGuard configuration file {}", configFile);
applyConfigurationFile(configFile);
}
configuration.printMapping = printMapping;
configuration.dump = dump;
configuration.printSeeds = printSeeds;
configuration.printUsage = printUsage;
forceprocessing();
runProguard();
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
throw new IOException(e);
}
}
private void addInputsToConfiguration(
@NonNull Collection<TransformInput> inputs,
boolean referencedOnly) {
ClassPath classPath;
List<String> baseFilter;
if (referencedOnly) {
classPath = configuration.libraryJars;
baseFilter = JAR_FILTER;
} else {
classPath = configuration.programJars;
baseFilter = null;
}
for (TransformInput transformInput : inputs) {
for (JarInput jarInput : transformInput.getJarInputs()) {
handleQualifiedContent(classPath, jarInput, baseFilter);
}
for (DirectoryInput directoryInput : transformInput.getDirectoryInputs()) {
handleQualifiedContent(classPath, directoryInput, baseFilter);
}
}
}
private void handleQualifiedContent(
@NonNull ClassPath classPath,
@NonNull QualifiedContent content,
@Nullable List<String> baseFilter) {
List<String> filter = baseFilter;
if (!content.getContentTypes().contains(DefaultContentType.CLASSES)) {
// if the content is not meant to contain classes, we ignore them
// in case they are present.
ImmutableList.Builder<String> builder = ImmutableList.builder();
if (filter != null) {
builder.addAll(filter);
}
builder.add("!**.class");
filter = builder.build();
} else if (!content.getContentTypes().contains(DefaultContentType.RESOURCES)) {
// if the content is not meant to contain resources, we ignore them
// in case they are present (by accepting only classes.)
filter = ImmutableList.of("**.class");
}
inputJar(classPath, content.getFile(), filter);
}
@Nullable
private File computeMappingFile() {
if (testedMappingFile != null && testedMappingFile.isFile()) {
return testedMappingFile;
} else if (testMappingConfiguration != null && testMappingConfiguration.getSingleFile().isFile()) {
return testMappingConfiguration.getSingleFile();
}
return null;
}
}