blob: 6f6848f8fc34473b9b86c2ef553406c3c5fae78b [file] [log] [blame]
/*
* Copyright (C) 2014 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.tasks;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.build.gradle.internal.TaskManager;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.scope.ConventionMappingHelper;
import com.android.build.gradle.internal.scope.GlobalScope;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.tasks.AbstractAndroidCompile;
import com.android.build.gradle.internal.tasks.FileSupplier;
import com.android.build.gradle.internal.variant.ApplicationVariantData;
import com.android.build.gradle.tasks.factory.AbstractCompilesUtil;
import com.android.builder.core.AndroidBuilder;
import com.android.builder.tasks.Job;
import com.android.builder.tasks.JobContext;
import com.android.builder.tasks.Task;
import com.android.ide.common.process.LoggedProcessOutputHandler;
import com.android.ide.common.process.ProcessException;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.repository.FullRevision;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.ParallelizableTask;
import org.gradle.api.tasks.TaskAction;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Jack task.
*/
@ParallelizableTask
public class JackTask extends AbstractAndroidCompile
implements FileSupplier, BinaryFileProviderTask, JavaResourcesProvider {
public static final FullRevision JACK_MIN_REV = new FullRevision(21, 1, 0);
private AndroidBuilder androidBuilder;
private boolean isVerbose;
private boolean isDebugLog;
private Collection<File> packagedLibraries;
private Collection<File> proguardFiles;
private Collection<File> jarJarRuleFiles;
private boolean debug;
private File tempFolder;
private File jackFile;
private File javaResourcesFolder;
private File mappingFile;
private boolean multiDexEnabled;
private int minSdkVersion;
private String javaMaxHeapSize;
private File incrementalDir;
@Override
@TaskAction
public void compile() {
final Job<Void> job = new Job<Void>(getName(), new Task<Void>() {
@Override
public void run(@NonNull Job<Void> job, @NonNull JobContext<Void> context)
throws IOException {
try {
JackTask.this.doMinification();
} catch (ProcessException e) {
throw new IOException(e);
}
}
});
try {
SimpleWorkQueue.push(job);
// wait for the task completion.
job.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
private void doMinification() throws ProcessException, IOException {
if (System.getenv("USE_JACK_API") != null) {
androidBuilder.convertByteCodeUsingJackApis(
getDestinationDir(),
getJackFile(),
getClasspath().getFiles(),
getPackagedLibraries(),
getSource().getFiles(),
getProguardFiles(),
getMappingFile(),
getJarJarRuleFiles(),
getIncrementalDir(),
getJavaResourcesFolder(),
isMultiDexEnabled(),
getMinSdkVersion());
} else {
// no incremental support through command line so far.
androidBuilder.convertByteCodeWithJack(
getDestinationDir(),
getJackFile(),
computeBootClasspath(),
getPackagedLibraries(),
computeEcjOptionFile(),
getProguardFiles(),
getMappingFile(),
getJarJarRuleFiles(),
isMultiDexEnabled(),
getMinSdkVersion(),
isDebugLog,
getJavaMaxHeapSize(),
new LoggedProcessOutputHandler(androidBuilder.getLogger()));
}
}
private File computeEcjOptionFile() throws IOException {
File folder = getTempFolder();
//noinspection ResultOfMethodCallIgnored
folder.mkdirs();
File file = new File(folder, "ecj-options.txt");
StringBuilder sb = new StringBuilder();
for (File sourceFile : getSource().getFiles()) {
sb.append(sourceFile.getAbsolutePath()).append("\n");
}
//noinspection ResultOfMethodCallIgnored
file.getParentFile().mkdirs();
Files.write(sb.toString(), file, Charsets.UTF_8);
return file;
}
private String computeBootClasspath() {
return Joiner.on(':').join(
Iterables.transform(getClasspath().getFiles(), GET_ABSOLUTE_PATH));
}
private static final Function<File, String> GET_ABSOLUTE_PATH = new Function<File, String>() {
@Override
public String apply(File file) {
return file.getAbsolutePath();
}
};
@InputFile
public File getJackExe() {
return new File(
androidBuilder.getTargetInfo().getBuildTools().getPath(BuildToolInfo.PathId.JACK));
}
public AndroidBuilder getAndroidBuilder() {
return androidBuilder;
}
public void setAndroidBuilder(AndroidBuilder androidBuilder) {
this.androidBuilder = androidBuilder;
}
public boolean getIsVerbose() {
return isVerbose;
}
public void setIsVerbose(boolean isVerbose) {
this.isVerbose = isVerbose;
}
public boolean getIsDebugLog() {
return isDebugLog;
}
public void setIsDebugLog(boolean isDebugLog) {
this.isDebugLog = isDebugLog;
}
@InputFiles
public Collection<File> getPackagedLibraries() {
return packagedLibraries;
}
public void setPackagedLibraries(Collection<File> packagedLibraries) {
this.packagedLibraries = packagedLibraries;
}
@InputFiles
@Optional
public Collection<File> getProguardFiles() {
return proguardFiles;
}
public void setProguardFiles(Collection<File> proguardFiles) {
this.proguardFiles = proguardFiles;
}
@InputFiles
@Optional
public Collection<File> getJarJarRuleFiles() {
return jarJarRuleFiles;
}
public void setJarJarRuleFiles(Collection<File> jarJarRuleFiles) {
this.jarJarRuleFiles = jarJarRuleFiles;
}
@Input
public boolean getDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public File getTempFolder() {
return tempFolder;
}
public void setTempFolder(File tempFolder) {
this.tempFolder = tempFolder;
}
@OutputFile
public File getJackFile() {
return jackFile;
}
public void setJackFile(File jackFile) {
this.jackFile = jackFile;
}
@OutputFile
@Optional
public File getMappingFile() {
return mappingFile;
}
public void setMappingFile(File mappingFile) {
this.mappingFile = mappingFile;
}
@Input
public boolean isMultiDexEnabled() {
return multiDexEnabled;
}
public void setMultiDexEnabled(boolean multiDexEnabled) {
this.multiDexEnabled = multiDexEnabled;
}
@Input
public int getMinSdkVersion() {
return minSdkVersion;
}
public void setMinSdkVersion(int minSdkVersion) {
this.minSdkVersion = minSdkVersion;
}
@Input
@Optional
public String getJavaMaxHeapSize() {
return javaMaxHeapSize;
}
public void setJavaMaxHeapSize(String javaMaxHeapSize) {
this.javaMaxHeapSize = javaMaxHeapSize;
}
@Input
@Optional
public File getIncrementalDir() {
return incrementalDir;
}
public void setIncrementalDir(File incrementalDir) {
this.incrementalDir = incrementalDir;
}
@Input
@Optional
public File getJavaResourcesFolder() {
return javaResourcesFolder;
}
public void setJavaResourcesFolder(File javaResourcesFolder) {
this.javaResourcesFolder = javaResourcesFolder;
}
@NonNull
@Override
public ImmutableList<JavaResourcesLocation> getJavaResourcesLocations() {
return ImmutableList.of(
new JavaResourcesLocation(Type.FOLDER, getDestinationDir()));
}
@Override
@NonNull
public BinaryFileProviderTask.Artifact getArtifact() {
return new BinaryFileProviderTask.Artifact(
BinaryFileProviderTask.BinaryArtifactType.JACK,
getJackFile());
}
// ----- FileSupplierTask ----
@NonNull
@Override
public org.gradle.api.Task getTask() {
return this;
}
@Override
public File get() {
return getMappingFile();
}
public static class ConfigAction implements TaskConfigAction<JackTask> {
private final VariantScope scope;
private final boolean isVerbose;
private final boolean isDebugLog;
public ConfigAction(VariantScope scope, boolean isVerbose, boolean isDebugLog) {
this.scope = scope;
this.isVerbose = isVerbose;
this.isDebugLog = isDebugLog;
}
@Override
public String getName() {
return scope.getTaskName("compile", "JavaWithJack");
}
@Override
public Class<JackTask> getType() {
return JackTask.class;
}
@Override
public void execute(JackTask jackTask) {
jackTask.setIsVerbose(isVerbose);
jackTask.setIsDebugLog(isDebugLog);
GlobalScope globalScope = scope.getGlobalScope();
jackTask.androidBuilder = globalScope.getAndroidBuilder();
jackTask.setJavaMaxHeapSize(
globalScope.getExtension().getDexOptions().getJavaMaxHeapSize());
jackTask.setSource(scope.getVariantData().getJavaSources());
final GradleVariantConfiguration config = scope.getVariantData().getVariantConfiguration();
jackTask.setMultiDexEnabled(config.isMultiDexEnabled());
jackTask.setMinSdkVersion(config.getMinSdkVersion().getApiLevel());
jackTask.incrementalDir = scope.getJackIncrementalDir();
// if the tested variant is an app, add its classpath. For the libraries,
// it's done automatically since the classpath includes the library output as a normal
// dependency.
if (scope.getTestedVariantData() instanceof ApplicationVariantData) {
ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() {
@Override
public FileCollection call() throws Exception {
Project project = scope.getGlobalScope().getProject();
return project.fileTree(scope.getJillRuntimeLibrariesDir()).plus(
project.fileTree(
scope.getTestedVariantData().getScope()
.getJillRuntimeLibrariesDir())).plus(
project.fileTree(
scope.getTestedVariantData().getScope().getJackClassesZip()
));
}
});
} else {
ConventionMappingHelper.map(jackTask, "classpath", new Callable<FileCollection>() {
@Override
public FileCollection call() throws Exception {
return scope.getGlobalScope().getProject().fileTree(
scope.getJillRuntimeLibrariesDir());
}
});
}
ConventionMappingHelper.map(jackTask, "packagedLibraries", new Callable<Collection<File>>() {
@Override
public Collection<File> call() throws Exception {
return scope.getGlobalScope().getProject()
.fileTree(scope.getJillPackagedLibrariesDir()).getFiles();
}
});
jackTask.setDestinationDir(scope.getJackDestinationDir());
jackTask.setJackFile(scope.getJackClassesZip());
jackTask.setTempFolder(new File(scope.getGlobalScope().getIntermediatesDir(),
"/tmp/jack/" + scope.getVariantConfiguration().getDirName()));
jackTask.setJavaResourcesFolder(scope.getJavaResourcesDestinationDir());
scope.setJavaResourcesProvider(jackTask);
if (config.isMinifyEnabled()) {
ConventionMappingHelper.map(jackTask, "proguardFiles", new Callable<List<File>>() {
@Override
public List<File> call() throws Exception {
// since all the output use the same resources, we can use the first output
// to query for a proguard file.
File sdkDir = scope.getGlobalScope().getSdkHandler().getAndCheckSdkFolder();
File defaultProguardFile = new File(sdkDir,
SdkConstants.FD_TOOLS + File.separatorChar
+ SdkConstants.FD_PROGUARD + File.separatorChar
+ TaskManager.DEFAULT_PROGUARD_CONFIG_FILE);
List<File> proguardFiles = config.getProguardFiles(true /*includeLibs*/,
ImmutableList.of(defaultProguardFile));
File proguardResFile = scope.getProcessAndroidResourcesProguardOutputFile();
proguardFiles.add(proguardResFile);
// for tested app, we only care about their aapt config since the base
// configs are the same files anyway.
if (scope.getTestedVariantData() != null) {
proguardResFile = scope.getTestedVariantData().getScope()
.getProcessAndroidResourcesProguardOutputFile();
proguardFiles.add(proguardResFile);
}
return proguardFiles;
}
});
jackTask.mappingFile = new File(scope.getProguardOutputFolder(), "mapping.txt");
}
ConventionMappingHelper.map(jackTask, "jarJarRuleFiles", new Callable<List<File>>() {
@Override
public List<File> call() throws Exception {
List<File> jarJarRuleFiles = Lists.newArrayListWithCapacity(
config.getJarJarRuleFiles().size());
Project project = scope.getGlobalScope().getProject();
for (File file: config.getJarJarRuleFiles()) {
jarJarRuleFiles.add(project.file(file));
}
return jarJarRuleFiles;
}
});
AbstractCompilesUtil.configureLanguageLevel(
jackTask,
scope.getGlobalScope().getExtension().getCompileOptions(),
scope.getGlobalScope().getExtension().getCompileSdkVersion()
);
scope.getVariantData().jackTask = jackTask;
scope.getVariantData().javaCompilerTask = jackTask;
scope.getVariantData().mappingFileProviderTask = jackTask;
scope.getVariantData().binayFileProviderTask = jackTask;
}
}
}