blob: cf400918026bc6cbe5b1ff027561482a88516164 [file] [log] [blame]
/*
* Copyright (C) 2013 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.variant;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.ANDROID_RES;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH;
import static com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_BASE_CLASS_SOURCE_OUT;
import android.databinding.tool.LayoutXmlProcessor;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.OutputFile;
import com.android.build.gradle.BaseExtension;
import com.android.build.gradle.api.AndroidSourceSet;
import com.android.build.gradle.internal.TaskManager;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.dependency.VariantDependencies;
import com.android.build.gradle.internal.dsl.Splits;
import com.android.build.gradle.internal.dsl.VariantOutputFactory;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.build.gradle.internal.scope.GlobalScope;
import com.android.build.gradle.internal.scope.InternalArtifactType;
import com.android.build.gradle.internal.scope.MutableTaskContainer;
import com.android.build.gradle.internal.scope.OutputFactory;
import com.android.build.gradle.internal.scope.OutputScope;
import com.android.build.gradle.internal.scope.TaskContainer;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.scope.VariantScopeImpl;
import com.android.build.gradle.internal.tasks.factory.TaskFactoryUtils;
import com.android.build.gradle.options.BooleanOption;
import com.android.builder.core.VariantType;
import com.android.builder.model.SourceProvider;
import com.android.builder.profile.Recorder;
import com.android.ide.common.blame.MergingLog;
import com.android.ide.common.blame.SourceFile;
import com.android.utils.StringHelper;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.ConfigurableFileTree;
import org.gradle.api.file.Directory;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.logging.Logging;
import org.gradle.api.provider.Provider;
import org.gradle.api.resources.TextResource;
import org.gradle.api.tasks.Sync;
/** Base data about a variant. */
public abstract class BaseVariantData {
@NonNull
protected final TaskManager taskManager;
@NonNull
private final GradleVariantConfiguration variantConfiguration;
private VariantDependencies variantDependency;
// Needed for ModelBuilder. Should be removed once VariantScope can replace BaseVariantData.
@NonNull protected final VariantScope scope;
private ImmutableList<ConfigurableFileTree> defaultJavaSources;
private List<File> extraGeneratedSourceFolders = Lists.newArrayList();
private List<ConfigurableFileTree> extraGeneratedSourceFileTrees;
private List<ConfigurableFileTree> externalAptJavaOutputFileTrees;
private final ConfigurableFileCollection extraGeneratedResFolders;
private Map<Object, FileCollection> preJavacGeneratedBytecodeMap;
private FileCollection preJavacGeneratedBytecodeLatest;
private final ConfigurableFileCollection allPreJavacGeneratedBytecode;
private final ConfigurableFileCollection allPostJavacGeneratedBytecode;
private FileCollection rawAndroidResources = null;
private Set<String> densityFilters;
private Set<String> languageFilters;
private Set<String> abiFilters;
@Nullable
private LayoutXmlProcessor layoutXmlProcessor;
/**
* If true, variant outputs will be considered signed. Only set if you manually set the outputs
* to point to signed files built by other tasks.
*/
public boolean outputsAreSigned = false;
@NonNull private final OutputFactory outputFactory;
public VariantOutputFactory variantOutputFactory;
private final MultiOutputPolicy multiOutputPolicy;
private final MutableTaskContainer taskContainer;
public TextResource applicationIdTextResource;
public BaseVariantData(
@NonNull GlobalScope globalScope,
@NonNull TaskManager taskManager,
@NonNull GradleVariantConfiguration variantConfiguration,
@NonNull Recorder recorder) {
this.variantConfiguration = variantConfiguration;
this.taskManager = taskManager;
final Splits splits = globalScope.getExtension().getSplits();
boolean splitsEnabled =
splits.getDensity().isEnable()
|| splits.getAbi().isEnable()
|| splits.getLanguage().isEnable();
// eventually, this will require a more open ended comparison.
multiOutputPolicy =
(globalScope.getExtension().getGeneratePureSplits()
|| variantConfiguration.getType().isHybrid()) // == FEATURE
&& variantConfiguration.getMinSdkVersionValue() >= 21
? MultiOutputPolicy.SPLITS
: MultiOutputPolicy.MULTI_APK;
// warn the user if we are forced to ignore the generatePureSplits flag.
if (splitsEnabled
&& globalScope.getExtension().getGeneratePureSplits()
&& multiOutputPolicy != MultiOutputPolicy.SPLITS) {
Logging.getLogger(BaseVariantData.class).warn(
String.format("Variant %s, MinSdkVersion %s is too low (<21) "
+ "to support pure splits, reverting to full APKs",
variantConfiguration.getFullName(),
variantConfiguration.getMinSdkVersion().getApiLevel()));
}
final Project project = globalScope.getProject();
scope =
new VariantScopeImpl(
globalScope,
new TransformManager(
globalScope.getProject(),
globalScope.getErrorHandler(),
recorder),
this);
outputFactory = new OutputFactory(globalScope.getProjectBaseName(), variantConfiguration);
taskManager.configureScopeForNdk(scope);
// this must be created immediately since the variant API happens after the task that
// depends on this are created.
extraGeneratedResFolders = globalScope.getProject().files();
preJavacGeneratedBytecodeLatest = globalScope.getProject().files();
allPreJavacGeneratedBytecode = project.files();
allPostJavacGeneratedBytecode = project.files();
taskContainer = scope.getTaskContainer();
applicationIdTextResource = project.getResources().getText().fromString("");
}
@NonNull
public LayoutXmlProcessor getLayoutXmlProcessor() {
if (layoutXmlProcessor == null) {
File resourceBlameLogDir = scope.getResourceBlameLogDir();
final MergingLog mergingLog = new MergingLog(resourceBlameLogDir);
layoutXmlProcessor =
new LayoutXmlProcessor(
getVariantConfiguration().getOriginalApplicationId(),
taskManager
.getDataBindingBuilder()
.createJavaFileWriter(scope.getClassOutputForDataBinding()),
file -> {
SourceFile input = new SourceFile(file);
SourceFile original = mergingLog.find(input);
// merged log api returns the file back if original cannot be found.
// it is not what we want so we alter the response.
return original == input ? null : original.getSourceFile();
},
scope.getGlobalScope()
.getProjectOptions()
.get(BooleanOption.USE_ANDROID_X));
}
return layoutXmlProcessor;
}
@NonNull
public TaskContainer getTaskContainer() {
return taskContainer;
}
@NonNull
public OutputScope getOutputScope() {
return outputFactory.getOutput();
}
@NonNull
public OutputFactory getOutputFactory() {
return outputFactory;
}
@NonNull
public MultiOutputPolicy getMultiOutputPolicy() {
return multiOutputPolicy;
}
@NonNull
public GradleVariantConfiguration getVariantConfiguration() {
return variantConfiguration;
}
public void setVariantDependency(@NonNull VariantDependencies variantDependency) {
this.variantDependency = variantDependency;
}
@NonNull
public VariantDependencies getVariantDependency() {
return variantDependency;
}
@NonNull
public abstract String getDescription();
@NonNull
public String getApplicationId() {
return variantConfiguration.getApplicationId();
}
@NonNull
public VariantType getType() {
return variantConfiguration.getType();
}
@NonNull
public String getName() {
return variantConfiguration.getFullName();
}
@NonNull
public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
return StringHelper.appendCapitalized(prefix, variantConfiguration.getFullName(), suffix);
}
@NonNull
public List<File> getExtraGeneratedSourceFolders() {
return extraGeneratedSourceFolders;
}
@Nullable
public FileCollection getExtraGeneratedResFolders() {
return extraGeneratedResFolders;
}
@NonNull
public FileCollection getAllPreJavacGeneratedBytecode() {
return allPreJavacGeneratedBytecode;
}
@NonNull
public FileCollection getAllPostJavacGeneratedBytecode() {
return allPostJavacGeneratedBytecode;
}
@NonNull
public FileCollection getGeneratedBytecode(@Nullable Object generatorKey) {
if (generatorKey == null) {
return allPreJavacGeneratedBytecode;
}
FileCollection result = preJavacGeneratedBytecodeMap.get(generatorKey);
if (result == null) {
throw new RuntimeException("Bytecode generator key not found");
}
return result;
}
public void addJavaSourceFoldersToModel(@NonNull File generatedSourceFolder) {
extraGeneratedSourceFolders.add(generatedSourceFolder);
}
public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
Collections.addAll(extraGeneratedSourceFolders, generatedSourceFolders);
}
public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
extraGeneratedSourceFolders.addAll(generatedSourceFolders);
}
public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... generatedSourceFolders) {
registerJavaGeneratingTask(task, Arrays.asList(generatedSourceFolders));
}
public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
TaskFactoryUtils.dependsOn(taskContainer.getSourceGenTask(), task);
if (extraGeneratedSourceFileTrees == null) {
extraGeneratedSourceFileTrees = new ArrayList<>();
}
final Project project = scope.getGlobalScope().getProject();
for (File f : generatedSourceFolders) {
ConfigurableFileTree fileTree = project.fileTree(f).builtBy(task);
extraGeneratedSourceFileTrees.add(fileTree);
}
addJavaSourceFoldersToModel(generatedSourceFolders);
}
public void registerExternalAptJavaOutput(@NonNull ConfigurableFileTree folder) {
if (externalAptJavaOutputFileTrees == null) {
externalAptJavaOutputFileTrees = new ArrayList<>();
}
externalAptJavaOutputFileTrees.add(folder);
addJavaSourceFoldersToModel(folder.getDir());
}
public void registerGeneratedResFolders(@NonNull FileCollection folders) {
extraGeneratedResFolders.from(folders);
}
@Deprecated
public void registerResGeneratingTask(@NonNull Task task, @NonNull File... generatedResFolders) {
registerResGeneratingTask(task, Arrays.asList(generatedResFolders));
}
@Deprecated
public void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedResFolders) {
System.out.println(
"registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)");
final Project project = scope.getGlobalScope().getProject();
registerGeneratedResFolders(project.files(generatedResFolders).builtBy(task));
}
public Object registerPreJavacGeneratedBytecode(@NonNull FileCollection fileCollection) {
if (preJavacGeneratedBytecodeMap == null) {
preJavacGeneratedBytecodeMap = Maps.newHashMap();
}
// latest contains the generated bytecode up to now, so create a new key and put it in the
// map.
Object key = new Object();
preJavacGeneratedBytecodeMap.put(key, preJavacGeneratedBytecodeLatest);
// now create a new file collection that will contains the previous latest plus the new
// one
// and make this the latest
preJavacGeneratedBytecodeLatest = preJavacGeneratedBytecodeLatest.plus(fileCollection);
// also add the stable all-bytecode file collection. We need a stable collection for
// queries that request all the generated bytecode before the variant api is called.
allPreJavacGeneratedBytecode.from(fileCollection);
return key;
}
public void registerPostJavacGeneratedBytecode(@NonNull FileCollection fileCollection) {
allPostJavacGeneratedBytecode.from(fileCollection);
}
/**
* Calculates the filters for this variant. The filters can either be manually specified by
* the user within the build.gradle or can be automatically discovered using the variant
* specific folders.
*
* This method must be called before {@link #getFilters(OutputFile.FilterType)}.
*
* @param splits the splits configuration from the build.gradle.
*/
public void calculateFilters(Splits splits) {
densityFilters = getFilters(DiscoverableFilterType.DENSITY, splits);
languageFilters = getFilters(DiscoverableFilterType.LANGUAGE, splits);
abiFilters = getFilters(DiscoverableFilterType.ABI, splits);
}
/**
* Returns the filters values (as manually specified or automatically discovered) for a
* particular {@link com.android.build.OutputFile.FilterType}
* @param filterType the type of filter in question
* @return a possibly empty set of filter values.
* @throws IllegalStateException if {@link #calculateFilters(Splits)} has not been called prior
* to invoking this method.
*/
@NonNull
public Set<String> getFilters(OutputFile.FilterType filterType) {
if (densityFilters == null || languageFilters == null || abiFilters == null) {
throw new IllegalStateException("calculateFilters method not called");
}
switch(filterType) {
case DENSITY:
return densityFilters;
case LANGUAGE:
return languageFilters;
case ABI:
return abiFilters;
default:
throw new RuntimeException("Unhandled filter type");
}
}
@NonNull
public FileCollection getAllRawAndroidResources() {
if (rawAndroidResources == null) {
Project project = scope.getGlobalScope().getProject();
Iterator<Object> builtBy =
Lists.newArrayList(
taskContainer.getRenderscriptCompileTask(),
taskContainer.getGenerateResValuesTask(),
taskContainer.getGenerateApkDataTask(),
extraGeneratedResFolders.getBuiltBy())
.stream()
.filter(Objects::nonNull)
.iterator();
FileCollection allRes = project.files().builtBy(builtBy);
FileCollection libraries =
scope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, ANDROID_RES)
.getArtifactFiles();
allRes = allRes.plus(libraries);
Iterator<FileCollection> sourceSets = getAndroidResources().values().iterator();
FileCollection mainSourceSet = sourceSets.next();
FileCollection generated =
project.files(
scope.getRenderscriptResOutputDir(),
scope.getGeneratedResOutputDir(),
scope.getMicroApkResDirectory(),
extraGeneratedResFolders);
allRes = allRes.plus(mainSourceSet.plus(generated));
while (sourceSets.hasNext()) {
allRes = allRes.plus(sourceSets.next());
}
rawAndroidResources = allRes;
}
return rawAndroidResources;
}
/**
* Defines the discoverability attributes of filters.
*/
private enum DiscoverableFilterType {
DENSITY {
@NonNull
@Override
Collection<String> getConfiguredFilters(@NonNull Splits splits) {
return splits.getDensityFilters();
}
},
LANGUAGE {
@NonNull
@Override
Collection<String> getConfiguredFilters(@NonNull Splits splits) {
return splits.getLanguageFilters();
}
},
ABI {
@NonNull
@Override
Collection<String> getConfiguredFilters(@NonNull Splits splits) {
return splits.getAbiFilters();
}
};
/**
* Returns the applicable filters configured in the build.gradle for this filter type.
* @param splits the build.gradle splits configuration
* @return a list of filters.
*/
@NonNull
abstract Collection<String> getConfiguredFilters(@NonNull Splits splits);
}
/**
* Gets the list of filter values for a filter type either from the user specified build.gradle
* settings or through a discovery mechanism using folders names.
* @param filterType the filter type
* @param splits the variant's configuration for splits.
* @return a possibly empty list of filter value for this filter type.
*/
@NonNull
private static Set<String> getFilters(
@NonNull DiscoverableFilterType filterType,
@NonNull Splits splits) {
return new HashSet<>(filterType.getConfiguredFilters(splits));
}
/**
* Computes the Java sources to use for compilation.
*
* <p>Every entry is a ConfigurableFileTree instance to enable incremental java compilation.
*/
@NonNull
public List<ConfigurableFileTree> getJavaSources() {
// Shortcut for the common cases, otherwise we build the full list below.
if (extraGeneratedSourceFileTrees == null && externalAptJavaOutputFileTrees == null) {
return getDefaultJavaSources();
}
// Build the list of source folders.
ImmutableList.Builder<ConfigurableFileTree> sourceSets = ImmutableList.builder();
// First the default source folders.
sourceSets.addAll(getDefaultJavaSources());
// then the third party ones
if (extraGeneratedSourceFileTrees != null) {
sourceSets.addAll(extraGeneratedSourceFileTrees);
}
if (externalAptJavaOutputFileTrees != null) {
sourceSets.addAll(externalAptJavaOutputFileTrees);
}
return sourceSets.build();
}
public LinkedHashMap<String, FileCollection> getAndroidResources() {
return getVariantConfiguration()
.getSortedSourceProviders()
.stream()
.collect(
Collectors.toMap(
SourceProvider::getName,
(provider) ->
((AndroidSourceSet) provider)
.getRes()
.getBuildableArtifact(),
(u, v) -> {
throw new IllegalStateException(
String.format("Duplicate key %s", u));
},
LinkedHashMap::new));
}
/**
* Computes the default java sources: source sets and generated sources.
*
* <p>Every entry is a ConfigurableFileTree instance to enable incremental java compilation.
*/
@NonNull
private List<ConfigurableFileTree> getDefaultJavaSources() {
if (defaultJavaSources == null) {
Project project = scope.getGlobalScope().getProject();
// Build the list of source folders.
ImmutableList.Builder<ConfigurableFileTree> sourceSets = ImmutableList.builder();
// First the actual source folders.
List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
for (SourceProvider provider : providers) {
sourceSets.addAll(
((AndroidSourceSet) provider).getJava().getSourceDirectoryTrees());
}
// then all the generated src folders.
if (scope.getGlobalScope().getProjectOptions().get(BooleanOption.GENERATE_R_JAVA)) {
Provider<Directory> rClassSource =
scope.getArtifacts()
.getFinalProduct(
InternalArtifactType.NOT_NAMESPACED_R_CLASS_SOURCES);
if (rClassSource.isPresent()) {
sourceSets.add(project.fileTree(rClassSource).builtBy(rClassSource));
}
}
// for the other, there's no duplicate so no issue.
if (taskContainer.getGenerateBuildConfigTask() != null) {
sourceSets.add(
project.fileTree(scope.getBuildConfigSourceOutputDir())
.builtBy(taskContainer.getGenerateBuildConfigTask().getName()));
}
if (taskContainer.getAidlCompileTask() != null) {
Provider<FileSystemLocation> aidlFC =
scope.getArtifacts()
.getFinalProduct(InternalArtifactType.AIDL_SOURCE_OUTPUT_DIR);
sourceSets.add(project.fileTree(aidlFC).builtBy(aidlFC));
}
BaseExtension extension = scope.getGlobalScope().getExtension();
boolean isDataBindingEnabled = extension.getDataBinding().isEnabled();
boolean isViewBindingEnabled = extension.getViewBinding().isEnabled();
if (isDataBindingEnabled || isViewBindingEnabled) {
if (scope.getTaskContainer().getDataBindingExportBuildInfoTask() != null) {
sourceSets.add(
project.fileTree(scope.getClassOutputForDataBinding())
.builtBy(
scope.getTaskContainer()
.getDataBindingExportBuildInfoTask()));
}
if (scope.getArtifacts().hasFinalProduct(DATA_BINDING_BASE_CLASS_SOURCE_OUT)) {
Provider<FileSystemLocation> baseClassSource =
scope.getArtifacts()
.getFinalProduct(DATA_BINDING_BASE_CLASS_SOURCE_OUT);
sourceSets.add(project.fileTree(baseClassSource).builtBy(baseClassSource));
}
}
if (!variantConfiguration.getRenderscriptNdkModeEnabled()
&& taskContainer.getRenderscriptCompileTask() != null) {
Provider<Directory> rsFC =
scope.getArtifacts()
.getFinalProduct(
InternalArtifactType.RENDERSCRIPT_SOURCE_OUTPUT_DIR);
sourceSets.add(project.fileTree(rsFC).builtBy(rsFC));
}
defaultJavaSources = sourceSets.build();
}
return defaultJavaSources;
}
/**
* Returns the Java folders needed for code coverage report.
*
* <p>This includes all the source folders except for the ones containing R and buildConfig.
*/
@NonNull
public FileCollection getJavaSourceFoldersForCoverage() {
ConfigurableFileCollection fc = scope.getGlobalScope().getProject().files();
// First the actual source folders.
List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
for (SourceProvider provider : providers) {
for (File sourceFolder : provider.getJavaDirectories()) {
if (sourceFolder.isDirectory()) {
fc.from(sourceFolder);
}
}
}
// then all the generated src folders, except the ones for the R/Manifest and
// BuildConfig classes.
fc.from(scope.getArtifacts().getFinalProduct(InternalArtifactType.AIDL_SOURCE_OUTPUT_DIR));
if (!variantConfiguration.getRenderscriptNdkModeEnabled()) {
fc.from(
scope.getArtifacts()
.getFinalProduct(InternalArtifactType.RENDERSCRIPT_SOURCE_OUTPUT_DIR));
}
return fc;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.addValue(variantConfiguration.getFullName())
.toString();
}
@NonNull
public VariantScope getScope() {
return scope;
}
@NonNull
public File getJavaResourcesForUnitTesting() {
// FIXME we need to revise this API as it force-configure the tasks
Sync processJavaResourcesTask = taskContainer.getProcessJavaResourcesTask().get();
if (processJavaResourcesTask != null) {
return processJavaResourcesTask.getOutputs().getFiles().getSingleFile();
} else {
return scope.getArtifacts()
.getFinalProduct(InternalArtifactType.JAVA_RES)
.get()
.getAsFile();
}
}
}