blob: 4d8e711c73cd367daca8c51c4647bd3cd6c37408 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.internal.variant
import com.android.build.api.component.impl.ComponentPropertiesImpl
import com.android.build.api.component.impl.TestComponentPropertiesImpl
import com.android.build.api.variant.impl.VariantPropertiesImpl
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.builder.errors.IssueReporter
import com.google.common.base.Joiner
import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.ImmutableMap
import java.util.ArrayList
import java.util.Comparator
class VariantModelImpl(
override val inputs: VariantInputModel<DefaultConfig, BuildType, ProductFlavor, SigningConfig>,
private val testBuilderTypeProvider: () -> String?,
private val variantProvider: () -> List<VariantPropertiesImpl>,
private val testComponentProvider: () -> List<TestComponentPropertiesImpl>,
private val issueHandler: IssueReporter) : VariantModel {
override val variants: List<VariantPropertiesImpl>
get() = variantProvider()
override val testComponents: List<TestComponentPropertiesImpl>
get() = testComponentProvider()
override val defaultVariant: String?
get() = computeDefaultVariant()
/**
* Calculates the default variant to put in the model.
*
*
* Given user preferences, this attempts to respect them in the presence of the variant
* filter.
*
*
* This prioritizes by, in descending order of preference:
*
*
* - The build author's explicit build type settings
* - The build author's explicit product flavor settings, matching the highest number of
* chosen defaults
* - The fallback default build type, which is the tested build type, if applicable,
* otherwise 'debug'
* - The alphabetically sorted default product flavors, left to right
*
*
* @return the name of a variant that exists under the presence of the variant filter. Only
* returns null if all variants are removed.
*/
private fun computeDefaultVariant(): String? {
// Finalize the DSL we are about to read.
finalizeDefaultVariantDsl()
// Exit early if all variants were filtered out, this is not a valid project
if (variants.isEmpty()) {
return null
}
// Otherwise get the 'best' build type, respecting the user's preferences first.
val chosenBuildType: String? = getBuildAuthorSpecifiedDefaultBuildType()
val chosenFlavors: Map<String, String> = getBuildAuthorSpecifiedDefaultFlavors()
val fallbackDefaultBuildType: String = testBuilderTypeProvider() ?: "debug"
val preferredDefaultVariantScopeComparator: Comparator<ComponentPropertiesImpl> =
BuildAuthorSpecifiedDefaultBuildTypeComparator(chosenBuildType)
.thenComparing(BuildAuthorSpecifiedDefaultsFlavorComparator(chosenFlavors))
.thenComparing(DefaultBuildTypeComparator(fallbackDefaultBuildType))
.thenComparing(DefaultFlavorComparator())
// Ignore test, base feature and feature variants.
// * Test variants have corresponding production variants
// * Hybrid feature variants have corresponding library variants.
val defaultComponent: VariantPropertiesImpl? = variants.minWith(preferredDefaultVariantScopeComparator)
return defaultComponent?.name
}
/** Prevent any subsequent modifications to the default variant DSL properties. */
private fun finalizeDefaultVariantDsl() {
for (buildTypeData in inputs.buildTypes.values) {
buildTypeData.buildType.getIsDefault().finalizeValue()
}
for (productFlavorData in inputs.productFlavors.values) {
productFlavorData.productFlavor.getIsDefault().finalizeValue()
}
}
/**
* Computes explicit build-author default build type.
*
* @return user specified default build type, null if none set.
*/
private fun getBuildAuthorSpecifiedDefaultBuildType(): String? {
// First look for the user setting
val buildTypesMarkedAsDefault: MutableList<String> = ArrayList(1)
for (buildType in inputs.buildTypes.values) {
if (buildType.buildType.isDefault) {
buildTypesMarkedAsDefault.add(buildType.buildType.name)
}
}
buildTypesMarkedAsDefault.sort()
if (buildTypesMarkedAsDefault.size > 1) {
issueHandler.reportWarning(
IssueReporter.Type.AMBIGUOUS_BUILD_TYPE_DEFAULT,
"Ambiguous default build type: '"
+ Joiner.on("', '").join(buildTypesMarkedAsDefault)
+ "'.\n"
+ "Please only set `isDefault = true` for one build type.",
Joiner.on(',').join(buildTypesMarkedAsDefault)
)
}
return if (buildTypesMarkedAsDefault.isEmpty()) {
null
} else {
// This picks the first alphabetically that was tagged, to make it stable,
// even if the user accidentally tags two build types as default.
buildTypesMarkedAsDefault[0]
}
}
/**
* Computes explicit user set default product flavors for each dimension.
*
* @param syncIssueHandler any configuration issues will be added here, e.g. if multiple flavors
* in one dimension are marked as default.
* @return map from flavor dimension to the user-specified default flavor for that dimension,
* with entries missing for flavors without user-specified defaults.
*/
private fun getBuildAuthorSpecifiedDefaultFlavors(): Map<String, String> {
// Using ArrayListMultiMap to preserve sorting of flavor names.
val userDefaults = ArrayListMultimap.create<String, String>()
for (flavor in inputs.productFlavors.values) {
val productFlavor = flavor.productFlavor
val dimension = productFlavor.dimension
@Suppress("DEPRECATION")
if (productFlavor.getIsDefault().get()) {
userDefaults.put(dimension, productFlavor.name)
}
}
val defaults = ImmutableMap.builder<String, String>()
// For each user preference, validate it and override the alphabetical default.
for (dimension in userDefaults.keySet()) {
val userDefault = userDefaults[dimension]
userDefault.sort()
if (userDefault.isNotEmpty()) {
// This picks the first alphabetically that was tagged, to make it stable,
// even if the user accidentally tags two flavors in the same dimension as default.
defaults.put(dimension, userDefault[0])
}
// Report the ambiguous default setting.
if (userDefault.size > 1) {
issueHandler.reportWarning(
IssueReporter.Type.AMBIGUOUS_PRODUCT_FLAVOR_DEFAULT,
"""Ambiguous default product flavors for flavor dimension '$dimension': '${Joiner.on("', '").join(userDefault)}'.
Please only set `isDefault = true` for one product flavor in each flavor dimension.""",
dimension
)
}
}
return defaults.build()
}
}
/**
* Compares variants prioritizing those that match the given default build type.
*
*
* The best match is the *minimum* element.
*
*
* Note: this comparator imposes orderings that are inconsistent with equals, as variants
* that do not match the default will compare the same.
*/
private class BuildAuthorSpecifiedDefaultBuildTypeComparator constructor(
private val chosen: String?
) : Comparator<ComponentPropertiesImpl> {
override fun compare(v1: ComponentPropertiesImpl, v2: ComponentPropertiesImpl): Int {
if (chosen == null) {
return 0
}
val b1Score = if (v1.buildType == chosen) 1 else 0
val b2Score = if (v2.buildType == chosen) 1 else 0
return b2Score - b1Score
}
}
/**
* Compares variants prioritizing those that match the given default flavors over those that do
* not.
*
*
* The best match is the *minimum* element.
*
*
* Note: this comparator imposes orderings that are inconsistent with equals, as variants
* that do not match the default will compare the same.
*/
private class BuildAuthorSpecifiedDefaultsFlavorComparator constructor(
private val defaultFlavors: Map<String, String>
) : Comparator<ComponentPropertiesImpl> {
override fun compare(v1: ComponentPropertiesImpl, v2: ComponentPropertiesImpl): Int {
var f1Score = 0
var f2Score = 0
for (flavor in v1.variantDslInfo.productFlavorList) {
if (flavor.name == defaultFlavors[flavor.dimension]) {
f1Score++
}
}
for (flavor in v2.variantDslInfo.productFlavorList) {
if (flavor.name == defaultFlavors[flavor.dimension]) {
f2Score++
}
}
return f2Score - f1Score
}
}
/**
* Compares variants on build types.
*
*
* Prefers 'debug', then falls back to the first alphabetically.
*
*
* The best match is the *minimum* element.
*/
private class DefaultBuildTypeComparator constructor(
private val preferredBuildType: String
) : Comparator<ComponentPropertiesImpl> {
override fun compare(v1: ComponentPropertiesImpl, v2: ComponentPropertiesImpl): Int {
val b1 = v1.buildType
val b2 = v2.buildType
return if (b1 == b2) {
0
} else if (b1 == preferredBuildType) {
-1
} else if (b2 == preferredBuildType) {
1
} else {
b1!!.compareTo(b2!!)
}
}
}
/**
* Compares variants prioritizing product flavors alphabetically, left-to-right.
*
*
* The best match is the *minimum* element.
*/
private class DefaultFlavorComparator : Comparator<ComponentPropertiesImpl> {
override fun compare(v1: ComponentPropertiesImpl, v2: ComponentPropertiesImpl): Int {
// Compare flavors left-to right.
for (i in v1.variantDslInfo.productFlavorList.indices) {
val f1 = v1.variantDslInfo.productFlavorList[i].name
val f2 = v2.variantDslInfo.productFlavorList[i].name
val diff = f1.compareTo(f2)
if (diff != 0) {
return diff
}
}
return 0
}
}