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