blob: 01319d9e80435e7015b8281b1f88ef80c54d280c [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.gradle.internal.dependency.DexingOutputSplitTransformKt.registerDexingOutputSplitTransform;
import static com.android.build.gradle.internal.dependency.DexingTransformKt.getDexingArtifactConfigurations;
import static com.android.build.gradle.internal.dependency.L8DexDesugarLibTransformKt.getDesugarLibConfigurations;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.FILTERED_PROGUARD_RULES;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.UNFILTERED_PROGUARD_RULES;
import static com.android.build.gradle.internal.utils.DesugarLibUtils.getDesugarLibConfig;
import static com.android.builder.core.VariantTypeImpl.ANDROID_TEST;
import static com.android.builder.core.VariantTypeImpl.UNIT_TEST;
import static org.gradle.api.internal.artifacts.ArtifactAttributes.ARTIFACT_FORMAT;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.api.attributes.ProductFlavorAttr;
import com.android.build.api.component.ComponentIdentity;
import com.android.build.api.component.TestComponentProperties;
import com.android.build.api.component.impl.AndroidTestImpl;
import com.android.build.api.component.impl.AndroidTestPropertiesImpl;
import com.android.build.api.component.impl.ComponentImpl;
import com.android.build.api.component.impl.ComponentPropertiesImpl;
import com.android.build.api.component.impl.UnitTestImpl;
import com.android.build.api.component.impl.UnitTestPropertiesImpl;
import com.android.build.api.variant.impl.VariantImpl;
import com.android.build.api.variant.impl.VariantPropertiesImpl;
import com.android.build.gradle.BaseExtension;
import com.android.build.gradle.TestedAndroidConfig;
import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
import com.android.build.gradle.internal.api.VariantFilter;
import com.android.build.gradle.internal.api.artifact.BuildArtifactSpec;
import com.android.build.gradle.internal.api.dsl.DslScope;
import com.android.build.gradle.internal.core.VariantBuilder;
import com.android.build.gradle.internal.core.VariantDslInfo;
import com.android.build.gradle.internal.core.VariantDslInfoImpl;
import com.android.build.gradle.internal.core.VariantSources;
import com.android.build.gradle.internal.crash.ExternalApiUsageException;
import com.android.build.gradle.internal.dependency.AndroidXDependencySubstitution;
import com.android.build.gradle.internal.dependency.DesugarLibConfiguration;
import com.android.build.gradle.internal.dependency.DexingArtifactConfiguration;
import com.android.build.gradle.internal.dependency.FilterShrinkerRulesTransform;
import com.android.build.gradle.internal.dependency.SourceSetManager;
import com.android.build.gradle.internal.dependency.VariantDependencies;
import com.android.build.gradle.internal.dependency.VariantDependenciesBuilder;
import com.android.build.gradle.internal.dependency.VersionedCodeShrinker;
import com.android.build.gradle.internal.dsl.ActionableVariantObjectOperationsExecutor;
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension;
import com.android.build.gradle.internal.dsl.BuildType;
import com.android.build.gradle.internal.dsl.DefaultConfig;
import com.android.build.gradle.internal.dsl.ProductFlavor;
import com.android.build.gradle.internal.dsl.SigningConfig;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.build.gradle.internal.profile.AnalyticsUtil;
import com.android.build.gradle.internal.publishing.PublishingSpecs;
import com.android.build.gradle.internal.scope.BuildArtifactsHolder;
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.SingleArtifactType;
import com.android.build.gradle.internal.scope.VariantBuildArtifactsHolder;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.scope.VariantScopeImpl;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.DimensionCombination;
import com.android.build.gradle.internal.variant.DimensionCombinator;
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.build.gradle.internal.variant.VariantInputModel;
import com.android.build.gradle.internal.variant.VariantPathHelper;
import com.android.build.gradle.options.BooleanOption;
import com.android.build.gradle.options.ProjectOptions;
import com.android.build.gradle.options.SigningOptions;
import com.android.build.gradle.options.SyncOptions;
import com.android.builder.core.DefaultManifestParser;
import com.android.builder.core.ManifestAttributeSupplier;
import com.android.builder.core.VariantType;
import com.android.builder.errors.IssueReporter;
import com.android.builder.model.CodeShrinker;
import com.android.builder.profile.ProcessProfileWriter;
import com.android.builder.profile.Recorder;
import com.android.utils.Pair;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.wireless.android.sdk.stats.ApiVersion;
import com.google.wireless.android.sdk.stats.GradleBuildVariant;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileSystemLocation;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Provider;
/** Class to create, manage variants. */
public class VariantManager {
private static final String MULTIDEX_VERSION = "1.0.2";
protected static final String COM_ANDROID_SUPPORT_MULTIDEX =
"com.android.support:multidex:" + MULTIDEX_VERSION;
protected static final String COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION =
"com.android.support:multidex-instrumentation:" + MULTIDEX_VERSION;
protected static final String ANDROIDX_MULTIDEX_MULTIDEX =
AndroidXDependencySubstitution.getAndroidXMappings()
.get("com.android.support:multidex");
protected static final String ANDROIDX_MULTIDEX_MULTIDEX_INSTRUMENTATION =
AndroidXDependencySubstitution.getAndroidXMappings()
.get("com.android.support:multidex-instrumentation");
@NonNull private final Project project;
@NonNull private final ProjectOptions projectOptions;
@NonNull private final BaseExtension extension;
@NonNull private final VariantFactory variantFactory;
@NonNull private final VariantInputModel variantInputModel;
@NonNull private final TaskManager taskManager;
@NonNull private final SourceSetManager sourceSetManager;
@NonNull private final Recorder recorder;
@NonNull private final VariantFilter variantFilter;
@NonNull private final List<ComponentPropertiesImpl> components;
@NonNull private final Map<File, ManifestAttributeSupplier> manifestParserMap;
@NonNull protected final GlobalScope globalScope;
@Nullable private final SigningConfig signingOverride;
// We cannot use gradle's state of executed as that returns true while inside afterEvalute.
// Wew want this to only be true after all tasks have been create.
private boolean hasCreatedTasks = false;
public static final Attribute<String> SHRINKER_ATTR =
Attribute.of("codeShrinker", String.class);
public VariantManager(
@NonNull GlobalScope globalScope,
@NonNull Project project,
@NonNull ProjectOptions projectOptions,
@NonNull BaseExtension extension,
@NonNull VariantFactory variantFactory,
@NonNull VariantInputModel variantInputModel,
@NonNull TaskManager taskManager,
@NonNull SourceSetManager sourceSetManager,
@NonNull Recorder recorder) {
this.globalScope = globalScope;
this.extension = extension;
this.project = project;
this.projectOptions = projectOptions;
this.variantFactory = variantFactory;
this.variantInputModel = variantInputModel;
this.taskManager = taskManager;
this.sourceSetManager = sourceSetManager;
this.recorder = recorder;
this.signingOverride = createSigningOverride();
this.variantFilter = new VariantFilter(new ReadOnlyObjectProvider());
this.components = Lists.newArrayList();
this.manifestParserMap = Maps.newHashMap();
}
/** Registers a new variant. */
private void addComponent(@NonNull ComponentPropertiesImpl component) {
components.add(component);
}
/** Returns a list of all created {@link VariantScope}s. */
@NonNull
public List<ComponentPropertiesImpl> getComponents() {
return components;
}
/** Creates the variants and their tasks. */
public List<ComponentPropertiesImpl> createVariantsAndTasks() {
variantFactory.validateModel(variantInputModel);
variantFactory.preVariantWork(project);
if (components.isEmpty()) {
computeVariants();
}
// Create top level test tasks.
taskManager.createTopLevelTestTasks(!variantInputModel.getProductFlavors().isEmpty());
// Create tasks for all variants (main and tests)
for (ComponentPropertiesImpl component : components) {
createTasksForVariant(component);
}
taskManager.createReportTasks(components);
return components;
}
/** Create tasks for the specified variant. */
private void createTasksForVariant(@NonNull ComponentPropertiesImpl componentProperties) {
final VariantType variantType = componentProperties.getVariantType();
taskManager.createAssembleTask(componentProperties);
if (variantType.isBaseModule()) {
taskManager.createBundleTask(componentProperties);
}
if (variantType.isTestComponent()) {
TestComponentProperties testComponent = (TestComponentProperties) componentProperties;
VariantPropertiesImpl testedVariant =
(VariantPropertiesImpl) testComponent.getTestedVariant();
VariantDslInfo variantDslInfo = componentProperties.getVariantDslInfo();
VariantDependencies variantDependencies = componentProperties.getVariantDependencies();
if (testedVariant.getVariantDslInfo().getRenderscriptSupportModeEnabled()) {
project.getDependencies()
.add(
variantDependencies.getCompileClasspath().getName(),
project.files(
globalScope
.getSdkComponents()
.getRenderScriptSupportJarProvider()));
}
if (variantType.isApk()) { // ANDROID_TEST
if (variantDslInfo.isLegacyMultiDexMode()) {
String multiDexInstrumentationDep =
globalScope.getProjectOptions().get(BooleanOption.USE_ANDROID_X)
? ANDROIDX_MULTIDEX_MULTIDEX_INSTRUMENTATION
: COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION;
project.getDependencies()
.add(
variantDependencies.getCompileClasspath().getName(),
multiDexInstrumentationDep);
project.getDependencies()
.add(
variantDependencies.getRuntimeClasspath().getName(),
multiDexInstrumentationDep);
}
taskManager.createAndroidTestVariantTasks(
(AndroidTestPropertiesImpl) componentProperties,
getVariantPropertiesList());
} else { // UNIT_TEST
taskManager.createUnitTestVariantTasks(
(UnitTestPropertiesImpl) componentProperties);
}
} else {
taskManager.createTasksForVariantScope(
(VariantPropertiesImpl) componentProperties, getVariantPropertiesList());
}
}
private List<VariantPropertiesImpl> getVariantPropertiesList() {
return components
.stream()
.filter(component -> component instanceof VariantPropertiesImpl)
.map(component -> (VariantPropertiesImpl) component)
.collect(Collectors.toList());
}
/** Publish intermediate artifacts in the BuildArtifactsHolder based on PublishingSpecs. */
public void publishBuildArtifacts(@NonNull ComponentPropertiesImpl componentProperties) {
BuildArtifactsHolder buildArtifactsHolder = componentProperties.getArtifacts();
for (PublishingSpecs.OutputSpec outputSpec :
componentProperties.getVariantScope().getPublishingSpec().getOutputs()) {
SingleArtifactType<? extends FileSystemLocation> buildArtifactType =
outputSpec.getOutputType();
// Gradle only support publishing single file. Therefore, unless Gradle starts
// supporting publishing multiple files, PublishingSpecs should not contain any
// OutputSpec with an appendable ArtifactType.
if (BuildArtifactSpec.Companion.has(buildArtifactType)
&& BuildArtifactSpec.Companion.get(buildArtifactType).getAppendable()) {
throw new RuntimeException(
String.format(
"Appendable ArtifactType '%1s' cannot be published.",
buildArtifactType.name()));
}
if (buildArtifactsHolder.hasFinalProduct(buildArtifactType)) {
Provider<? extends FileSystemLocation> artifact =
buildArtifactsHolder.getFinalProduct(buildArtifactType);
componentProperties
.getVariantScope()
.publishIntermediateArtifact(
artifact,
outputSpec.getArtifactType(),
outputSpec.getPublishedConfigTypes());
} else {
if (buildArtifactType == InternalArtifactType.ALL_CLASSES.INSTANCE) {
Provider<FileCollection> allClasses =
buildArtifactsHolder.getFinalProductAsFileCollection(
InternalArtifactType.ALL_CLASSES.INSTANCE);
Provider<File> file = allClasses.map(FileCollection::getSingleFile);
componentProperties
.getVariantScope()
.publishIntermediateArtifact(
file,
outputSpec.getArtifactType(),
outputSpec.getPublishedConfigTypes());
}
}
}
}
@NonNull
private Map<Attribute<ProductFlavorAttr>, ProductFlavorAttr> getFlavorSelection(
@NonNull VariantDslInfo variantDslInfo) {
ObjectFactory factory = project.getObjects();
return variantDslInfo
.getMissingDimensionStrategies()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> Attribute.of(entry.getKey(), ProductFlavorAttr.class),
entry ->
factory.named(
ProductFlavorAttr.class,
entry.getValue().getRequested())));
}
/** Configure artifact transforms that require variant-specific attribute information. */
private void configureVariantArtifactTransforms() {
DependencyHandler dependencies = project.getDependencies();
if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_DEXING_ARTIFACT_TRANSFORM)) {
for (DexingArtifactConfiguration artifactConfiguration :
getDexingArtifactConfigurations(components)) {
artifactConfiguration.registerTransform(
globalScope.getProject().getName(),
dependencies,
globalScope.getBootClasspath(),
getDesugarLibConfig(globalScope.getProject()),
SyncOptions.getErrorFormatMode(globalScope.getProjectOptions()),
projectOptions.get(BooleanOption.ENABLE_INCREMENTAL_DEXING_V2));
}
}
if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_PROGUARD_RULES_EXTRACTION)) {
Set<CodeShrinker> shrinkers =
components
.stream()
.map(component -> component.getVariantScope().getCodeShrinker())
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (CodeShrinker shrinker : shrinkers) {
dependencies.registerTransform(
FilterShrinkerRulesTransform.class,
reg -> {
reg.getFrom()
.attribute(
ARTIFACT_FORMAT, UNFILTERED_PROGUARD_RULES.getType());
reg.getTo()
.attribute(ARTIFACT_FORMAT, FILTERED_PROGUARD_RULES.getType());
reg.getFrom().attribute(SHRINKER_ATTR, shrinker.toString());
reg.getTo().attribute(SHRINKER_ATTR, shrinker.toString());
reg.parameters(
params -> {
params.getShrinker()
.set(VersionedCodeShrinker.of(shrinker));
params.getProjectName().set(project.getName());
});
});
}
}
for (DesugarLibConfiguration configuration : getDesugarLibConfigurations(components)) {
configuration.registerTransform(dependencies);
}
registerDexingOutputSplitTransform(dependencies);
}
/**
* Returns a modified name.
*
* <p>This name is used to request a missing dimension. It is the same name as the flavor that
* sets up the request, which means it's not going to be matched, and instead it'll go to a
* custom fallbacks provided by the flavor.
*
* <p>We are just modifying the name to avoid collision in case the same name exists in
* different dimensions
*/
public static String getModifiedName(@NonNull String name) {
return "____" + name;
}
/** Create all variants. */
@VisibleForTesting
public void computeVariants() {
List<String> flavorDimensionList = extension.getFlavorDimensionList();
DimensionCombinator computer =
new DimensionCombinator(
variantInputModel,
globalScope.getDslScope().getIssueReporter(),
variantFactory.getVariantType(),
flavorDimensionList);
List<DimensionCombination> variants = computer.computeVariants();
// get some info related to testing
BuildTypeData testBuildTypeData = getTestBuildTypeData();
// loop on all the new variant objects to create the legacy ones.
for (DimensionCombination variant : variants) {
createVariantsFromCombination(variant, testBuildTypeData);
}
configureVariantArtifactTransforms();
}
@Nullable
private BuildTypeData getTestBuildTypeData() {
BuildTypeData testBuildTypeData = null;
if (extension instanceof TestedAndroidConfig) {
TestedAndroidConfig testedExtension = (TestedAndroidConfig) extension;
testBuildTypeData =
variantInputModel.getBuildTypes().get(testedExtension.getTestBuildType());
if (testBuildTypeData == null) {
throw new RuntimeException(
String.format(
"Test Build Type '%1$s' does not" + " exist.",
testedExtension.getTestBuildType()));
}
}
return testBuildTypeData;
}
@Nullable
private Pair<VariantImpl<VariantPropertiesImpl>, VariantPropertiesImpl> createVariant(
@NonNull DimensionCombination dimensionCombination,
@NonNull BuildTypeData buildTypeData,
@NonNull List<ProductFlavorData<ProductFlavor>> productFlavorDataList,
@NonNull VariantType variantType) {
// entry point for a given buildType/Flavors/VariantType combo.
// Need to run the new variant API to selectively ignore variants.
// in order to do this, we need access to the VariantDslInfo, to create a
DslScope dslScope = globalScope.getDslScope();
final ProductFlavorData<DefaultConfig> defaultConfig = variantInputModel.getDefaultConfig();
DefaultAndroidSourceSet defaultConfigSourceProvider = defaultConfig.getSourceSet();
VariantBuilder variantBuilder =
VariantBuilder.getBuilder(
dimensionCombination,
variantType,
defaultConfig.getProductFlavor(),
defaultConfigSourceProvider,
buildTypeData.getBuildType(),
buildTypeData.getSourceSet(),
signingOverride,
getParser(
defaultConfigSourceProvider.getManifestFile(),
variantType.getRequiresManifest()),
globalScope.getProjectOptions(),
dslScope.getIssueReporter(),
this::canParseManifest);
// We must first add the flavors to the variant config, in order to get the proper
// variant-specific and multi-flavor name as we add/create the variant providers later.
for (ProductFlavorData<ProductFlavor> productFlavorData : productFlavorDataList) {
variantBuilder.addProductFlavor(
productFlavorData.getProductFlavor(), productFlavorData.getSourceSet());
}
VariantDslInfoImpl variantDslInfo = variantBuilder.createVariantDslInfo();
ComponentIdentity componentIdentity = variantDslInfo.getComponentIdentity();
// create the Variant object so that we can run the action which may interrupt the creation
// (in case of enabled = false)
VariantImpl<VariantPropertiesImpl> variant =
variantFactory.createVariantObject(componentIdentity, variantDslInfo);
// HACK, we need access to the new type rather than the old. This will go away in the
// future
ActionableVariantObjectOperationsExecutor commonExtension =
(ActionableVariantObjectOperationsExecutor) extension;
//noinspection unchecked
commonExtension.executeVariantOperations(variant);
if (!variant.getEnabled()) {
return null;
}
// now that we have the result of the filter, we can continue configuring the variant
createCompoundSourceSets(productFlavorDataList, variantBuilder, sourceSetManager);
VariantSources variantSources = variantBuilder.createVariantSources();
// Only record release artifacts
if (!buildTypeData.getBuildType().isDebuggable()
&& variantType.isApk()
&& !variantDslInfo.getVariantType().isForTesting()) {
ProcessProfileWriter.get().recordApplicationId(variantDslInfo::getApplicationId);
}
// Add the container of dependencies.
// The order of the libraries is important, in descending order:
// variant-specific, build type, multi-flavor, flavor1, flavor2, ..., defaultConfig.
// variant-specific if the full combo of flavors+build type. Does not exist if no flavors.
// multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension.
final List<DefaultAndroidSourceSet> variantSourceSets =
Lists.newArrayListWithExpectedSize(productFlavorDataList.size() + 4);
// 1. add the variant-specific if applicable.
if (!productFlavorDataList.isEmpty()) {
variantSourceSets.add(
(DefaultAndroidSourceSet) variantSources.getVariantSourceProvider());
}
// 2. the build type.
variantSourceSets.add(buildTypeData.getSourceSet());
// 3. the multi-flavor combination
if (productFlavorDataList.size() > 1) {
variantSourceSets.add(
(DefaultAndroidSourceSet) variantSources.getMultiFlavorSourceProvider());
}
// 4. the flavors.
for (ProductFlavorData<ProductFlavor> productFlavor : productFlavorDataList) {
variantSourceSets.add(productFlavor.getSourceSet());
}
// 5. The defaultConfig
variantSourceSets.add(variantInputModel.getDefaultConfig().getSourceSet());
// Create VariantDependencies
VariantDependenciesBuilder builder =
VariantDependenciesBuilder.builder(
project,
projectOptions,
globalScope.getDslScope().getIssueReporter(),
variantDslInfo)
.setFlavorSelection(getFlavorSelection(variantDslInfo))
.addSourceSets(variantSourceSets);
if (extension instanceof BaseAppModuleExtension) {
builder.setFeatureList(((BaseAppModuleExtension) extension).getDynamicFeatures());
}
final VariantDependencies variantDependencies = builder.build(globalScope);
// Done. Create the (too) many variant objects
VariantPathHelper pathHelper = new VariantPathHelper(project, variantDslInfo, dslScope);
VariantBuildArtifactsHolder artifacts =
new VariantBuildArtifactsHolder(
project, componentIdentity.getName(), globalScope.getBuildDir());
MutableTaskContainer taskContainer = new MutableTaskContainer();
// create the obsolete VariantScope
VariantScopeImpl variantScope =
new VariantScopeImpl(
componentIdentity,
variantDslInfo,
variantSources,
variantDependencies,
pathHelper,
artifacts,
globalScope,
new TransformManager(project, dslScope.getIssueReporter(), recorder),
null /* testedVariantProperties*/);
// and the obsolete variant data
BaseVariantData variantData =
variantFactory.createVariantData(
componentIdentity,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
globalScope,
taskManager,
taskContainer);
// then the new VariantProperties which will contain the 2 old objects.
VariantPropertiesImpl variantProperties =
variantFactory.createVariantPropertiesObject(
componentIdentity,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
variantScope,
variantData);
if (variantDslInfo.isLegacyMultiDexMode()) {
String multiDexDependency =
globalScope.getProjectOptions().get(BooleanOption.USE_ANDROID_X)
? ANDROIDX_MULTIDEX_MULTIDEX
: COM_ANDROID_SUPPORT_MULTIDEX;
project.getDependencies()
.add(variantDependencies.getCompileClasspath().getName(), multiDexDependency);
project.getDependencies()
.add(variantDependencies.getRuntimeClasspath().getName(), multiDexDependency);
}
if (variantDslInfo.getRenderscriptSupportModeEnabled()) {
final ConfigurableFileCollection fileCollection =
project.files(
globalScope.getSdkComponents().getRenderScriptSupportJarProvider());
project.getDependencies()
.add(variantDependencies.getCompileClasspath().getName(), fileCollection);
if (variantType.isApk() && !variantType.isForTesting()) {
project.getDependencies()
.add(variantDependencies.getRuntimeClasspath().getName(), fileCollection);
}
}
// Run the VariantProperties actions
//noinspection unchecked
commonExtension.executeVariantPropertiesOperations(variantProperties);
// also execute the delayed actions registered on the Variant object itself
//noinspection unchecked
variant.executePropertiesActions(variantProperties);
return Pair.of(variant, variantProperties);
}
private static void createCompoundSourceSets(
@NonNull List<ProductFlavorData<ProductFlavor>> productFlavorList,
@NonNull VariantBuilder variantBuilder,
@NonNull SourceSetManager sourceSetManager) {
final VariantType variantType = variantBuilder.getVariantType();
if (!productFlavorList.isEmpty() /* && !variantConfig.getType().isSingleBuildType()*/) {
DefaultAndroidSourceSet variantSourceSet =
(DefaultAndroidSourceSet)
sourceSetManager.setUpSourceSet(
VariantBuilder.computeSourceSetName(
variantBuilder.getName(), variantType),
variantType.isTestComponent());
variantBuilder.setVariantSourceProvider(variantSourceSet);
}
if (productFlavorList.size() > 1) {
DefaultAndroidSourceSet multiFlavorSourceSet =
(DefaultAndroidSourceSet)
sourceSetManager.setUpSourceSet(
VariantBuilder.computeSourceSetName(
variantBuilder.getFlavorName(), variantType),
variantType.isTestComponent());
variantBuilder.setMultiFlavorSourceProvider(multiFlavorSourceSet);
}
}
/** Create a TestVariantData for the specified testedVariantData. */
@Nullable
public ComponentPropertiesImpl createTestComponents(
@NonNull DimensionCombination dimensionCombination,
@NonNull BuildTypeData buildTypeData,
@NonNull List<ProductFlavorData<ProductFlavor>> productFlavorDataList,
@NonNull VariantImpl<VariantPropertiesImpl> testedVariant,
@NonNull VariantPropertiesImpl testedVariantProperties,
@NonNull VariantType variantType) {
// handle test variant
// need a suppress warning because ProductFlavor.getTestSourceSet(type) is annotated
// to return @Nullable and the constructor is @NonNull on this parameter,
// but it's never the case on defaultConfigData
// The constructor does a runtime check on the instances so we should be safe.
final DefaultAndroidSourceSet testSourceSet =
variantInputModel.getDefaultConfig().getTestSourceSet(variantType);
DslScope dslScope = globalScope.getDslScope();
@SuppressWarnings("ConstantConditions")
VariantBuilder variantBuilder =
VariantBuilder.getBuilder(
dimensionCombination,
variantType,
variantInputModel.getDefaultConfig().getProductFlavor(),
testSourceSet,
buildTypeData.getBuildType(),
buildTypeData.getTestSourceSet(variantType),
signingOverride,
testSourceSet != null
? getParser(
testSourceSet.getManifestFile(),
variantType.getRequiresManifest())
: null,
globalScope.getProjectOptions(),
dslScope.getIssueReporter(),
this::canParseManifest);
variantBuilder.setTestedVariant(
(VariantDslInfoImpl) testedVariantProperties.getVariantDslInfo());
List<ProductFlavor> productFlavorList =
testedVariantProperties.getVariantDslInfo().getProductFlavorList();
// We must first add the flavors to the variant builder, in order to get the proper
// variant-specific and multi-flavor name as we add/create the variant providers later.
final Map<String, ProductFlavorData<ProductFlavor>> productFlavors =
variantInputModel.getProductFlavors();
for (ProductFlavor productFlavor : productFlavorList) {
ProductFlavorData<ProductFlavor> data = productFlavors.get(productFlavor.getName());
//noinspection ConstantConditions
variantBuilder.addProductFlavor(
data.getProductFlavor(), data.getTestSourceSet(variantType));
}
VariantDslInfoImpl variantDslInfo = variantBuilder.createVariantDslInfo();
// this is ANDROID_TEST
ComponentImpl component;
if (variantType.isApk()) {
AndroidTestImpl androidTestVariant =
variantFactory.createAndroidTestObject(
variantDslInfo.getComponentIdentity(), variantDslInfo);
testedVariant.executeAndroidTestActions(androidTestVariant);
component = androidTestVariant;
} else {
// this is UNIT_TEST
UnitTestImpl unitTestVariant =
variantFactory.createUnitTestObject(
variantDslInfo.getComponentIdentity(), variantDslInfo);
testedVariant.executeUnitTestActions(unitTestVariant);
component = unitTestVariant;
}
if (!component.getEnabled()) {
return null;
}
// now that we have the result of the filter, we can continue configuring the variant
createCompoundSourceSets(productFlavorDataList, variantBuilder, sourceSetManager);
VariantSources variantSources = variantBuilder.createVariantSources();
// Add the container of dependencies, the order of the libraries is important.
// In descending order: build type (only for unit test), flavors, defaultConfig.
// Add the container of dependencies.
// The order of the libraries is important, in descending order:
// variant-specific, build type (, multi-flavor, flavor1, flavor2, ..., defaultConfig.
// variant-specific if the full combo of flavors+build type. Does not exist if no flavors.
// multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension.
List<ProductFlavor> testProductFlavors = variantDslInfo.getProductFlavorList();
List<DefaultAndroidSourceSet> testVariantSourceSets =
Lists.newArrayListWithExpectedSize(4 + testProductFlavors.size());
// 1. add the variant-specific if applicable.
if (!testProductFlavors.isEmpty()) {
testVariantSourceSets.add(
(DefaultAndroidSourceSet) variantSources.getVariantSourceProvider());
}
// 2. the build type.
DefaultAndroidSourceSet buildTypeConfigurationProvider =
buildTypeData.getTestSourceSet(variantType);
if (buildTypeConfigurationProvider != null) {
testVariantSourceSets.add(buildTypeConfigurationProvider);
}
// 3. the multi-flavor combination
if (testProductFlavors.size() > 1) {
testVariantSourceSets.add(
(DefaultAndroidSourceSet) variantSources.getMultiFlavorSourceProvider());
}
// 4. the flavors.
for (ProductFlavor productFlavor : testProductFlavors) {
testVariantSourceSets.add(
variantInputModel
.getProductFlavors()
.get(productFlavor.getName())
.getTestSourceSet(variantType));
}
// now add the default config
testVariantSourceSets.add(
variantInputModel.getDefaultConfig().getTestSourceSet(variantType));
// If the variant being tested is a library variant, VariantDependencies must be
// computed after the tasks for the tested variant is created. Therefore, the
// VariantDependencies is computed here instead of when the VariantData was created.
VariantDependenciesBuilder builder =
VariantDependenciesBuilder.builder(
project,
projectOptions,
globalScope.getDslScope().getIssueReporter(),
variantDslInfo)
.addSourceSets(testVariantSourceSets)
.setFlavorSelection(getFlavorSelection(variantDslInfo))
.setTestedVariant(testedVariantProperties);
final VariantDependencies variantDependencies = builder.build(globalScope);
VariantPathHelper pathHelper = new VariantPathHelper(project, variantDslInfo, dslScope);
ComponentIdentity componentIdentity = variantDslInfo.getComponentIdentity();
VariantBuildArtifactsHolder artifacts =
new VariantBuildArtifactsHolder(
project, componentIdentity.getName(), globalScope.getBuildDir());
MutableTaskContainer taskContainer = new MutableTaskContainer();
VariantScopeImpl variantScope =
new VariantScopeImpl(
componentIdentity,
variantDslInfo,
variantSources,
variantDependencies,
pathHelper,
artifacts,
globalScope,
new TransformManager(project, dslScope.getIssueReporter(), recorder),
testedVariantProperties);
// create the internal storage for this variant.
TestVariantData testVariantData =
new TestVariantData(
componentIdentity,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
(TestedVariantData) testedVariantProperties.getVariantData(),
globalScope,
taskManager,
taskContainer);
// this is ANDROID_TEST
ComponentPropertiesImpl componentProperties;
if (variantType.isApk()) {
AndroidTestPropertiesImpl androidTestProperties =
variantFactory.createAndroidTestProperties(
componentIdentity,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
variantScope,
testVariantData,
testedVariantProperties);
component.executePropertiesActions(androidTestProperties);
componentProperties = androidTestProperties;
} else {
// this is UNIT_TEST
UnitTestPropertiesImpl unitTestProperties =
variantFactory.createUnitTestProperties(
componentIdentity,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
variantScope,
testVariantData,
testedVariantProperties);
// also execute the delayed actions registered on the Variant object itself
component.executePropertiesActions(unitTestProperties);
componentProperties = unitTestProperties;
}
// register
testedVariantProperties
.getTestComponents()
.put(variantDslInfo.getVariantType(), componentProperties);
return componentProperties;
}
/**
* Creates Variant objects for a specific {@link ComponentIdentity}
*
* <p>This will create both the prod and the androidTest/unitTest variants.
*/
private void createVariantsFromCombination(
@NonNull DimensionCombination dimensionCombination,
@Nullable BuildTypeData testBuildTypeData) {
VariantType variantType = variantFactory.getVariantType();
// first run the old variantFilter API
// This acts on buildtype/flavor only, and applies in one pass to prod/tests.
Action<com.android.build.api.variant.VariantFilter> variantFilterAction =
extension.getVariantFilter();
DefaultConfig defaultConfig = variantInputModel.getDefaultConfig().getProductFlavor();
BuildTypeData buildTypeData =
variantInputModel.getBuildTypes().get(dimensionCombination.getBuildType());
BuildType buildType = buildTypeData.getBuildType();
// get the list of ProductFlavorData from the list of flavor name
List<ProductFlavorData<ProductFlavor>> productFlavorDataList =
dimensionCombination
.getProductFlavors()
.stream()
.map(it -> variantInputModel.getProductFlavors().get(it.getSecond()))
.collect(Collectors.toList());
List<ProductFlavor> productFlavorList =
productFlavorDataList
.stream()
.map(ProductFlavorData::getProductFlavor)
.collect(Collectors.toList());
boolean ignore = false;
if (variantFilterAction != null) {
variantFilter.reset(
dimensionCombination, defaultConfig, buildType, variantType, productFlavorList);
try {
// variantFilterAction != null always true here.
variantFilterAction.execute(variantFilter);
} catch (Throwable t) {
throw new ExternalApiUsageException(t);
}
ignore = variantFilter.getIgnore();
}
if (!ignore) {
// create the prod variant
Pair<VariantImpl<VariantPropertiesImpl>, VariantPropertiesImpl> variantInfo =
createVariant(
dimensionCombination,
buildTypeData,
productFlavorDataList,
variantType);
if (variantInfo != null) {
addComponent(variantInfo.getSecond());
VariantPropertiesImpl variantProperties = variantInfo.getSecond();
VariantDslInfo variantDslInfo = variantProperties.getVariantDslInfo();
VariantScope variantScope = variantProperties.getVariantScope();
int minSdkVersion = variantDslInfo.getMinSdkVersion().getApiLevel();
int targetSdkVersion = variantDslInfo.getTargetSdkVersion().getApiLevel();
if (minSdkVersion > 0 && targetSdkVersion > 0 && minSdkVersion > targetSdkVersion) {
globalScope
.getDslScope()
.getIssueReporter()
.reportWarning(
IssueReporter.Type.GENERIC,
String.format(
Locale.US,
"minSdkVersion (%d) is greater than targetSdkVersion"
+ " (%d) for variant \"%s\". Please change the"
+ " values such that minSdkVersion is less than or"
+ " equal to targetSdkVersion.",
minSdkVersion,
targetSdkVersion,
variantProperties.getName()));
}
GradleBuildVariant.Builder profileBuilder =
ProcessProfileWriter.getOrCreateVariant(
project.getPath(), variantProperties.getName())
.setIsDebug(buildType.isDebuggable())
.setMinSdkVersion(
AnalyticsUtil.toProto(variantDslInfo.getMinSdkVersion()))
.setMinifyEnabled(variantScope.getCodeShrinker() != null)
.setUseMultidex(variantDslInfo.isMultiDexEnabled())
.setUseLegacyMultidex(variantDslInfo.isLegacyMultiDexMode())
.setVariantType(
variantProperties
.getVariantType()
.getAnalyticsVariantType())
.setDexBuilder(AnalyticsUtil.toProto(variantScope.getDexer()))
.setDexMerger(AnalyticsUtil.toProto(variantScope.getDexMerger()))
.setCoreLibraryDesugaringEnabled(
variantScope.isCoreLibraryDesugaringEnabled())
.setTestExecution(
AnalyticsUtil.toProto(
globalScope
.getExtension()
.getTestOptions()
.getExecutionEnum()));
if (variantScope.getCodeShrinker() != null) {
profileBuilder.setCodeShrinker(
AnalyticsUtil.toProto(variantScope.getCodeShrinker()));
}
if (variantDslInfo.getTargetSdkVersion().getApiLevel() > 0) {
profileBuilder.setTargetSdkVersion(
AnalyticsUtil.toProto(variantDslInfo.getTargetSdkVersion()));
}
if (variantDslInfo.getMaxSdkVersion() != null) {
profileBuilder.setMaxSdkVersion(
ApiVersion.newBuilder().setApiLevel(variantDslInfo.getMaxSdkVersion()));
}
VariantScope.Java8LangSupport supportType = variantScope.getJava8LangSupportType();
if (supportType != VariantScope.Java8LangSupport.INVALID
&& supportType != VariantScope.Java8LangSupport.UNUSED) {
profileBuilder.setJava8LangSupport(AnalyticsUtil.toProto(supportType));
}
if (variantFactory.hasTestScope()) {
if (buildTypeData == testBuildTypeData) {
ComponentPropertiesImpl androidTestVariantProperties =
createTestComponents(
dimensionCombination,
buildTypeData,
productFlavorDataList,
variantInfo.getFirst(),
variantProperties,
ANDROID_TEST);
if (androidTestVariantProperties != null) {
addComponent(androidTestVariantProperties);
}
}
ComponentPropertiesImpl unitTestVariantProperties =
createTestComponents(
dimensionCombination,
buildTypeData,
productFlavorDataList,
variantInfo.getFirst(),
variantProperties,
UNIT_TEST);
if (unitTestVariantProperties != null) {
addComponent(unitTestVariantProperties);
}
}
}
}
}
private SigningConfig createSigningOverride() {
SigningOptions signingOptions = SigningOptions.readSigningOptions(projectOptions);
if (signingOptions != null) {
com.android.build.gradle.internal.dsl.SigningConfig signingConfigDsl =
new com.android.build.gradle.internal.dsl.SigningConfig("externalOverride");
signingConfigDsl.setStoreFile(new File(signingOptions.getStoreFile()));
signingConfigDsl.setStorePassword(signingOptions.getStorePassword());
signingConfigDsl.setKeyAlias(signingOptions.getKeyAlias());
signingConfigDsl.setKeyPassword(signingOptions.getKeyPassword());
if (signingOptions.getStoreType() != null) {
signingConfigDsl.setStoreType(signingOptions.getStoreType());
}
if (signingOptions.getV1Enabled() != null) {
signingConfigDsl.setV1SigningEnabled(signingOptions.getV1Enabled());
}
if (signingOptions.getV2Enabled() != null) {
signingConfigDsl.setV2SigningEnabled(signingOptions.getV2Enabled());
}
return signingConfigDsl;
}
return null;
}
@NonNull
private ManifestAttributeSupplier getParser(
@NonNull File file, boolean isManifestFileRequired) {
return manifestParserMap.computeIfAbsent(
file,
f ->
new DefaultManifestParser(
f,
this::canParseManifest,
isManifestFileRequired,
globalScope.getDslScope().getIssueReporter()));
}
private boolean canParseManifest() {
return hasCreatedTasks || !projectOptions.get(BooleanOption.DISABLE_EARLY_MANIFEST_PARSING);
}
public void setHasCreatedTasks(boolean hasCreatedTasks) {
this.hasCreatedTasks = hasCreatedTasks;
}
}