blob: 5438e7676680b932cabf1d3ad0a1fc04de7510be [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 com.android.build.api.artifact.impl.ArtifactsImpl
import com.android.build.api.attributes.ProductFlavorAttr
import com.android.build.api.component.impl.AndroidTestImpl
import com.android.build.api.component.impl.TestFixturesImpl
import com.android.build.api.component.impl.UnitTestImpl
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.TestedExtension
import com.android.build.api.extension.impl.VariantApiOperationsRegistrar
import com.android.build.api.variant.HasAndroidTestBuilder
import com.android.build.api.variant.HasTestFixturesBuilder
import com.android.build.api.variant.Variant
import com.android.build.api.variant.VariantBuilder
import com.android.build.api.variant.VariantExtensionConfig
import com.android.build.api.variant.impl.ArtifactMetadataProcessor
import com.android.build.api.variant.impl.GlobalVariantBuilderConfig
import com.android.build.api.variant.impl.GlobalVariantBuilderConfigImpl
import com.android.build.api.variant.impl.HasAndroidTest
import com.android.build.api.variant.impl.HasTestFixtures
import com.android.build.api.variant.impl.InternalVariantBuilder
import com.android.build.api.variant.impl.VariantImpl
import com.android.build.gradle.BaseExtension
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.component.NestedComponentCreationConfig
import com.android.build.gradle.internal.component.TestComponentCreationConfig
import com.android.build.gradle.internal.component.TestFixturesCreationConfig
import com.android.build.gradle.internal.component.VariantCreationConfig
import com.android.build.gradle.internal.core.VariantDslInfoBuilder
import com.android.build.gradle.internal.core.VariantDslInfoBuilder.Companion.computeSourceSetName
import com.android.build.gradle.internal.core.VariantDslInfoBuilder.Companion.getBuilder
import com.android.build.gradle.internal.core.VariantDslInfoImpl
import com.android.build.gradle.internal.core.dsl.AndroidTestComponentDslInfo
import com.android.build.gradle.internal.core.dsl.ComponentDslInfo
import com.android.build.gradle.internal.core.dsl.TestComponentDslInfo
import com.android.build.gradle.internal.core.dsl.TestFixturesComponentDslInfo
import com.android.build.gradle.internal.core.dsl.UnitTestComponentDslInfo
import com.android.build.gradle.internal.core.dsl.VariantDslInfo
import com.android.build.gradle.internal.crash.ExternalApiUsageException
import com.android.build.gradle.internal.dependency.VariantDependenciesBuilder
import com.android.build.gradle.internal.dsl.BuildType
import com.android.build.gradle.internal.dsl.CommonExtensionImpl
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.manifest.LazyManifestParser
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.build.gradle.internal.profile.AnalyticsConfiguratorService
import com.android.build.gradle.internal.profile.AnalyticsUtil
import com.android.build.gradle.internal.scope.BuildFeatureValues
import com.android.build.gradle.internal.scope.Java8LangSupport
import com.android.build.gradle.internal.scope.MutableTaskContainer
import com.android.build.gradle.internal.services.DslServices
import com.android.build.gradle.internal.services.ProjectServices
import com.android.build.gradle.internal.services.TaskCreationServices
import com.android.build.gradle.internal.services.TaskCreationServicesImpl
import com.android.build.gradle.internal.services.VariantBuilderServices
import com.android.build.gradle.internal.services.VariantBuilderServicesImpl
import com.android.build.gradle.internal.services.VariantServicesImpl
import com.android.build.gradle.internal.services.getBuildService
import com.android.build.gradle.internal.tasks.factory.GlobalTaskCreationConfig
import com.android.build.gradle.internal.tasks.factory.GlobalTaskCreationConfigImpl.Companion.toExecutionEnum
import com.android.build.gradle.internal.variant.ComponentInfo
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.VariantComponentInfo
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.SigningOptions
import com.android.builder.core.AbstractProductFlavor.DimensionRequest
import com.android.builder.core.ComponentType
import com.android.builder.core.ComponentTypeImpl
import com.android.builder.dexing.isLegacyMultiDexMode
import com.android.builder.errors.IssueReporter
import com.android.builder.model.TestOptions
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 org.gradle.api.Project
import org.gradle.api.attributes.Attribute
import org.gradle.api.internal.GeneratedSubclass
import java.io.File
import java.util.Locale
import java.util.function.BooleanSupplier
import java.util.stream.Collectors
/** Class to create, manage variants. */
@Suppress("UnstableApiUsage")
class VariantManager<
CommonExtensionT: CommonExtension<*, *, *, *>,
VariantBuilderT : VariantBuilder,
VariantDslInfoT: VariantDslInfo,
VariantT : VariantCreationConfig>(
private val project: Project,
private val dslServices: DslServices,
@Deprecated("Use dslExtension") private val oldExtension: BaseExtension,
private val dslExtension: CommonExtensionT,
val variantApiOperationsRegistrar: VariantApiOperationsRegistrar<CommonExtensionT, VariantBuilder, Variant>,
private val variantFactory: VariantFactory<VariantBuilderT, VariantDslInfoT, VariantT>,
private val variantInputModel: VariantInputModel<DefaultConfig, BuildType, ProductFlavor, SigningConfig>,
val globalTaskCreationConfig: GlobalTaskCreationConfig,
private val projectServices: ProjectServices
) {
private val variantBuilderServices: VariantBuilderServices
private val variantPropertiesApiServices: VariantServicesImpl
private val taskCreationServices: TaskCreationServices
private val variantFilter: VariantFilter
private val variants: MutableList<ComponentInfo<VariantBuilderT, VariantT>> =
Lists.newArrayList()
private val lazyManifestParserMap: MutableMap<File, LazyManifestParser> =
Maps.newHashMapWithExpectedSize(3)
private val signingOverride: SigningConfig?
// 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 var hasCreatedTasks = false
/**
* Returns a list of all main components.
*
* @see .createVariants
*/
val mainComponents: List<ComponentInfo<VariantBuilderT, VariantT>>
get() = variants
/**
* Returns a list of all nested components.
*/
val nestedComponents: MutableList<NestedComponentCreationConfig> = Lists.newArrayList()
/**
* Returns a list of all test components.
*
* @see .createVariants
*/
val testComponents: MutableList<TestComponentCreationConfig> =
Lists.newArrayList()
/**
* Returns a list of all test fixtures components.
*/
val testFixturesComponents: MutableList<TestFixturesCreationConfig> = Lists.newArrayList()
val buildFeatureValues: BuildFeatureValues
get() = _buildFeatureValues
private lateinit var _buildFeatureValues: BuildFeatureValues
private fun hasDynamicFeatures(): Boolean =
(dslExtension as? ApplicationExtension)?.dynamicFeatures?.isNotEmpty() ?: false
private fun getCompileSdkVersion(): String? {
val newExtension = dslExtension as? CommonExtensionImpl<*,*,*,*> ?: throw RuntimeException("Wrong DSL instance")
return newExtension.compileSdkVersion
}
/**
* Creates the variants.
*
* @param buildFeatureValues the build feature value instance
*/
fun createVariants(
buildFeatureValues: BuildFeatureValues,
) {
_buildFeatureValues = buildFeatureValues
variantFactory.preVariantCallback(project, dslExtension, variantInputModel)
computeVariants()
}
private fun getFlavorSelection(
variantDslInfo: ComponentDslInfo): Map<Attribute<ProductFlavorAttr>, ProductFlavorAttr> {
val factory = project.objects
return variantDslInfo.missingDimensionStrategies.entries.stream()
.collect(
Collectors.toMap(
{ entry: Map.Entry<String, DimensionRequest> ->
ProductFlavorAttr.of(entry.key)
}
) { entry: Map.Entry<String, DimensionRequest> ->
factory.named(
ProductFlavorAttr::class.java,
entry.value.requested)
})
}
/**
* Create all variants.
*/
private fun computeVariants() {
val flavorDimensionList: List<String> = dslExtension.flavorDimensions
val computer = DimensionCombinator(
variantInputModel,
projectServices.issueReporter,
flavorDimensionList)
val variants = computer.computeVariants()
// get some info related to testing
val testBuildTypeData = testBuildTypeData
// figure out whether there are inconsistency in the appId of the flavors
val inconsistentTestAppId = checkInconsistentTestAppId(
variantInputModel.productFlavors.values.map { it.productFlavor }
)
val globalConfig = GlobalVariantBuilderConfigImpl(dslExtension)
// loop on all the new variant objects to create the legacy ones.
for (variant in variants) {
createVariantsFromCombination(
variant,
testBuildTypeData,
inconsistentTestAppId,
globalConfig
)
}
// FIXME we should lock the variant API properties after all the beforeVariants, and
// before any onVariants to avoid cross access between the two.
// This means changing the way to run beforeVariants vs onVariants.
variantBuilderServices.lockValues()
}
private val testBuildTypeData: BuildTypeData<BuildType>?
get() {
var testBuildTypeData: BuildTypeData<BuildType>? = null
if (dslExtension is TestedExtension) {
testBuildTypeData = variantInputModel.buildTypes[dslExtension.testBuildType]
if (testBuildTypeData == null) {
throw RuntimeException(String.format(
"Test Build Type '%1\$s' does not" + " exist.",
dslExtension.testBuildType))
}
}
return testBuildTypeData
}
private fun createVariant(
dimensionCombination: DimensionCombination,
buildTypeData: BuildTypeData<BuildType>,
productFlavorDataList: List<ProductFlavorData<ProductFlavor>>,
componentType: ComponentType,
globalConfig: GlobalVariantBuilderConfig,
): VariantComponentInfo<VariantBuilderT, VariantDslInfoT, VariantT>? {
// 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
val defaultConfig = variantInputModel.defaultConfigData
val defaultConfigSourceProvider = defaultConfig.sourceSet
val variantDslInfoBuilder = getBuilder<CommonExtensionT, VariantDslInfoT>(
dimensionCombination,
componentType,
defaultConfig.defaultConfig,
defaultConfigSourceProvider,
buildTypeData.buildType,
buildTypeData.sourceSet,
signingOverride,
getLazyManifestParser(
defaultConfigSourceProvider.manifestFile,
componentType.requiresManifest) { canParseManifest() },
dslServices,
variantPropertiesApiServices,
oldExtension,
dslExtension,
hasDynamicFeatures = hasDynamicFeatures(),
dslExtension.experimentalProperties,
)
// 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 in productFlavorDataList) {
variantDslInfoBuilder.addProductFlavor(
productFlavorData.productFlavor, productFlavorData.sourceSet)
}
val variantDslInfo = variantDslInfoBuilder.createVariantDslInfo(
project.layout.buildDirectory)
val componentIdentity = variantDslInfo.componentIdentity
// create the Variant object so that we can run the action which may interrupt the creation
// (in case of enabled = false)
val variantBuilder = variantFactory.createVariantBuilder(
globalConfig, componentIdentity, variantDslInfo, variantBuilderServices,
)
// now that we have the variant, create the analytics object,
val configuratorService = getBuildService(
project.gradle.sharedServices,
AnalyticsConfiguratorService::class.java)
.get()
val profileEnabledVariantBuilder = configuratorService.getVariantBuilder(
project.path, variantBuilder.name)
val userVisibleVariantBuilder =
(variantBuilder as InternalVariantBuilder).createUserVisibleVariantObject<VariantBuilder>(
projectServices,
profileEnabledVariantBuilder,
)
// execute the Variant API
variantApiOperationsRegistrar.variantBuilderOperations.executeOperations(userVisibleVariantBuilder)
if (!variantBuilder.enable) {
return null
}
// now that we have the result of the filter, we can continue configuring the variant
createCompoundSourceSets(productFlavorDataList, variantDslInfoBuilder)
val variantSources = variantDslInfoBuilder.createVariantSources()
// 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.
val variantSourceSets: MutableList<DefaultAndroidSourceSet?> =
Lists.newArrayListWithExpectedSize(productFlavorDataList.size + 4)
// 1. add the variant-specific if applicable.
if (productFlavorDataList.isNotEmpty()) {
variantSourceSets.add(variantSources.variantSourceProvider)
}
// 2. the build type.
variantSourceSets.add(buildTypeData.sourceSet)
// 3. the multi-flavor combination
if (productFlavorDataList.size > 1) {
variantSourceSets.add(variantSources.multiFlavorSourceProvider)
}
// 4. the flavors.
for (productFlavor in productFlavorDataList) {
variantSourceSets.add(productFlavor.sourceSet)
}
// 5. The defaultConfig
variantSourceSets.add(variantInputModel.defaultConfigData.sourceSet)
// Create VariantDependencies
val builder = VariantDependenciesBuilder.builder(
project,
dslServices.projectOptions,
projectServices.issueReporter,
variantDslInfo)
.setFlavorSelection(getFlavorSelection(variantDslInfo))
.addSourceSets(variantSourceSets)
if (dslExtension is ApplicationExtension) {
builder.setFeatureList(dslExtension.dynamicFeatures)
}
val variantDependencies = builder.build()
// Done. Create the (too) many variant objects
val pathHelper =
VariantPathHelper(
project.layout.buildDirectory,
variantDslInfo,
dslServices
)
val artifacts = ArtifactsImpl(project, componentIdentity.name)
val taskContainer = MutableTaskContainer()
val transformManager = TransformManager(project, dslServices.issueReporter)
// and the obsolete variant data
val variantData = variantFactory.createVariantData(
componentIdentity,
artifacts,
variantPropertiesApiServices,
taskContainer
)
// then the new Variant which will contain the 2 old objects.
val variantApiObject = variantFactory.createVariant(
variantBuilder,
componentIdentity,
buildFeatureValues,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
variantData,
taskContainer,
transformManager,
variantPropertiesApiServices,
taskCreationServices,
globalTaskCreationConfig,
)
return VariantComponentInfo(
variantBuilder,
variantApiObject,
profileEnabledVariantBuilder,
variantDslInfo
)
}
private fun<DslInfoT: ComponentDslInfo> createCompoundSourceSets(
productFlavorList: List<ProductFlavorData<ProductFlavor>>,
variantDslInfoBuilder: VariantDslInfoBuilder<CommonExtensionT, DslInfoT>) {
val componentType = variantDslInfoBuilder.componentType
if (productFlavorList.isNotEmpty() /* && !variantConfig.getType().isSingleBuildType()*/) {
val variantSourceSet = variantInputModel
.sourceSetManager
.setUpSourceSet(
computeSourceSetName(variantDslInfoBuilder.name, componentType),
componentType.isTestComponent) as DefaultAndroidSourceSet
variantDslInfoBuilder.variantSourceProvider = variantSourceSet
}
if (productFlavorList.size > 1) {
val multiFlavorSourceSet = variantInputModel
.sourceSetManager
.setUpSourceSet(
computeSourceSetName(variantDslInfoBuilder.flavorName,
componentType),
componentType.isTestComponent) as DefaultAndroidSourceSet
variantDslInfoBuilder.multiFlavorSourceProvider = multiFlavorSourceSet
}
}
/** Create a test fixtures component for the specified main component. */
private fun createTestFixturesComponent(
dimensionCombination: DimensionCombination,
buildTypeData: BuildTypeData<BuildType>,
productFlavorDataList: List<ProductFlavorData<ProductFlavor>>,
mainComponentInfo: VariantComponentInfo<VariantBuilderT, VariantDslInfoT, VariantT>
): TestFixturesCreationConfig {
val testFixturesComponentType = ComponentTypeImpl.TEST_FIXTURES
val testFixturesSourceSet = variantInputModel.defaultConfigData.testFixturesSourceSet!!
val variantDslInfoBuilder = getBuilder<CommonExtensionT, TestFixturesComponentDslInfo>(
dimensionCombination,
testFixturesComponentType,
variantInputModel.defaultConfigData.defaultConfig,
testFixturesSourceSet,
buildTypeData.buildType,
buildTypeData.testFixturesSourceSet,
signingOverride,
getLazyManifestParser(
testFixturesSourceSet.manifestFile,
testFixturesComponentType.requiresManifest) { canParseManifest() },
dslServices,
variantPropertiesApiServices,
oldExtension = oldExtension,
extension = dslExtension,
hasDynamicFeatures = hasDynamicFeatures(),
testFixtureMainVariantName = mainComponentInfo.variant.name
)
variantDslInfoBuilder.productionVariant = mainComponentInfo.variantDslInfo as VariantDslInfoImpl
val productFlavorList = mainComponentInfo.variantDslInfo.productFlavorList
// 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.
val productFlavors = variantInputModel.productFlavors
for (productFlavor in productFlavorList) {
productFlavors[productFlavor.name]?.let {
variantDslInfoBuilder.addProductFlavor(
it.productFlavor,
it.testFixturesSourceSet!!
)
}
}
val variantDslInfo = variantDslInfoBuilder.createVariantDslInfo(
project.layout.buildDirectory
)
// now that we have the result of the filter, we can continue configuring the variant
createCompoundSourceSets(productFlavorDataList, variantDslInfoBuilder)
val variantSources = variantDslInfoBuilder.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.
val testFixturesProductFlavors = variantDslInfo.productFlavorList
val testFixturesVariantSourceSets: MutableList<DefaultAndroidSourceSet?> =
Lists.newArrayListWithExpectedSize(4 + testFixturesProductFlavors.size)
// 1. add the variant-specific if applicable.
if (testFixturesProductFlavors.isNotEmpty()) {
testFixturesVariantSourceSets.add(variantSources.variantSourceProvider)
}
// 2. the build type.
val buildTypeConfigurationProvider = buildTypeData.testFixturesSourceSet
buildTypeConfigurationProvider?.let {
testFixturesVariantSourceSets.add(it)
}
// 3. the multi-flavor combination
if (testFixturesProductFlavors.size > 1) {
testFixturesVariantSourceSets.add(variantSources.multiFlavorSourceProvider)
}
// 4. the flavors.
for (productFlavor in testFixturesProductFlavors) {
variantInputModel.productFlavors[productFlavor.name]?.let {
testFixturesVariantSourceSets.add(it.testFixturesSourceSet)
}
}
// now add the default config
testFixturesVariantSourceSets.add(variantInputModel.defaultConfigData.testFixturesSourceSet)
// 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.
val variantDependencies = VariantDependenciesBuilder.builder(
project,
dslServices.projectOptions,
projectServices.issueReporter,
variantDslInfo
)
.addSourceSets(testFixturesVariantSourceSets)
.setFlavorSelection(getFlavorSelection(variantDslInfo))
.overrideVariantNameAttribute(mainComponentInfo.variant.name)
.setMainVariant(mainComponentInfo.variant)
.build()
val pathHelper =
VariantPathHelper(
project.layout.buildDirectory,
variantDslInfo,
dslServices
)
val componentIdentity = variantDslInfo.componentIdentity
val artifacts = ArtifactsImpl(project, componentIdentity.name)
val taskContainer = MutableTaskContainer()
val transformManager = TransformManager(project, dslServices.issueReporter)
val testFixturesBuildFeatureValues = variantFactory.createTestFixturesBuildFeatureValues(
dslExtension.buildFeatures,
dslServices.projectOptions,
variantDslInfo.testFixturesAndroidResourcesEnabled
)
return variantFactory.createTestFixtures(
variantDslInfo.componentIdentity,
testFixturesBuildFeatureValues,
variantDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
taskContainer,
mainComponentInfo.variant,
transformManager,
variantPropertiesApiServices,
taskCreationServices,
globalTaskCreationConfig
).also {
// register testFixtures component to the main variant
mainComponentInfo.variant.testFixturesComponent = it
}
}
/** Create a TestVariantData for the specified testedVariantData. */
fun<TestDslInfoT: TestComponentDslInfo> createTestComponents(
dimensionCombination: DimensionCombination,
buildTypeData: BuildTypeData<BuildType>,
productFlavorDataList: List<ProductFlavorData<ProductFlavor>>,
testedComponentInfo: VariantComponentInfo<VariantBuilderT, VariantDslInfoT, VariantT>,
componentType: ComponentType,
testFixturesEnabled: Boolean,
inconsistentTestAppId: Boolean
): TestComponentCreationConfig? {
// 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.
val testSourceSet = variantInputModel.defaultConfigData.getTestSourceSet(componentType)
val variantDslInfoBuilder = getBuilder<CommonExtensionT, TestDslInfoT> (
dimensionCombination,
componentType,
variantInputModel.defaultConfigData.defaultConfig,
testSourceSet!!,
buildTypeData.buildType,
buildTypeData.getTestSourceSet(componentType),
signingOverride,
getLazyManifestParser(
testSourceSet.manifestFile,
componentType.requiresManifest) { canParseManifest() },
dslServices,
variantPropertiesApiServices,
oldExtension = oldExtension,
extension = dslExtension,
hasDynamicFeatures = hasDynamicFeatures())
variantDslInfoBuilder.productionVariant =
testedComponentInfo.variantDslInfo as VariantDslInfoImpl
variantDslInfoBuilder.inconsistentTestAppId = inconsistentTestAppId
val productFlavorList = testedComponentInfo.variantDslInfo.productFlavorList
// 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.
val productFlavors = variantInputModel.productFlavors
for (productFlavor in productFlavorList) {
productFlavors[productFlavor.name]?.let {
variantDslInfoBuilder.addProductFlavor(
it.productFlavor,
it.getTestSourceSet(componentType)!!)
}
}
val variantDslInfo = variantDslInfoBuilder.createVariantDslInfo(
project.layout.buildDirectory)
val apiAccessStats = testedComponentInfo.stats
if (componentType.isApk
&& testedComponentInfo.variantBuilder is HasAndroidTestBuilder) {
// this is ANDROID_TEST
if (!testedComponentInfo.variantBuilder.enableAndroidTest) {
return null
}
} else {
// this is UNIT_TEST
if (!testedComponentInfo.variantBuilder.enableUnitTest) {
return null
}
}
// now that we have the result of the filter, we can continue configuring the variant
createCompoundSourceSets(productFlavorDataList, variantDslInfoBuilder)
val variantSources = variantDslInfoBuilder.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.
val testProductFlavors = variantDslInfo.productFlavorList
val testVariantSourceSets: MutableList<DefaultAndroidSourceSet?> =
Lists.newArrayListWithExpectedSize(4 + testProductFlavors.size)
// 1. add the variant-specific if applicable.
if (testProductFlavors.isNotEmpty()) {
testVariantSourceSets.add(variantSources.variantSourceProvider)
}
// 2. the build type.
val buildTypeConfigurationProvider = buildTypeData.getTestSourceSet(componentType)
buildTypeConfigurationProvider?.let {
testVariantSourceSets.add(it)
}
// 3. the multi-flavor combination
if (testProductFlavors.size > 1) {
testVariantSourceSets.add(variantSources.multiFlavorSourceProvider)
}
// 4. the flavors.
for (productFlavor in testProductFlavors) {
variantInputModel.productFlavors[productFlavor.name]?.let {
testVariantSourceSets.add(it.getTestSourceSet(componentType))
}
}
// now add the default config
testVariantSourceSets.add(
variantInputModel.defaultConfigData.getTestSourceSet(componentType))
// 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.
val builder = VariantDependenciesBuilder.builder(
project,
dslServices.projectOptions,
projectServices.issueReporter,
variantDslInfo)
.addSourceSets(testVariantSourceSets)
.setFlavorSelection(getFlavorSelection(variantDslInfo))
.setTestedVariant(testedComponentInfo.variant)
.setTestFixturesEnabled(testFixturesEnabled)
val variantDependencies = builder.build()
val pathHelper =
VariantPathHelper(
project.layout.buildDirectory,
variantDslInfo,
dslServices
)
val componentIdentity = variantDslInfo.componentIdentity
val artifacts = ArtifactsImpl(project, componentIdentity.name)
val taskContainer = MutableTaskContainer()
val transformManager = TransformManager(project, dslServices.issueReporter)
// create the internal storage for this variant.
val testVariantData = TestVariantData(
componentIdentity,
artifacts,
variantPropertiesApiServices,
taskContainer
)
// this is ANDROID_TEST
val testComponent = if (componentType.isApk) {
val androidTest = variantFactory.createAndroidTest(
variantDslInfo.componentIdentity,
variantFactory.createAndroidTestBuildFeatureValues(
dslExtension.buildFeatures,
dslExtension.dataBinding,
dslServices.projectOptions
),
variantDslInfo as AndroidTestComponentDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
testVariantData,
taskContainer,
testedComponentInfo.variant,
transformManager,
variantPropertiesApiServices,
taskCreationServices,
globalTaskCreationConfig
)
androidTest
} else {
// this is UNIT_TEST
val unitTest = variantFactory.createUnitTest(
variantDslInfo.componentIdentity,
variantFactory.createUnitTestBuildFeatureValues(
dslExtension.buildFeatures,
dslExtension.dataBinding,
dslServices.projectOptions,
globalTaskCreationConfig.testOptions.unitTests.isIncludeAndroidResources
),
variantDslInfo as UnitTestComponentDslInfo,
variantDependencies,
variantSources,
pathHelper,
artifacts,
testVariantData,
taskContainer,
testedComponentInfo.variant,
transformManager,
variantPropertiesApiServices,
taskCreationServices,
globalTaskCreationConfig
)
unitTest
}
// register
testedComponentInfo
.variant
.testComponents[variantDslInfo.componentType] = testComponent
return testComponent
}
/**
* Creates Variant objects for a specific [ComponentIdentity]
*
*
* This will create both the prod and the androidTest/unitTest variants.
*/
private fun createVariantsFromCombination(
dimensionCombination: DimensionCombination,
testBuildTypeData: BuildTypeData<BuildType>?,
inconsistentTestAppId: Boolean,
globalConfig: GlobalVariantBuilderConfig,
) {
val componentType = variantFactory.componentType
// first run the old variantFilter API
// This acts on buildtype/flavor only, and applies in one pass to prod/tests.
val defaultConfig = variantInputModel.defaultConfigData.defaultConfig
val buildTypeData = variantInputModel.buildTypes[dimensionCombination.buildType]
val buildType = buildTypeData!!.buildType
// get the list of ProductFlavorData from the list of flavor name
val productFlavorDataList: List<ProductFlavorData<ProductFlavor>> = dimensionCombination
.productFlavors
.mapNotNull { (_, second) -> variantInputModel.productFlavors[second] }
val productFlavorList: List<ProductFlavor> = productFlavorDataList
.map { it.productFlavor }
var ignore = false
oldExtension.variantFilter?.let {
variantFilter.reset(
dimensionCombination, defaultConfig, buildType, componentType, productFlavorList)
try {
// variantFilterAction != null always true here.
it.execute(variantFilter)
} catch (t: Throwable) {
throw ExternalApiUsageException(t)
}
ignore = variantFilter.ignore
}
if (!ignore) {
// create the prod variant
createVariant(
dimensionCombination,
buildTypeData,
productFlavorDataList,
componentType,
globalConfig
)?.let { variantInfo ->
addVariant(variantInfo)
val variant = variantInfo.variant
val variantBuilder = variantInfo.variantBuilder
val minSdkVersion = variant.minSdkVersion
val targetSdkVersion = variant.targetSdkVersion
if (minSdkVersion.apiLevel > targetSdkVersion.apiLevel) {
projectServices
.issueReporter
.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.apiLevel,
targetSdkVersion.apiLevel,
variant.name))
}
val testFixturesEnabledForVariant =
variantBuilder is HasTestFixturesBuilder &&
(variantBuilder as HasTestFixturesBuilder)
.enableTestFixtures
if (testFixturesEnabledForVariant) {
val testFixtures = createTestFixturesComponent(
dimensionCombination,
buildTypeData,
productFlavorDataList,
variantInfo
)
addTestFixturesComponent(testFixtures)
(variant as HasTestFixtures).testFixtures = testFixtures as TestFixturesImpl
}
if (variantFactory.componentType.hasTestComponents) {
if (buildTypeData == testBuildTypeData) {
val androidTest = createTestComponents<AndroidTestComponentDslInfo>(
dimensionCombination,
buildTypeData,
productFlavorDataList,
variantInfo,
ComponentTypeImpl.ANDROID_TEST,
testFixturesEnabledForVariant,
inconsistentTestAppId
)
androidTest?.let {
addTestComponent(it)
(variant as HasAndroidTest).androidTest = it as AndroidTestImpl
}
}
val unitTest = createTestComponents<UnitTestComponentDslInfo>(
dimensionCombination,
buildTypeData,
productFlavorDataList,
variantInfo,
ComponentTypeImpl.UNIT_TEST,
testFixturesEnabledForVariant,
false
)
unitTest?.let {
addTestComponent(it)
variant.unitTest = it as UnitTestImpl
}
}
// Now that unitTest and/or androidTest have been created and added to the main
// user visible variant object, we can run the onVariants() actions
val userVisibleVariant = (variant as VariantImpl<*>)
.createUserVisibleVariantObject<Variant>(projectServices,
variantApiOperationsRegistrar,
variantInfo.stats)
// The variant object is created, let's create the user extension variant scoped objects
// and store them in our newly created variant object.
val variantExtensionConfig = object: VariantExtensionConfig<Variant> {
override val variant: Variant
get() = userVisibleVariant
override fun <T> projectExtension(extensionType: Class<T>): T {
// we need to make DefaultConfig or CommonExtension implement ExtensionAware.
throw RuntimeException("No global extension DSL element implements ExtensionAware.")
}
override fun <T> buildTypeExtension(extensionType: Class<T>): T =
buildTypeData.buildType.extensions.getByType(extensionType)
override fun <T> productFlavorsExtensions(extensionType: Class<T>): List<T> =
productFlavorDataList.map { productFlavorData ->
productFlavorData.productFlavor.extensions.getByType(extensionType)
}
}
variantApiOperationsRegistrar.dslExtensions.forEach { registeredExtension ->
registeredExtension.configurator.invoke(variantExtensionConfig).let {
variantBuilder.registerExtension(
if (it is GeneratedSubclass) it.publicType() else it.javaClass,
it
)
}
}
variantApiOperationsRegistrar.variantOperations.executeOperations(userVisibleVariant)
// all the variant public APIs have run, we can now safely fill the analytics with
// the final values that will be used throughout the task creation and execution.
val variantAnalytics = variantInfo.stats
variantAnalytics?.let {
it
.setIsDebug(buildType.isDebuggable)
.setMinSdkVersion(AnalyticsUtil.toProto(minSdkVersion))
.setMinifyEnabled(variant.minifiedEnabled)
.setUseMultidex(variant.isMultiDexEnabled)
.setUseLegacyMultidex(variant.dexingType.isLegacyMultiDexMode())
.setVariantType(variant.componentType.analyticsVariantType)
.setDexBuilder(GradleBuildVariant.DexBuilderTool.D8_DEXER)
.setDexMerger(GradleBuildVariant.DexMergerTool.D8_MERGER)
.setCoreLibraryDesugaringEnabled(variant.isCoreLibraryDesugaringEnabled)
.setHasUnitTest(variant.unitTest != null)
.setHasAndroidTest((variant as? HasAndroidTest)?.androidTest != null)
.setHasTestFixtures((variant as? HasTestFixtures)?.testFixtures != null)
.testExecution = AnalyticsUtil.toProto(dslExtension.testOptions.execution.toExecutionEnum() ?: TestOptions.Execution.HOST)
if (variant.minifiedEnabled) {
// If code shrinker is used, it can only be R8
variantAnalytics.codeShrinker = GradleBuildVariant.CodeShrinkerTool.R8
}
variantAnalytics.targetSdkVersion = AnalyticsUtil.toProto(targetSdkVersion)
variant.maxSdkVersion?.let { version ->
variantAnalytics.setMaxSdkVersion(
ApiVersion.newBuilder().setApiLevel(version.toLong()))
}
val supportType = variant.getJava8LangSupportType()
if (supportType != Java8LangSupport.INVALID
&& supportType != Java8LangSupport.UNUSED) {
variantAnalytics.java8LangSupport = AnalyticsUtil.toProto(supportType)
}
}
}
}
}
private fun addVariant(variant: ComponentInfo<VariantBuilderT, VariantT>) {
variants.add(variant)
}
private fun addTestComponent(testComponent: TestComponentCreationConfig) {
nestedComponents.add(testComponent)
testComponents.add(testComponent)
}
private fun addTestFixturesComponent(testFixturesComponent: TestFixturesCreationConfig) {
nestedComponents.add(testFixturesComponent)
testFixturesComponents.add(testFixturesComponent)
}
private fun createSigningOverride(): SigningConfig? {
SigningOptions.readSigningOptions(dslServices.projectOptions)?.let { signingOptions ->
val signingConfigDsl = dslServices.newDecoratedInstance(SigningConfig::class.java, SigningOptions.SIGNING_CONFIG_NAME, dslServices)
signingConfigDsl.storeFile(File(signingOptions.storeFile))
signingConfigDsl.storePassword(signingOptions.storePassword)
signingConfigDsl.keyAlias(signingOptions.keyAlias)
signingConfigDsl.keyPassword(signingOptions.keyPassword)
signingOptions.storeType?.let {
signingConfigDsl.storeType(it)
}
signingOptions.v1Enabled?.let {
signingConfigDsl.enableV1Signing = it
}
signingOptions.v2Enabled?.let {
signingConfigDsl.enableV2Signing = it
}
return signingConfigDsl
}
return null
}
private fun getLazyManifestParser(
file: File,
isManifestFileRequired: Boolean,
isInExecutionPhase: BooleanSupplier): LazyManifestParser {
return lazyManifestParserMap.computeIfAbsent(
file
) { f: File? ->
LazyManifestParser(
projectServices.objectFactory.fileProperty().fileValue(f),
isManifestFileRequired,
projectServices,
isInExecutionPhase)
}
}
private fun canParseManifest(): Boolean {
return hasCreatedTasks || !dslServices.projectOptions[BooleanOption.DISABLE_EARLY_MANIFEST_PARSING]
}
fun setHasCreatedTasks(hasCreatedTasks: Boolean) {
this.hasCreatedTasks = hasCreatedTasks
}
fun lockVariantProperties() {
variantPropertiesApiServices.lockProperties()
}
fun finalizeAllVariants() {
variants.forEach { variant ->
variant.variant.artifacts.finalizeAndLock()
ArtifactMetadataProcessor.wireAllFinalizedBy(variant.variant)
}
testComponents.forEach { testComponent ->
testComponent.artifacts.finalizeAndLock()
}
testFixturesComponents.forEach { testFixturesComponent ->
testFixturesComponent.artifacts.finalizeAndLock()
}
}
companion object {
/**
* Returns a modified name.
*
*
* 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.
*
*
* We are just modifying the name to avoid collision in case the same name exists in
* different dimensions
*/
fun getModifiedName(name: String): String {
return "____$name"
}
internal fun checkInconsistentTestAppId(
flavors: List<ProductFlavor>
): Boolean {
if (flavors.isEmpty()) {
return false
}
// as soon as one flavor declares an ID or a suffix, we bail.
// There are possible corner cases where a project could have 2 flavors setting the same
// appId in which case it would be safe to keep the current behavior but this is
// unlikely to be a common case.
for (flavor in flavors) {
if (flavor.applicationId != null || flavor.applicationIdSuffix != null) {
return true
}
}
return false
}
}
init {
signingOverride = createSigningOverride()
variantFilter = VariantFilter(ReadOnlyObjectProvider())
variantBuilderServices = VariantBuilderServicesImpl(projectServices)
variantPropertiesApiServices = VariantServicesImpl(
projectServices,
// detects whether we are running the plugin under unit test mode
forUnitTesting = project.hasProperty("_agp_internal_test_mode_")
)
taskCreationServices = TaskCreationServicesImpl(projectServices)
}
}