blob: b5d2a696280308112230d5bbefc08f6d504eed8c [file] [log] [blame]
/*
* Copyright (C) 2012 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 static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.AIDL;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.internal.CombinedInput;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.tasks.IncrementalTask;
import com.android.build.gradle.internal.tasks.TaskInputHelper;
import com.android.builder.compiling.DependencyFileProcessor;
import com.android.builder.core.VariantConfiguration;
import com.android.builder.core.VariantType;
import com.android.builder.internal.incremental.DependencyData;
import com.android.builder.internal.incremental.DependencyDataStore;
import com.android.ide.common.internal.WaitableExecutor;
import com.android.ide.common.process.LoggedProcessOutputHandler;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessOutputHandler;
import com.android.ide.common.res2.FileStatus;
import com.android.utils.FileUtils;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileTree;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.util.PatternSet;
/** Task to compile aidl files. Supports incremental update. */
@CacheableTask
public class AidlCompile extends IncrementalTask {
private static final String DEPENDENCY_STORE = "dependency.store";
private static final PatternSet PATTERN_SET = new PatternSet().include("**/*.aidl");
private File sourceOutputDir;
@Nullable
private File packagedDir;
@Nullable
private Collection<String> packageWhitelist;
private Supplier<Collection<File>> sourceDirs;
private FileCollection importDirs;
@Input
public String getBuildToolsVersion() {
return getBuildTools().getRevision().toString();
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public FileTree getSourceFiles() {
// this is because aidl may be in the same folder as Java and we want to restrict to
// .aidl files and not java files.
return getProject().files(sourceDirs.get()).getAsFileTree().matching(PATTERN_SET);
}
private static class DepFileProcessor implements DependencyFileProcessor {
List<DependencyData> dependencyDataList =
Collections.synchronizedList(Lists.newArrayList());
List<DependencyData> getDependencyDataList() {
return dependencyDataList;
}
@Override
public DependencyData processFile(@NonNull File dependencyFile) throws IOException {
DependencyData data = DependencyData.parseDependencyFile(dependencyFile);
if (data != null) {
dependencyDataList.add(data);
}
return data;
}
}
@Override
@Internal
protected boolean isIncremental() {
// TODO fix once dep file parsing is resolved.
return false;
}
/**
* Action methods to compile all the files.
*
* <p>The method receives a {@link DependencyFileProcessor} to be used by the {@link
* com.android.builder.internal.compiler.SourceSearcher.SourceFileProcessor} during the
* compilation.
*
* @param dependencyFileProcessor a DependencyFileProcessor
*/
private void compileAllFiles(DependencyFileProcessor dependencyFileProcessor)
throws InterruptedException, ProcessException, IOException {
getBuilder().compileAllAidlFiles(
sourceDirs.get(),
getSourceOutputDir(),
getPackagedDir(),
getPackageWhitelist(),
getImportDirs().getFiles(),
dependencyFileProcessor,
new LoggedProcessOutputHandler(getILogger()));
}
/** Returns the import folders. */
@NonNull
@Internal
private List<File> getImportFolders() {
List<File> fullImportDir = Lists.newArrayList();
fullImportDir.addAll(getImportDirs().getFiles());
fullImportDir.addAll(sourceDirs.get());
return fullImportDir;
}
/**
* Compiles a single file.
*
* @param sourceFolder the file to compile.
* @param file the file to compile.
* @param importFolders the import folders.
* @param dependencyFileProcessor a DependencyFileProcessor
*/
private void compileSingleFile(
@NonNull File sourceFolder,
@NonNull File file,
@Nullable List<File> importFolders,
@NonNull DependencyFileProcessor dependencyFileProcessor,
@NonNull ProcessOutputHandler processOutputHandler)
throws InterruptedException, ProcessException, IOException {
getBuilder().compileAidlFile(
sourceFolder,
file,
getSourceOutputDir(),
getPackagedDir(),
getPackageWhitelist(),
Preconditions.checkNotNull(importFolders),
dependencyFileProcessor,
processOutputHandler);
}
@Override
protected void doFullTaskAction() throws IOException {
// this is full run, clean the previous output
File destinationDir = getSourceOutputDir();
File parcelableDir = getPackagedDir();
FileUtils.cleanOutputDir(destinationDir);
if (parcelableDir != null) {
FileUtils.cleanOutputDir(parcelableDir);
}
DepFileProcessor processor = new DepFileProcessor();
try {
compileAllFiles(processor);
} catch (Exception e) {
throw new RuntimeException(e);
}
List<DependencyData> dataList = processor.getDependencyDataList();
DependencyDataStore store = new DependencyDataStore();
store.addData(dataList);
try {
store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) throws IOException {
File incrementalData = new File(getIncrementalFolder(), DEPENDENCY_STORE);
DependencyDataStore store = new DependencyDataStore();
Multimap<String, DependencyData> inputMap;
try {
inputMap = store.loadFrom(incrementalData);
} catch (Exception ignored) {
FileUtils.delete(incrementalData);
getProject().getLogger().info(
"Failed to read dependency store: full task run!");
doFullTaskAction();
return;
}
final List<File> importFolders = getImportFolders();
final DepFileProcessor processor = new DepFileProcessor();
final ProcessOutputHandler processOutputHandler =
new LoggedProcessOutputHandler(getILogger());
// use an executor to parallelize the compilation of multiple files.
WaitableExecutor executor = WaitableExecutor.useGlobalSharedThreadPool();
Map<String, DependencyData> mainFileMap = store.getMainFileMap();
for (final Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
FileStatus status = entry.getValue();
switch (status) {
case NEW:
executor.execute(() -> {
File file = entry.getKey();
compileSingleFile(getSourceFolder(file), file, importFolders,
processor, processOutputHandler);
return null;
});
break;
case CHANGED:
Collection<DependencyData> impactedData =
inputMap.get(entry.getKey().getAbsolutePath());
if (impactedData != null) {
for (final DependencyData data : impactedData) {
executor.execute(() -> {
File file = new File(data.getMainFile());
compileSingleFile(getSourceFolder(file), file,
importFolders, processor, processOutputHandler);
return null;
});
}
}
break;
case REMOVED:
final DependencyData data2 = mainFileMap.get(entry.getKey().getAbsolutePath());
if (data2 != null) {
executor.execute(() -> {
cleanUpOutputFrom(data2);
return null;
});
store.remove(data2);
}
break;
}
}
try {
executor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
} catch (Throwable t) {
FileUtils.delete(incrementalData);
throw new RuntimeException(t);
}
// get all the update data for the recompiled objects
store.updateAll(processor.getDependencyDataList());
try {
store.saveTo(incrementalData);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getSourceFolder(@NonNull File file) {
File parentDir = file;
while ((parentDir = parentDir.getParentFile()) != null) {
if (sourceDirs.get().contains(parentDir)) {
return parentDir;
}
}
throw new IllegalArgumentException(String.format("File '%s' is not in a source dir", file));
}
private static void cleanUpOutputFrom(@NonNull DependencyData dependencyData)
throws IOException {
for (String output : dependencyData.getOutputFiles()) {
FileUtils.delete(new File(output));
}
for (String output : dependencyData.getSecondaryOutputFiles()) {
FileUtils.delete(new File(output));
}
}
@OutputDirectory
public File getSourceOutputDir() {
return sourceOutputDir;
}
public void setSourceOutputDir(File sourceOutputDir) {
this.sourceOutputDir = sourceOutputDir;
}
@OutputDirectory
@Optional
@Nullable
public File getPackagedDir() {
return packagedDir;
}
public void setPackagedDir(@Nullable File packagedDir) {
this.packagedDir = packagedDir;
}
@Input
@Optional
@Nullable
public Collection<String> getPackageWhitelist() {
return packageWhitelist;
}
public void setPackageWhitelist(@Nullable Collection<String> packageWhitelist) {
this.packageWhitelist = packageWhitelist;
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getImportDirs() {
return importDirs;
}
public static class ConfigAction implements TaskConfigAction<AidlCompile> {
@NonNull
VariantScope scope;
public ConfigAction(@NonNull VariantScope scope) {
this.scope = scope;
}
@Override
@NonNull
public String getName() {
return scope.getTaskName("compile", "Aidl");
}
@Override
@NonNull
public Class<AidlCompile> getType() {
return AidlCompile.class;
}
@Override
public void execute(@NonNull AidlCompile compileTask) {
final VariantConfiguration<?, ?, ?> variantConfiguration = scope
.getVariantConfiguration();
scope.getVariantData().aidlCompileTask = compileTask;
compileTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
compileTask.setVariantName(scope.getVariantConfiguration().getFullName());
compileTask.setIncrementalFolder(scope.getIncrementalDir(getName()));
compileTask.sourceDirs = TaskInputHelper
.bypassFileSupplier(variantConfiguration::getAidlSourceList);
compileTask.importDirs = scope.getArtifactFileCollection(
COMPILE_CLASSPATH, ALL, AIDL);
compileTask.setSourceOutputDir(scope.getAidlSourceOutputDir());
if (variantConfiguration.getType() == VariantType.LIBRARY) {
compileTask.setPackagedDir(scope.getPackagedAidlDir());
compileTask.setPackageWhitelist(
scope.getGlobalScope().getExtension().getAidlPackageWhiteList());
}
}
}
// Workaround for https://issuetracker.google.com/67418335
@Override
@Input
@NonNull
public String getCombinedInput() {
return new CombinedInput(super.getCombinedInput())
.add("packagedDir", getPackagedDir())
.toString();
}
}