blob: 222ac06bddfb44919365351f0a63a4ac97fada5e [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 com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.FilterData;
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.api.DefaultAndroidSourceSet;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.coverage.JacocoInstrumentTask;
import com.android.build.gradle.internal.dependency.VariantDependencies;
import com.android.build.gradle.internal.dsl.Splits;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.tasks.CheckManifest;
import com.android.build.gradle.internal.tasks.FileSupplier;
import com.android.build.gradle.internal.tasks.GenerateApkDataTask;
import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
import com.android.build.gradle.tasks.AidlCompile;
import com.android.build.gradle.tasks.BinaryFileProviderTask;
import com.android.build.gradle.tasks.GenerateBuildConfig;
import com.android.build.gradle.tasks.PreprocessResourcesTask;
import com.android.build.gradle.tasks.GenerateResValues;
import com.android.build.gradle.tasks.MergeAssets;
import com.android.build.gradle.tasks.MergeResources;
import com.android.build.gradle.tasks.NdkCompile;
import com.android.build.gradle.tasks.ProcessAndroidResources;
import com.android.build.gradle.tasks.RenderscriptCompile;
import com.android.builder.core.VariantType;
import com.android.builder.model.SourceProvider;
import com.android.ide.common.res2.ResourceSet;
import com.android.utils.StringHelper;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import org.gradle.api.Task;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.compile.AbstractCompile;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Base data about a variant.
*/
public abstract class BaseVariantData<T extends BaseVariantOutputData> {
public enum SplitHandlingPolicy {
/**
* Any release before L will create fake splits where each split will be the entire
* application with the split specific resources.
*/
PRE_21_POLICY,
/**
* Android L and after, the splits are pure splits where splits only contain resources
* specific to the split characteristics.
*/
RELEASE_21_AND_AFTER_POLICY
}
@NonNull
protected final BaseExtension baseExtension;
@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
private final VariantScope scope;
public Task preBuildTask;
public PrepareDependenciesTask prepareDependenciesTask;
public ProcessAndroidResources generateRClassTask;
public Task sourceGenTask;
public Task resourceGenTask;
public Task assetGenTask;
public CheckManifest checkManifestTask;
public RenderscriptCompile renderscriptCompileTask;
public AidlCompile aidlCompileTask;
public MergeResources mergeResourcesTask;
public MergeAssets mergeAssetsTask;
public GenerateBuildConfig generateBuildConfigTask;
public GenerateResValues generateResValuesTask;
public Copy copyApkTask;
public GenerateApkDataTask generateApkDataTask;
public PreprocessResourcesTask preprocessResourcesTask;
public Copy processJavaResourcesTask;
public NdkCompile ndkCompileTask;
// can be JavaCompile or JackTask depending on user's settings.
public AbstractCompile javaCompileTask;
public Jar classesJarTask;
// empty anchor compile task to set all compilations tasks as dependents.
public Task compileTask;
public JacocoInstrumentTask jacocoInstrumentTask;
public FileSupplier mappingFileProviderTask;
public BinaryFileProviderTask binayFileProviderTask;
// TODO : why is Jack not registered as the obfuscationTask ???
public Task obfuscationTask;
public File obfuscatedClassesJar;
// Task to assemble the variant and all its output.
public Task assembleVariantTask;
private Object[] javaSources;
private List<File> extraGeneratedSourceFolders;
private List<File> extraGeneratedResFolders;
private final List<T> outputs = Lists.newArrayListWithExpectedSize(4);
private Set<String> densityFilters;
private Set<String> languageFilters;
private Set<String> abiFilters;
/**
* 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;
private SplitHandlingPolicy mSplitHandlingPolicy;
public BaseVariantData(
@NonNull BaseExtension baseExtension,
@NonNull TaskManager taskManager,
@NonNull GradleVariantConfiguration variantConfiguration) {
this.baseExtension = baseExtension;
this.variantConfiguration = variantConfiguration;
this.taskManager = taskManager;
// eventually, this will require a more open ended comparison.
mSplitHandlingPolicy =
baseExtension.getGeneratePureSplits()
&& variantConfiguration.getMinSdkVersion().getApiLevel() >= 21
? SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY
: SplitHandlingPolicy.PRE_21_POLICY;
// warn the user in case we are forced to ignore the generatePureSplits flag.
if (baseExtension.getGeneratePureSplits()
&& mSplitHandlingPolicy != SplitHandlingPolicy.RELEASE_21_AND_AFTER_POLICY) {
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()));
}
variantConfiguration.checkSourceProviders();
scope = new VariantScope(taskManager.getGlobalScope(), this);
}
public SplitHandlingPolicy getSplitHandlingPolicy() {
return mSplitHandlingPolicy;
}
@NonNull
protected abstract T doCreateOutput(
OutputFile.OutputType outputType,
Collection<FilterData> filters);
@NonNull
public T createOutput(OutputFile.OutputType outputType,
Collection<FilterData> filters) {
T data = doCreateOutput(outputType, filters);
// if it's the first time we add an output, mark previous output as part of a multi-output
// setup.
if (outputs.size() == 1) {
outputs.get(0).setMultiOutput(true);
data.setMultiOutput(true);
} else if (outputs.size() > 1) {
data.setMultiOutput(true);
}
outputs.add(data);
return data;
}
@NonNull
public List<T> getOutputs() {
return outputs;
}
@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
protected String getCapitalizedBuildTypeName() {
return StringHelper.capitalize(variantConfiguration.getBuildType().getName());
}
@NonNull
protected String getCapitalizedFlavorName() {
return StringHelper.capitalize(variantConfiguration.getFlavorName());
}
public VariantType getType() {
return variantConfiguration.getType();
}
@NonNull
public String getName() {
return variantConfiguration.getFullName();
}
@Nullable
public List<File> getExtraGeneratedSourceFolders() {
return extraGeneratedSourceFolders;
}
@Nullable
public List<File> getExtraGeneratedResFolders() {
return extraGeneratedResFolders;
}
public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
if (extraGeneratedSourceFolders == null) {
extraGeneratedSourceFolders = Lists.newArrayList();
}
Collections.addAll(extraGeneratedSourceFolders, generatedSourceFolders);
}
public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
if (extraGeneratedSourceFolders == null) {
extraGeneratedSourceFolders = Lists.newArrayList();
}
extraGeneratedSourceFolders.addAll(generatedSourceFolders);
}
public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... generatedSourceFolders) {
sourceGenTask.dependsOn(task);
for (File f : generatedSourceFolders) {
javaCompileTask.source(f);
}
addJavaSourceFoldersToModel(generatedSourceFolders);
}
public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
sourceGenTask.dependsOn(task);
for (File f : generatedSourceFolders) {
javaCompileTask.source(f);
}
addJavaSourceFoldersToModel(generatedSourceFolders);
}
public void registerResGeneratingTask(@NonNull Task task, @NonNull File... generatedResFolders) {
// no need add the folders anywhere, the convention mapping closure for the MergeResources
// action will pick them up from here
resourceGenTask.dependsOn(task);
if (extraGeneratedResFolders == null) {
extraGeneratedResFolders = Lists.newArrayList();
}
Collections.addAll(extraGeneratedResFolders, generatedResFolders);
}
public void registerResGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedResFolders) {
// no need add the folders anywhere, the convention mapping closure for the MergeResources
// action will pick them up from here
resourceGenTask.dependsOn(task);
if (extraGeneratedResFolders == null) {
extraGeneratedResFolders = Lists.newArrayList();
}
extraGeneratedResFolders.addAll(generatedResFolders);
}
/**
* 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) {
List<ResourceSet> resourceSets = variantConfiguration
.getResourceSets(getGeneratedResFolders(), false);
densityFilters = getFilters(resourceSets, DiscoverableFilterType.DENSITY, splits);
languageFilters = getFilters(resourceSets, DiscoverableFilterType.LANGUAGE, splits);
abiFilters = getFilters(resourceSets, 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");
}
}
/**
* Returns the list of generated res folders for this variant.
*/
private List<File> getGeneratedResFolders() {
List<File> generatedResFolders = Lists.newArrayList(
scope.getRenderscriptResOutputDir(),
scope.getGeneratedResOutputDir());
if (extraGeneratedResFolders != null) {
generatedResFolders.addAll(extraGeneratedResFolders);
}
if (generateApkDataTask != null &&
getVariantConfiguration().getBuildType().isEmbedMicroApp()) {
generatedResFolders.add(generateApkDataTask.getResOutputDir());
}
return generatedResFolders;
}
@NonNull
public List<String> discoverListOfResourceConfigs() {
List<String> resFoldersOnDisk = new ArrayList<String>();
List<ResourceSet> resourceSets = variantConfiguration.getResourceSets(
getGeneratedResFolders(), false /* no libraries resources */);
resFoldersOnDisk.addAll(getAllFilters(
resourceSets,
DiscoverableFilterType.LANGUAGE.folderPrefix,
DiscoverableFilterType.DENSITY.folderPrefix));
return resFoldersOnDisk;
}
/**
* Defines the discoverability attributes of filters.
*/
private enum DiscoverableFilterType {
DENSITY("drawable-") {
@NonNull
@Override
Collection<String> getConfiguredFilters(@NonNull Splits splits) {
return splits.getDensityFilters();
}
@Override
boolean isAuto(@NonNull Splits splits) {
return splits.getDensity().isAuto();
}
}, LANGUAGE("values-") {
@NonNull
@Override
Collection<String> getConfiguredFilters(@NonNull Splits splits) {
return splits.getLanguageFilters();
}
@Override
boolean isAuto(@NonNull Splits splits) {
return splits.getLanguage().isAuto();
}
}, ABI("") {
@NonNull
@Override
Collection<String> getConfiguredFilters(@NonNull Splits splits) {
return splits.getAbiFilters();
}
@Override
boolean isAuto(@NonNull Splits splits) {
// so far, we never auto-discover abi filters.
return false;
}
};
/**
* Sets the folder prefix that filter specific resources must start with.
*/
private String folderPrefix;
DiscoverableFilterType(String folderPrefix) {
this.folderPrefix = folderPrefix;
}
/**
* 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);
/**
* Returns true if the user wants the build system to auto discover the splits for this
* split type.
* @param splits the build.gradle splits configuration.
* @return true to use auto-discovery, false to use the build.gradle configuration.
*/
abstract boolean isAuto(@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 resourceSets the list of source folders to discover from.
* @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 List<ResourceSet> resourceSets,
@NonNull DiscoverableFilterType filterType,
@NonNull Splits splits) {
Set<String> filtersList = new HashSet<String>();
if (filterType.isAuto(splits)) {
filtersList.addAll(getAllFilters(resourceSets, filterType.folderPrefix));
} else {
filtersList.addAll(removeAllNullEntries(filterType.getConfiguredFilters(splits)));
}
return filtersList;
}
/**
* Discover all sub-folders of all the {@link ResourceSet#getSourceFiles()} which names are
* starting with one of the provided prefixes.
* @param resourceSets the list of sources {@link ResourceSet}
* @param prefixes the list of prefixes to look for folders.
* @return a possibly empty list of folders.
*/
@NonNull
private static List<String> getAllFilters(List<ResourceSet> resourceSets, String... prefixes) {
List<String> providedResFolders = new ArrayList<String>();
for (ResourceSet resourceSet : resourceSets) {
for (File resFolder : resourceSet.getSourceFiles()) {
File[] subResFolders = resFolder.listFiles();
if (subResFolders != null) {
for (File subResFolder : subResFolders) {
for (String prefix : prefixes) {
if (subResFolder.getName().startsWith(prefix)) {
providedResFolders
.add(subResFolder.getName().substring(prefix.length()));
}
}
}
}
}
}
return providedResFolders;
}
/**
* Computes the Java sources to use for compilation. This Object[] contains
* {@link org.gradle.api.file.FileCollection} and {@link File} instances
*/
@NonNull
public Object[] getJavaSources() {
if (javaSources == null) {
// Build the list of source folders.
List<Object> sourceList = Lists.newArrayList();
// First the actual source folders.
List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
for (SourceProvider provider : providers) {
sourceList.add(((AndroidSourceSet) provider).getJava().getSourceFiles());
}
// then all the generated src folders.
if (getScope().getGenerateRClassTask() != null) {
sourceList.add(getScope().getRClassSourceOutputDir());
}
// for the other, there's no duplicate so no issue.
if (getScope().getGenerateBuildConfigTask() != null) {
sourceList.add(scope.getBuildConfigSourceOutputDir());
}
if (getScope().getAidlCompileTask() != null) {
sourceList.add(scope.getAidlSourceOutputDir());
}
if (!variantConfiguration.getRenderscriptNdkModeEnabled()
&& getScope().getRenderscriptCompileTask() != null) {
sourceList.add(scope.getRenderscriptSourceOutputDir());
}
javaSources = sourceList.toArray();
}
return javaSources;
}
/**
* Returns the Java folders needed for code coverage report.
*
* This includes all the source folders except for the ones containing R and buildConfig.
*/
@NonNull
public List<File> getJavaSourceFoldersForCoverage() {
// Build the list of source folders.
List<File> sourceFolders = Lists.newArrayList();
// First the actual source folders.
List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
for (SourceProvider provider : providers) {
for (File sourceFolder : provider.getJavaDirectories()) {
if (sourceFolder.isDirectory()) {
sourceFolders.add(sourceFolder);
}
}
}
File sourceFolder;
// then all the generated src folders, except the ones for the R/Manifest and
// BuildConfig classes.
sourceFolder = aidlCompileTask.getSourceOutputDir();
if (sourceFolder.isDirectory()) {
sourceFolders.add(sourceFolder);
}
if (!variantConfiguration.getRenderscriptNdkModeEnabled()) {
sourceFolder = renderscriptCompileTask.getSourceOutputDir();
if (sourceFolder.isDirectory()) {
sourceFolders.add(sourceFolder);
}
}
return sourceFolders;
}
/**
* Returns a list of configuration name for wear connection, from highest to lowest priority.
* @return list of config.
*/
@NonNull
public List<String> getWearConfigNames() {
List<SourceProvider> providers = variantConfiguration.getSortedSourceProviders();
// this is the wrong order, so let's reverse it as we gather the names.
final int count = providers.size();
List<String> names = Lists.newArrayListWithCapacity(count);
for (int i = count - 1 ; i >= 0; i--) {
DefaultAndroidSourceSet sourceSet = (DefaultAndroidSourceSet) providers.get(i);
names.add(sourceSet.getWearAppConfigurationName());
}
return names;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.addValue(variantConfiguration.getFullName())
.toString();
}
@Nullable
public FileSupplier getMappingFileProvider() {
return mappingFileProviderTask;
}
@Nullable
public File getMappingFile() {
return mappingFileProviderTask != null ? mappingFileProviderTask.get() : null;
}
@NonNull
public File getFinalResourcesDir() {
return preprocessResourcesTask != null
? preprocessResourcesTask.getOutputResDirectory()
: mergeResourcesTask.getOutputDir();
}
private static <T> Set<T> removeAllNullEntries(Collection<T> input) {
HashSet<T> output = new HashSet<T>();
for (T element : input) {
if (element != null) {
output.add(element);
}
}
return output;
}
@NonNull
public VariantScope getScope() {
return scope;
}
}