blob: 99edd5d7969442751f39fa6e7204e6d6ced2ba70 [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.internal;
import static com.android.build.OutputFile.NO_FILTER;
import static com.android.builder.core.BuilderConstants.ANDROID_TEST;
import static com.android.builder.core.BuilderConstants.DEBUG;
import static com.android.builder.core.BuilderConstants.LINT;
import static com.android.builder.core.BuilderConstants.UI_TEST;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.BaseExtension;
import com.android.build.gradle.BasePlugin;
import com.android.build.gradle.api.AndroidSourceSet;
import com.android.build.gradle.api.BaseVariant;
import com.android.build.gradle.api.GroupableProductFlavor;
import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
import com.android.build.gradle.internal.api.TestVariantImpl;
import com.android.build.gradle.internal.api.TestedVariant;
import com.android.build.gradle.internal.api.VariantFilterImpl;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.dependency.VariantDependencies;
import com.android.build.gradle.internal.dsl.BuildTypeDsl;
import com.android.build.gradle.internal.dsl.GroupableProductFlavorDsl;
import com.android.build.gradle.internal.dsl.ProductFlavorDsl;
import com.android.build.gradle.internal.dsl.SigningConfigDsl;
import com.android.build.gradle.internal.dsl.Splits;
import com.android.build.gradle.internal.variant.ApplicationVariantFactory;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.BaseVariantOutputData;
import com.android.build.gradle.internal.variant.TestVariantData;
import com.android.build.gradle.internal.variant.TestedVariantData;
import com.android.build.gradle.internal.variant.VariantFactory;
import com.android.builder.core.VariantConfiguration;
import com.android.builder.model.BuildType;
import com.android.builder.model.SigningConfig;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskContainer;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import groovy.lang.Closure;
/**
* Class to create, manage variants.
*/
public class VariantManager implements VariantModel {
protected static final String COM_ANDROID_SUPPORT_MULTIDEX
= "com.android.support:multidex:1.0.0";
@NonNull
private final Project project;
@NonNull
private final BasePlugin basePlugin;
@NonNull
private final BaseExtension extension;
@NonNull
private final VariantFactory variantFactory;
@NonNull
private final Map<String, BuildTypeData> buildTypes = Maps.newHashMap();
@NonNull
private final Map<String, ProductFlavorData<GroupableProductFlavorDsl>> productFlavors = Maps.newHashMap();
@NonNull
private final Map<String, SigningConfig> signingConfigs = Maps.newHashMap();
@NonNull
private final ReadOnlyObjectProvider readOnlyObjectProvider = new ReadOnlyObjectProvider();
@NonNull
private final VariantFilterImpl variantFilter = new VariantFilterImpl(readOnlyObjectProvider);
@NonNull
private final List<BaseVariantData<? extends BaseVariantOutputData>> variantDataList = Lists.newArrayList();
public VariantManager(
@NonNull Project project,
@NonNull BasePlugin basePlugin,
@NonNull BaseExtension extension,
@NonNull VariantFactory variantFactory) {
this.extension = extension;
this.basePlugin = basePlugin;
this.project = project;
this.variantFactory = variantFactory;
}
@Override
@NonNull
public Map<String, BuildTypeData> getBuildTypes() {
return buildTypes;
}
@Override
@NonNull
public Map<String, ProductFlavorData<GroupableProductFlavorDsl>> getProductFlavors() {
return productFlavors;
}
@Override
@NonNull
public Map<String, SigningConfig> getSigningConfigs() {
return signingConfigs;
}
public void addSigningConfig(@NonNull SigningConfigDsl signingConfigDsl) {
signingConfigs.put(signingConfigDsl.getName(), signingConfigDsl);
}
/**
* Adds new BuildType, creating a BuildTypeData, and the associated source set,
* and adding it to the map.
* @param buildType the build type.
*/
public void addBuildType(@NonNull BuildTypeDsl buildType) {
buildType.init(signingConfigs.get(DEBUG));
String name = buildType.getName();
checkName(name, "BuildType");
if (productFlavors.containsKey(name)) {
throw new RuntimeException("BuildType names cannot collide with ProductFlavor names");
}
DefaultAndroidSourceSet sourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(name);
BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project);
project.getTasks().getByName("assemble").dependsOn(buildTypeData.getAssembleTask());
buildTypes.put(name, buildTypeData);
}
/**
* Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
* and adding it to the map.
*
* @param productFlavor the product flavor
*/
public void addProductFlavor(@NonNull GroupableProductFlavorDsl productFlavor) {
String name = productFlavor.getName();
checkName(name, "ProductFlavor");
if (buildTypes.containsKey(name)) {
throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
}
DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
productFlavor.getName());
String testName = ANDROID_TEST + StringHelper.capitalize(productFlavor.getName());
DefaultAndroidSourceSet testSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
testName);
ProductFlavorData<GroupableProductFlavorDsl> productFlavorData =
new ProductFlavorData<GroupableProductFlavorDsl>(
productFlavor, mainSourceSet, testSourceSet, project);
productFlavors.put(productFlavor.getName(), productFlavorData);
}
/**
* Return a list of all created VariantData.
*/
@NonNull
public List<BaseVariantData<? extends BaseVariantOutputData>> getVariantDataList() {
return variantDataList;
}
/**
* Task creation entry point.
*/
public void createAndroidTasks(@Nullable SigningConfig signingOverride) {
variantFactory.validateModel(this);
if (!productFlavors.isEmpty()) {
// there'll be more than one test app, so we need a top level assembleTest
Task assembleTest = project.getTasks().create("assembleTest");
assembleTest.setGroup(org.gradle.api.plugins.BasePlugin.BUILD_GROUP);
assembleTest.setDescription("Assembles all the Test applications");
basePlugin.setAssembleTest(assembleTest);
}
if (variantDataList.isEmpty()) {
populateVariantDataList(signingOverride);
}
for (BaseVariantData variantData : variantDataList) {
createTasksForVariantData(project.getTasks(), variantData);
}
// create the lint tasks.
basePlugin.createLintTasks();
// create the test tasks.
basePlugin.createCheckTasks(!productFlavors.isEmpty(), false /*isLibrary*/);
// Create the variant API objects after the tasks have been created!
createApiObjects();
}
/**
* Create tasks for the specified variantData.
*/
public void createTasksForVariantData(TaskContainer tasks, BaseVariantData variantData) {
if (variantData.getVariantConfiguration().getType()
== GradleVariantConfiguration.Type.TEST) {
GradleVariantConfiguration testVariantConfig = variantData.getVariantConfiguration();
BaseVariantData testedVariantData = (BaseVariantData) ((TestVariantData) variantData)
.getTestedVariantData();
/// add the container of dependencies
// the order of the libraries is important. In descending order:
// flavors, defaultConfig. No build type for tests
List<ConfigurationProvider> testVariantProviders = Lists.newArrayListWithExpectedSize(
2 + testVariantConfig.getProductFlavors().size());
for (GroupableProductFlavor productFlavor : testVariantConfig.getProductFlavors()) {
ProductFlavorData<GroupableProductFlavorDsl> data = productFlavors.get(productFlavor.getName());
testVariantProviders.add(data.getTestProvider());
}
// now add the default config
testVariantProviders.add(basePlugin.getDefaultConfigData().getTestProvider());
assert(testVariantConfig.getTestedConfig() != null);
if (testVariantConfig.getTestedConfig().getType() == VariantConfiguration.Type.LIBRARY) {
testVariantProviders.add(testedVariantData.getVariantDependency());
}
// If the variant being tested is a library variant, VariantDependencies must be
// computed the tasks for the tested variant is created. Therefore, the
// VariantDependencies is computed here instead of when the VariantData was created.
VariantDependencies variantDep = VariantDependencies.compute(
project, testVariantConfig.getFullName(),
false /*publishVariant*/,
variantFactory.isLibrary(),
testVariantProviders.toArray(
new ConfigurationProvider[testVariantProviders.size()]));
variantData.setVariantDependency(variantDep);
basePlugin.resolveDependencies(variantDep);
testVariantConfig.setDependencies(variantDep);
basePlugin.createTestApkTasks((TestVariantData) variantData);
} else {
if (productFlavors.isEmpty()) {
variantFactory.createTasks(
variantData,
buildTypes.get(
variantData.getVariantConfiguration().getBuildType().getName())
.getAssembleTask());
} else {
variantFactory.createTasks(variantData, null);
// setup the task dependencies
// build type
buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName())
.getAssembleTask().dependsOn(variantData.assembleVariantTask);
// each flavor
GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
for (GroupableProductFlavorDsl flavor : variantConfig.getProductFlavors()) {
productFlavors.get(flavor.getName()).getAssembleTask()
.dependsOn(variantData.assembleVariantTask);
}
Task assembleTask = null;
// assembleTask for this flavor(dimension), created on demand if needed.
if (variantConfig.getProductFlavors().size() > 1) {
String name = StringHelper.capitalize(variantConfig.getFlavorName());
assembleTask = tasks.findByName("assemble" + name);
if (assembleTask == null) {
assembleTask = tasks.create("assemble" + name);
assembleTask.setDescription(
"Assembles all builds for flavor combination: " + name);
assembleTask.setGroup("Build");
tasks.getByName("assemble").dependsOn(assembleTask);
}
}
// flavor combo
if (assembleTask != null) {
assembleTask.dependsOn(variantData.assembleVariantTask);
}
}
}
}
/**
* Create all variants.
*
* @param signingOverride a signing override. Generally driven through the IDE.
*/
public void populateVariantDataList(@Nullable SigningConfig signingOverride) {
// Add a compile lint task
basePlugin.createLintCompileTask();
if (productFlavors.isEmpty()) {
createVariantDataForProductFlavors(signingOverride,
Collections.<GroupableProductFlavor>emptyList());
} else {
List<String> flavorDimensionList = extension.getFlavorDimensionList();
// Create iterable to get GroupableProductFlavorDsl from ProductFlavorData.
Iterable<GroupableProductFlavorDsl> flavorDsl =
Iterables.transform(
productFlavors.values(),
new Function<ProductFlavorData<GroupableProductFlavorDsl>, GroupableProductFlavorDsl>() {
@Override
public GroupableProductFlavorDsl apply(
ProductFlavorData<GroupableProductFlavorDsl> data) {
return data.getProductFlavor();
}
});
// Get a list of all combinations of product flavors.
List<ProductFlavorCombo> flavorComboList = ProductFlavorCombo.createCombinations(
flavorDimensionList,
flavorDsl);
for (ProductFlavorCombo flavorCombo : flavorComboList) {
createVariantDataForProductFlavors(signingOverride, flavorCombo.getFlavorList());
}
}
}
/**
* Create a VariantData for a specific combination of BuildType and GroupableProductFlavor list.
*/
public BaseVariantData<? extends BaseVariantOutputData> createVariantData(
@NonNull BuildType buildType,
@NonNull List<GroupableProductFlavor> productFlavorList,
@Nullable SigningConfig signingOverride) {
Splits splits = basePlugin.getExtension().getSplits();
Set<String> densities = splits.getDensityFilters();
Set<String> abis = splits.getAbiFilters();
// check against potentially empty lists. We always need to generate at least one output
densities = densities.isEmpty() ? Collections.singleton(NO_FILTER) : densities;
abis = abis.isEmpty() ? Collections.singleton(NO_FILTER) : abis;
ProductFlavorData<ProductFlavorDsl> defaultConfigData = basePlugin.getDefaultConfigData();
ProductFlavorDsl defaultConfig = defaultConfigData.getProductFlavor();
DefaultAndroidSourceSet defaultConfigSourceSet = defaultConfigData.getSourceSet();
BuildTypeData buildTypeData = buildTypes.get(buildType.getName());
Set<String> compatibleScreens = basePlugin.getExtension().getSplits().getDensity()
.getCompatibleScreens();
GradleVariantConfiguration variantConfig = new GradleVariantConfiguration(
defaultConfig,
defaultConfigSourceSet,
buildTypeData.getBuildType(),
buildTypeData.getSourceSet(),
variantFactory.getVariantConfigurationType(),
signingOverride);
// Add the container of dependencies.
// The order of the libraries is important, in descending order:
// build types, flavors, defaultConfig.
final List<ConfigurationProvider> variantProviders =
Lists.newArrayListWithCapacity(productFlavorList.size() + 2);
variantProviders.add(buildTypeData);
for (GroupableProductFlavor productFlavor : productFlavorList) {
ProductFlavorData<GroupableProductFlavorDsl> data = productFlavors.get(productFlavor.getName());
String dimensionName = productFlavor.getFlavorDimension();
if (dimensionName == null) {
dimensionName = "";
}
variantConfig.addProductFlavor(
data.getProductFlavor(),
data.getSourceSet(),
dimensionName);
variantProviders.add(data.getMainProvider());
}
// now add the defaultConfig
variantProviders.add(defaultConfigData.getMainProvider());
// Create variant source sets if necessary.
NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer = extension
.getSourceSetsContainer();
if (!productFlavorList.isEmpty()) {
DefaultAndroidSourceSet variantSourceSet =
(DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
variantConfig.getFullName());
variantConfig.setVariantSourceProvider(variantSourceSet);
// TODO: hmm this won't work
//variantProviders.add(new ConfigurationProviderImpl(project, variantSourceSet))
}
if (productFlavorList.size() > 1) {
DefaultAndroidSourceSet multiFlavorSourceSet =
(DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
variantConfig.getFlavorName());
variantConfig.setMultiFlavorSourceProvider(multiFlavorSourceSet);
// TODO: hmm this won't work
//variantProviders.add(new ConfigurationProviderImpl(project, multiFlavorSourceSet))
}
// create the variant and get its internal storage object.
BaseVariantData<?> variantData = variantFactory.createVariantData(variantConfig,
densities, abis, compatibleScreens);
VariantDependencies variantDep = VariantDependencies.compute(
project, variantConfig.getFullName(),
isVariantPublished(),
variantFactory.isLibrary(),
variantProviders.toArray(new ConfigurationProvider[variantProviders.size()]));
variantData.setVariantDependency(variantDep);
if (variantConfig.isMultiDexEnabled() && variantConfig.isLegacyMultiDexMode()) {
project.getDependencies().add(
variantDep.getCompileConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
project.getDependencies().add(
variantDep.getPackageConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
}
basePlugin.resolveDependencies(variantDep);
variantConfig.setDependencies(variantDep);
return variantData;
}
/**
* Create a TestVariantData for the specified testedVariantData.
*/
public TestVariantData createTestVariantData(
BaseVariantData testedVariantData,
SigningConfig signingOverride) {
BuildTypeData testData = buildTypes.get(extension.getTestBuildType());
ProductFlavorData<ProductFlavorDsl> defaultConfigData = basePlugin.getDefaultConfigData();
ProductFlavorDsl defaultConfig = defaultConfigData.getProductFlavor();
GradleVariantConfiguration testedConfig = testedVariantData.getVariantConfiguration();
List<? extends GroupableProductFlavor> productFlavorList = testedConfig.getProductFlavors();
// handle test variant
GradleVariantConfiguration testVariantConfig = new GradleVariantConfiguration(
defaultConfig,
defaultConfigData.getTestSourceSet(),
testData.getBuildType(),
null,
VariantConfiguration.Type.TEST,
testedVariantData.getVariantConfiguration(),
signingOverride);
for (GroupableProductFlavor productFlavor : productFlavorList) {
ProductFlavorData<GroupableProductFlavorDsl> data = productFlavors
.get(productFlavor.getName());
String dimensionName = productFlavor.getFlavorDimension();
if (dimensionName == null) {
dimensionName = "";
}
testVariantConfig.addProductFlavor(
data.getProductFlavor(),
data.getTestSourceSet(),
dimensionName);
}
// create the internal storage for this variant.
TestVariantData testVariantData = new TestVariantData(
basePlugin, testVariantConfig, (TestedVariantData) testedVariantData);
// link the testVariant to the tested variant in the other direction
((TestedVariantData) testedVariantData).setTestVariantData(testVariantData);
return testVariantData;
}
/**
* Creates VariantData for a specified list of product flavor.
*
* This will create VariantData for all build types of the given flavors.
*
* @param signingOverride a signing override. Generally driven through the IDE.
* @param productFlavorList the flavor(s) to build.
*/
private void createVariantDataForProductFlavors(
@Nullable SigningConfig signingOverride,
@NonNull List<GroupableProductFlavor> productFlavorList) {
BuildTypeData testData = buildTypes.get(extension.getTestBuildType());
if (testData == null) {
throw new RuntimeException(String.format(
"Test Build Type '%1$s' does not exist.", extension.getTestBuildType()));
}
BaseVariantData testedVariantData = null;
ProductFlavorData<ProductFlavorDsl> defaultConfigData = basePlugin.getDefaultConfigData();
ProductFlavorDsl defaultConfig = defaultConfigData.getProductFlavor();
Closure<Void> variantFilterClosure = basePlugin.getExtension().getVariantFilter();
for (BuildTypeData buildTypeData : buildTypes.values()) {
boolean ignore = false;
if (variantFilterClosure != null) {
variantFilter.reset(defaultConfig, buildTypeData.getBuildType(), productFlavorList);
variantFilterClosure.call(variantFilter);
ignore = variantFilter.isIgnore();
}
if (!ignore) {
BaseVariantData<?> variantData = createVariantData(
buildTypeData.getBuildType(),
productFlavorList,
signingOverride);
variantDataList.add(variantData);
if (buildTypeData == testData) {
GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
if (variantConfig.isMinifyEnabled() && variantConfig.getUseJack()) {
throw new RuntimeException("Cannot test obfuscated variants when compiling with jack.");
}
testedVariantData = variantData;
}
}
}
if (testedVariantData != null) {
TestVariantData testVariantData =
createTestVariantData(testedVariantData, signingOverride);
variantDataList.add(testVariantData);
}
}
private void createApiObjects() {
// we always want to have the test/tested objects created at the same time
// so that dynamic closure call on add can have referenced objects created.
// This means some objects are created before they are processed from the loop,
// so we store whether we have processed them or not.
Map<BaseVariantData, BaseVariant> map = Maps.newHashMap();
for (BaseVariantData variantData : variantDataList) {
if (map.get(variantData) != null) {
continue;
}
if (variantData instanceof TestVariantData) {
TestVariantData testVariantData = (TestVariantData) variantData;
createVariantApiObjects(
map,
(BaseVariantData) testVariantData.getTestedVariantData(),
testVariantData);
} else {
createVariantApiObjects(
map,
variantData,
((TestedVariantData) variantData).getTestVariantData());
}
}
}
private boolean isVariantPublished() {
return extension.getPublishNonDefault();
}
private void createVariantApiObjects(
@NonNull Map<BaseVariantData, BaseVariant> map,
@NonNull BaseVariantData<?> variantData,
@Nullable TestVariantData testVariantData) {
BaseVariant variantApi = variantFactory.createVariantApi(variantData,
readOnlyObjectProvider);
TestVariantImpl testVariant = null;
if (testVariantData != null) {
testVariant = basePlugin.getInstantiator().newInstance(
TestVariantImpl.class, testVariantData, basePlugin, readOnlyObjectProvider);
// add the test output.
ApplicationVariantFactory.createApkOutputApiObjects(basePlugin, testVariantData, testVariant);
}
if (testVariant != null) {
((TestedVariant) variantApi).setTestVariant(testVariant);
testVariant.setTestedVariant(variantApi);
}
extension.addVariant(variantApi);
map.put(variantData, variantApi);
if (testVariant != null) {
extension.addTestVariant(testVariant);
map.put(testVariantData, testVariant);
}
}
private static void checkName(@NonNull String name, @NonNull String displayName) {
if (name.startsWith(ANDROID_TEST)) {
throw new RuntimeException(String.format(
"%1$s names cannot start with '%2$s'", displayName, ANDROID_TEST));
}
if (name.startsWith(UI_TEST)) {
throw new RuntimeException(String.format(
"%1$s names cannot start with %2$s", displayName, UI_TEST));
}
if (LINT.equals(name)) {
throw new RuntimeException(String.format(
"%1$s names cannot be %2$s", displayName, LINT));
}
}
}