1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.baselineprofile.gradle.consumer 18 19 import com.android.build.api.variant.Variant 20 import org.gradle.api.GradleException 21 import org.gradle.api.Project 22 23 /** 24 * The [BaselineProfileConsumerPlugin] supports per variant configuration, according to values 25 * expressed in [BaselineProfileVariantConfiguration]. The correct value for a property is 26 * determined considering the concept of override or merge. When a property is evaluated considering 27 * the override, the variants are evaluated in this order: `variantName`, `buildType` or 28 * `productFlavor` and `main`. The first variant configuration to define the property is used to 29 * return that property. For lists only, a property can be evaluated also merging all the variant 30 * configurations. This is the case for dependencies for example, so that when accessing custom 31 * dependencies for variant `freeRelease` the returned list contains the dependencies for 32 * `freeRelease`, `free`, `release` and `main` (global ones). 33 */ 34 internal class PerVariantConsumerExtensionManager( 35 private val extension: BaselineProfileConsumerExtension, 36 ) { 37 variantnull38 fun variant(variant: Variant) = VariantConfigurationProxy(variant = variant, ext = extension) 39 40 internal class VariantConfigurationProxy 41 internal constructor( 42 private val variant: Variant, 43 private val ext: BaselineProfileConsumerExtension, 44 ) { 45 46 val filterRules: List<Pair<RuleType, String>> 47 get() = getMergedListForVariant(variant) { filters.rules } 48 49 val dependencies: List<Project> 50 get() = getMergedListForVariant(variant) { dependencies } 51 52 val baselineProfileRulesRewrite: Boolean? 53 get() = getOverriddenValueForVariantAllowNull(variant) { baselineProfileRulesRewrite } 54 55 val dexLayoutOptimization: Boolean? 56 get() = getOverriddenValueForVariantAllowNull(variant) { dexLayoutOptimization } 57 58 val saveInSrc: Boolean 59 get() = getOverriddenValueForVariant(variant) { saveInSrc } 60 61 val automaticGenerationDuringBuild: Boolean 62 get() = getOverriddenValueForVariant(variant) { automaticGenerationDuringBuild } 63 64 val baselineProfileOutputDir: String 65 get() = getOverriddenValueForVariant(variant) { baselineProfileOutputDir } 66 67 val mergeIntoMain: Boolean? 68 get() = getOverriddenValueForVariantAllowNull(variant) { mergeIntoMain } 69 70 private fun <T> getMergedListForVariant( 71 variant: Variant, 72 getter: BaselineProfileVariantConfigurationImpl.() -> List<T> 73 ): List<T> { 74 return listOfNotNull( 75 "main", 76 variant.flavorName, 77 *variant.productFlavors.map { it.second }.toTypedArray(), 78 variant.buildType, 79 variant.name 80 ) 81 .mapNotNull { ext.variants.findByName(it) } 82 .map { getter.invoke(it) } 83 .flatten() 84 } 85 86 private fun <T> getOverriddenValueForVariantAllowNull( 87 variant: Variant, 88 getter: BaselineProfileVariantConfigurationImpl.() -> T 89 ): T? { 90 // Here we select a setting for the given variant. [BaselineProfileVariantConfiguration] 91 // are evaluated in the following order: variant, flavor, build type, `main`. 92 // If a property is found it will return it. Note that `main` should have all the 93 // defaults set so this method never returns a nullable value and should always return. 94 95 val definedProperties = 96 listOfNotNull( 97 variant.name, 98 *variant.productFlavors.map { it.second }.toTypedArray(), 99 variant.flavorName, 100 variant.buildType, 101 "main" 102 ) 103 .mapNotNull { 104 val variantConfig = ext.variants.findByName(it) ?: return@mapNotNull null 105 return@mapNotNull Pair(it, getter.invoke(variantConfig)) 106 } 107 .filter { it.second != null } 108 109 // This is a case where the property is defined in both build type and flavor. 110 // In this case it should fail because the result is ambiguous. 111 val propMap = definedProperties.toMap() 112 if ( 113 variant.flavorName in propMap && 114 variant.buildType in propMap && 115 propMap[variant.flavorName] != propMap[variant.buildType] 116 ) { 117 throw GradleException( 118 """ 119 The per-variant configuration for baseline profiles is ambiguous. This happens when 120 that the same property has been defined in both a build type and a flavor. 121 122 For example: 123 124 baselineProfiles { 125 variants { 126 free { 127 saveInSrc = true 128 } 129 release { 130 saveInSrc = false 131 } 132 } 133 } 134 135 In this case for `freeRelease` it's not possible to determine the exact value of the 136 property. Please specify either the build type or the flavor. 137 """ 138 .trimIndent() 139 ) 140 } 141 142 return definedProperties.firstOrNull()?.second 143 } 144 145 private fun <T> getOverriddenValueForVariant( 146 variant: Variant, 147 default: T? = null, 148 getter: BaselineProfileVariantConfigurationImpl.() -> T? 149 ): T { 150 val value = getOverriddenValueForVariantAllowNull(variant, getter) 151 if (value != null) return value 152 if (default != null) return default 153 154 // This should never happen. It means the extension is missing a default property and 155 // n default was specified when accessing this value. This cannot happen because of the 156 // user configuration. 157 throw GradleException("The required property does not have a default.") 158 } 159 } 160 } 161