blob: 5340a5c51f5006754030487744ff543d9f42236d [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.dsl.DexOptions;
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.BaseTask;
import com.android.builder.core.AndroidBuilder;
import com.android.ide.common.internal.LoggedErrorException;
import com.android.ide.common.internal.WaitableExecutor;
import com.android.ide.common.process.LoggedProcessOutputHandler;
import com.android.sdklib.repository.FullRevision;
import com.android.utils.FileUtils;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import org.gradle.api.Action;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.ParallelizableTask;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.incremental.IncrementalTaskInputs;
import org.gradle.api.tasks.incremental.InputFileDetails;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
@ParallelizableTask
public class JillTask extends BaseTask {
private Collection<File> inputLibs;
private File outputFolder;
private DexOptions dexOptions;
@TaskAction
public void taskAction(IncrementalTaskInputs taskInputs)
throws LoggedErrorException, InterruptedException, IOException {
FullRevision revision = getBuilder().getTargetInfo().getBuildTools().getRevision();
if (revision.compareTo(JackTask.JACK_MIN_REV) < 0) {
throw new RuntimeException(
"Jack requires Build Tools " + JackTask.JACK_MIN_REV.toString()
+ " or later");
}
final File outFolder = getOutputFolder();
// if we are not in incremental mode, then outOfDate will contain
// all th files, but first we need to delete the previous output
if (!taskInputs.isIncremental()) {
FileUtils.emptyFolder(outFolder);
}
final Set<String> hashs = Sets.newHashSet();
final WaitableExecutor<Void> executor = new WaitableExecutor<Void>();
final List<File> inputFileDetails = Lists.newArrayList();
final AndroidBuilder builder = getBuilder();
taskInputs.outOfDate(new Action<InputFileDetails>() {
@Override
public void execute(InputFileDetails change) {
inputFileDetails.add(change.getFile());
}
});
for (final File file : inputFileDetails) {
Callable<Void> action = new JillCallable(this, file, hashs, outFolder, builder);
executor.execute(action);
}
taskInputs.removed(new Action<InputFileDetails>() {
@Override
public void execute(InputFileDetails change) {
File jackFile = getJackFileName(outFolder, ((InputFileDetails) change).getFile());
//noinspection ResultOfMethodCallIgnored
jackFile.delete();
}
});
executor.waitForTasksWithQuickFail(false);
}
@Input
public String getBuildToolsVersion() {
return getBuildTools().getRevision().toString();
}
@InputFiles
public Collection<File> getInputLibs() {
return inputLibs;
}
public void setInputLibs(Collection<File> inputLibs) {
this.inputLibs = inputLibs;
}
@OutputDirectory
public File getOutputFolder() {
return outputFolder;
}
public void setOutputFolder(File outputFolder) {
this.outputFolder = outputFolder;
}
@Nested
public DexOptions getDexOptions() {
return dexOptions;
}
public void setDexOptions(DexOptions dexOptions) {
this.dexOptions = dexOptions;
}
private final class JillCallable implements Callable<Void> {
@NonNull
private final File fileToProcess;
@NonNull
private final Set<String> hashs;
@NonNull
private final com.android.builder.core.DexOptions options = getDexOptions();
@NonNull
private final File outFolder;
@NonNull
private final AndroidBuilder builder;
private JillCallable(JillTask enclosing, @NonNull File file, @NonNull Set<String> hashs,
@NonNull File outFolder, @NonNull AndroidBuilder builder) {
this.fileToProcess = file;
this.hashs = hashs;
this.outFolder = outFolder;
this.builder = builder;
}
@Override
public Void call() throws Exception {
// TODO remove once we can properly add a library as a dependency of its test.
String hash = getFileHash(fileToProcess);
synchronized (hashs) {
if (hashs.contains(hash)) {
return null;
}
hashs.add(hash);
}
//noinspection GroovyAssignabilityCheck
File jackFile = getJackFileName(outFolder, fileToProcess);
//noinspection GroovyAssignabilityCheck
builder.convertLibraryToJack(fileToProcess, jackFile, options,
new LoggedProcessOutputHandler(builder.getLogger()));
return null;
}
@NonNull
public final File getOutFolder() {
return outFolder;
}
}
/**
* Returns the hash of a file.
*
* @param file the file to hash
*/
private static String getFileHash(@NonNull File file) throws IOException {
HashCode hashCode = Files.hash(file, Hashing.sha1());
return hashCode.toString();
}
/**
* Returns a unique File for the converted library, even if there are 2 libraries with the same
* file names (but different paths)
*
* @param outFolder the output folder.
* @param inputFile the library
*/
@NonNull
public static File getJackFileName(@NonNull File outFolder, @NonNull File inputFile) {
// get the filename
String name = inputFile.getName();
// remove the extension
int pos = name.lastIndexOf('.');
if (pos != -1) {
name = name.substring(0, pos);
}
// add a hash of the original file path.
String input = inputFile.getAbsolutePath();
HashFunction hashFunction = Hashing.sha1();
HashCode hashCode = hashFunction.hashString(input, Charsets.UTF_16LE);
return new File(outFolder, name + "-" + hashCode.toString() + SdkConstants.DOT_JAR);
}
public static class RuntimeTaskConfigAction implements TaskConfigAction<JillTask> {
private final VariantScope variantScope;
// TODO: If task can be shared between variants, change to GlobalScope.
public RuntimeTaskConfigAction(VariantScope scope) {
this.variantScope = scope;
}
@Override
public String getName() {
return variantScope.getTaskName("jill", "RuntimeLibraries");
}
@Override
public Class<JillTask> getType() {
return JillTask.class;
}
@Override
public void execute(JillTask jillTask) {
final GlobalScope globalScope = variantScope.getGlobalScope();
final AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
jillTask.setAndroidBuilder(androidBuilder);
jillTask.setVariantName(variantScope.getVariantConfiguration().getFullName());
jillTask.setDexOptions(globalScope.getExtension().getDexOptions());
ConventionMappingHelper.map(jillTask, "inputLibs", new Callable<List<File>>() {
@Override
public List<File> call() throws Exception {
return androidBuilder.getBootClasspath();
}
});
jillTask.setOutputFolder(variantScope.getJillRuntimeLibrariesDir());
}
}
public static class PackagedConfigAction implements TaskConfigAction<JillTask> {
private final VariantScope variantScope;
public PackagedConfigAction(VariantScope scope) {
this.variantScope = scope;
}
@Override
public String getName() {
return variantScope.getTaskName("jill", "PackagedLibraries");
}
@Override
public Class<JillTask> getType() {
return JillTask.class;
}
@Override
public void execute(JillTask jillTask) {
final GlobalScope globalScope = variantScope.getGlobalScope();
final AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
jillTask.setAndroidBuilder(androidBuilder);
jillTask.setVariantName(variantScope.getVariantConfiguration().getFullName());
jillTask.setDexOptions(globalScope.getExtension().getDexOptions());
ConventionMappingHelper.map(jillTask, "inputLibs", new Callable<Set<File>>() {
@Override
public Set<File> call() throws Exception {
return androidBuilder.getPackagedJars(variantScope.getVariantConfiguration());
}
});
jillTask.setOutputFolder(variantScope.getJillPackagedLibrariesDir());
}
}
}