/* * 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()); } } }