1 /*
<lambda>null2  * 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.apptarget
18 
19 import androidx.baselineprofile.gradle.apptarget.task.GenerateKeepRulesForBaselineProfilesTask
20 import androidx.baselineprofile.gradle.utils.AgpFeature
21 import androidx.baselineprofile.gradle.utils.AgpPlugin
22 import androidx.baselineprofile.gradle.utils.AgpPluginId
23 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BASELINE_PROFILE_PREFIX
24 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BENCHMARK_PREFIX
25 import androidx.baselineprofile.gradle.utils.Dependencies
26 import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
27 import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED_INCLUSIVE
28 import androidx.baselineprofile.gradle.utils.camelCase
29 import androidx.baselineprofile.gradle.utils.copyBuildTypeSources
30 import androidx.baselineprofile.gradle.utils.copySigningConfigIfNotSpecified
31 import androidx.baselineprofile.gradle.utils.createExtendedBuildTypes
32 import com.android.build.api.AndroidPluginVersion
33 import com.android.build.api.dsl.ApplicationExtension
34 import com.android.build.api.variant.ApplicationVariant
35 import com.android.build.api.variant.ApplicationVariantBuilder
36 import com.android.build.api.variant.HasUnitTestBuilder
37 import org.gradle.api.Plugin
38 import org.gradle.api.Project
39 
40 /**
41  * This is the app target plugin for baseline profile generation. In order to generate baseline
42  * profiles three plugins are needed: one is applied to the app or the library that should consume
43  * the baseline profile when building (consumer), one is applied to the module that should supply
44  * the under test app (app target) and the last one is applied to a test module containing the ui
45  * test that generate the baseline profile on the device (producer).
46  */
47 class BaselineProfileAppTargetPlugin : Plugin<Project> {
48     override fun apply(project: Project) = BaselineProfileAppTargetAgpPlugin(project).onApply()
49 }
50 
51 private class BaselineProfileAppTargetAgpPlugin(private val project: Project) :
52     AgpPlugin(
53         project = project,
54         supportedAgpPlugins =
55             setOf(AgpPluginId.ID_ANDROID_APPLICATION_PLUGIN, AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN),
56         minAgpVersionInclusive = MIN_AGP_VERSION_REQUIRED_INCLUSIVE,
57         maxAgpVersionExclusive = MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE
58     ) {
59 
60     private val ApplicationExtension.debugSigningConfig
61         get() = buildTypes.getByName("debug").signingConfig
62 
63     private val dependencies = Dependencies(project)
64 
65     // Benchmark build type to the original ones. Ex: benchmarkRelease -> release
66     private val benchmarkExtendedToOriginalTypeMap = mutableMapOf<String, String>()
67 
68     // This is the opposite. Ex: release -> benchmarkRelease
<lambda>null69     private val benchmarkOriginalToExtendedTypeMap by lazy {
70         benchmarkExtendedToOriginalTypeMap.toList().associate { Pair(it.second, it.first) }
71     }
72 
73     // Baseline Profile build type to the original ones. Ex: nonMinifiedRelease -> release
74     private val baselineProfileExtendedToOriginalTypeMap = mutableMapOf<String, String>()
75 
76     // This is the opposite. Ex: release -> nonMinifiedRelease
<lambda>null77     private val baselineProfileOriginalToExtendedTypeMap by lazy {
78         baselineProfileExtendedToOriginalTypeMap.toList().associate { Pair(it.second, it.first) }
79     }
80 
onAgpPluginNotFoundnull81     override fun onAgpPluginNotFound(pluginIds: Set<AgpPluginId>) {
82 
83         // If no supported plugin was found throw an exception.
84         throw IllegalStateException(
85             """
86             The module ${project.name} does not have the `com.android.application` plugin
87             applied. The `androidx.baselineprofile.apptarget` plugin supports only
88             android application modules. Please review your build.gradle to ensure this
89             plugin is applied to the correct module.
90             """
91                 .trimIndent()
92         )
93     }
94 
onAgpPluginFoundnull95     override fun onAgpPluginFound(pluginIds: Set<AgpPluginId>) {
96 
97         // If the library plugin was found throw an exception. It's possible the developer meant
98         // to generate a baseline profile for a library and we can give further information.
99         if (pluginIds.contains(AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN)) {
100             throw IllegalStateException(
101                 """
102             The module ${project.name} does not have the `com.android.application` plugin
103             but has the `com.android.library` plugin. If you're trying to generate a
104             baseline profile for a library, you'll need to apply the
105             `androidx.baselineprofile.apptarget` to an android application that
106             has the `com.android.application` plugin applied. This should be a sample app
107             running the code of the library for which you want to generate the profile.
108             Please review your build.gradle to ensure this plugin is applied to the
109             correct module.
110             """
111                     .trimIndent()
112             )
113         }
114 
115         // Otherwise, just log the plugin was applied.
116         project.logger.debug(
117             "[BaselineProfileAppTargetPlugin] afterEvaluate check: app plugin was applied"
118         )
119     }
120 
onApplicationFinalizeDslnull121     override fun onApplicationFinalizeDsl(extension: ApplicationExtension) {
122 
123         // Different build types are created according to the AGP version
124         if (supportsFeature(AgpFeature.TEST_MODULE_SUPPORTS_MULTIPLE_BUILD_TYPES)) {
125             createBuildTypesWithAgp81AndAbove(extension)
126         } else {
127             createBuildTypesWithAgp80(extension)
128         }
129     }
130 
onApplicationBeforeVariantsnull131     override fun onApplicationBeforeVariants(variantBuilder: ApplicationVariantBuilder) {
132 
133         // Process all the extended build types for both baseline profile and benchmark to
134         // disable unit tests.
135         if (
136             variantBuilder.buildType in baselineProfileExtendedToOriginalTypeMap.keys ||
137                 variantBuilder.buildType in benchmarkExtendedToOriginalTypeMap.keys
138         ) {
139 
140             if (supportsFeature(AgpFeature.APPLICATION_VARIANT_HAS_UNIT_TEST_BUILDER)) {
141                 (variantBuilder as? HasUnitTestBuilder)?.enableUnitTest = false
142             } else {
143                 @Suppress("deprecation")
144                 variantBuilder.enableUnitTest = false
145                 @Suppress("deprecation")
146                 variantBuilder.unitTestEnabled = false
147             }
148         }
149     }
150 
onApplicationVariantsnull151     override fun onApplicationVariants(variant: ApplicationVariant) {
152 
153         // Extending the build type won't also copy the build type specific dependencies, that
154         // need to be copied separately for both baseline profile and benchmark variants.
155         // Note that the maps used here are organized like: `release` -> `nonMinifiedRelease` and
156         // `release` -> `benchmark`.
157         data class MappingAndPrefix(val mapping: Map<String, String>, val prefix: String)
158         listOf(
159                 MappingAndPrefix(
160                     baselineProfileOriginalToExtendedTypeMap,
161                     BUILD_TYPE_BASELINE_PROFILE_PREFIX
162                 ),
163                 MappingAndPrefix(benchmarkOriginalToExtendedTypeMap, BUILD_TYPE_BENCHMARK_PREFIX),
164             )
165             .forEach {
166                 if (variant.buildType !in it.mapping.keys) {
167                     return@forEach
168                 }
169 
170                 // This would be, for example, `release`.
171                 val originalBuildTypeName =
172                     variant.buildType
173                         ?: throw IllegalStateException(
174                             // Note that this exception cannot happen due to user configuration.
175                             "Variant `${variant.name}` does not have a build type."
176                         )
177 
178                 // This would be, for example, `nonMinifiedRelease`.
179                 val extendedBuildTypeName =
180                     it.mapping[originalBuildTypeName]
181                         ?: throw IllegalStateException(
182                             // Note that this exception cannot happen due to user configuration.
183                             "Build type `${variant.buildType}` was not extended."
184                         )
185 
186                 // Copy build type specific dependencies
187                 dependencies.copy(
188                     fromPrefix = originalBuildTypeName,
189                     toPrefix = extendedBuildTypeName
190                 )
191 
192                 // Copy variant specific dependencies
193                 dependencies.copy(
194                     fromPrefix = variant.name,
195                     toPrefix = camelCase(variant.flavorName ?: "", extendedBuildTypeName)
196                 )
197 
198                 // Note that we don't need to copy flavor specific dependencies because they're
199                 // applied
200                 // to all the build types, including the extended ones.
201             }
202 
203         // This behavior is only for AGP 8.0: since we cannot support multiple build types in the
204         // same gradle invocation (including `assemble` or `build` due to b/265438201), we use a
205         // single build type for both benchmark and baseline profile in the producer module.
206         // This build type is minified but not obfuscated. Here we add a fixed proguard file that
207         // disables the obfuscation. Also we want to skip the build types that were NOT created by
208         // this plugin.
209         if (
210             agpVersion() < AndroidPluginVersion(8, 1, 0) &&
211                 variant.buildType in baselineProfileExtendedToOriginalTypeMap.keys
212         ) {
213             variant.proguardFiles.add(
214                 GenerateKeepRulesForBaselineProfilesTask.maybeRegister(project).flatMap {
215                     it.keepRuleFile
216                 }
217             )
218         }
219     }
220 
createBuildTypesWithAgp80null221     private fun createBuildTypesWithAgp80(extension: ApplicationExtension) {
222 
223         // Creates baseline profile build types extending the currently existing ones.
224         // They're named `<BUILD_TYPE_BASELINE_PROFILE_PREFIX><originalBuildTypeName>`.
225         // Note that if the build type already does not exist, the `newConfigureBlock` is applied,
226         // while if it exist the `overrideConfigureBlock` is applied.
227         createExtendedBuildTypes(
228             project = project,
229             extensionBuildTypes = extension.buildTypes,
230             extendedBuildTypeToOriginalBuildTypeMapping = baselineProfileExtendedToOriginalTypeMap,
231             newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX,
232             filterBlock = {
233                 // Create baseline profile build types only for non debuggable builds.
234                 // Note that it's possible to override benchmarkRelease and nonMinifiedRelease,
235                 // so we also want to make sure we don't extended these again.
236                 !it.isDebuggable &&
237                     !it.name.startsWith(BUILD_TYPE_BASELINE_PROFILE_PREFIX) &&
238                     !it.name.startsWith(BUILD_TYPE_BENCHMARK_PREFIX)
239             },
240             newConfigureBlock = { base, ext ->
241 
242                 // Properties applied when the build type does not exist.
243                 ext.isJniDebuggable = false
244                 ext.isDebuggable = false
245                 ext.isProfileable = true
246                 ext.enableAndroidTestCoverage = false
247                 ext.enableUnitTestCoverage = false
248 
249                 ext.isMinifyEnabled = base.isMinifyEnabled
250                 ext.isShrinkResources = base.isShrinkResources
251 
252                 copySigningConfigIfNotSpecified(base, ext, extension.debugSigningConfig)
253             },
254             overrideConfigureBlock = { base, ext ->
255 
256                 // Properties applied when the build type exists.
257                 ext.isDebuggable = false
258                 ext.isJniDebuggable = false
259                 ext.isProfileable = true
260                 ext.enableAndroidTestCoverage = false
261                 ext.enableUnitTestCoverage = false
262 
263                 copySigningConfigIfNotSpecified(base, ext, extension.debugSigningConfig)
264             }
265         )
266 
267         // Copies the source sets for the newly created build types
268         copyBuildTypeSources(
269             extensionSourceSets = extension.sourceSets,
270             fromToMapping = baselineProfileExtendedToOriginalTypeMap
271         )
272     }
273 
createBuildTypesWithAgp81AndAbovenull274     private fun createBuildTypesWithAgp81AndAbove(extension: ApplicationExtension) {
275 
276         // Creates baseline profile build types extending the currently existing ones.
277         // They're named `<BUILD_TYPE_BASELINE_PROFILE_PREFIX><originalBuildTypeName>`.
278         // Note that if the build type already does not exist, the `newConfigureBlock` is applied,
279         // while if it exist the `overrideConfigureBlock` is applied.
280         createExtendedBuildTypes(
281             project = project,
282             extendedBuildTypeToOriginalBuildTypeMapping = baselineProfileExtendedToOriginalTypeMap,
283             extensionBuildTypes = extension.buildTypes,
284             newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX,
285             filterBlock = {
286                 // Create baseline profile build types only for non debuggable builds.
287                 // Note that it's possible to override benchmarkRelease and nonMinifiedRelease,
288                 // so we also want to make sure we don't extended these again.
289                 !it.isDebuggable &&
290                     !it.name.startsWith(BUILD_TYPE_BASELINE_PROFILE_PREFIX) &&
291                     !it.name.startsWith(BUILD_TYPE_BENCHMARK_PREFIX)
292             },
293             newConfigureBlock = { base, ext ->
294 
295                 // Properties applied when the build type does not exist.
296                 ext.isJniDebuggable = false
297                 ext.isDebuggable = false
298                 ext.isMinifyEnabled = false
299                 ext.isShrinkResources = false
300                 ext.isProfileable = true
301                 ext.enableAndroidTestCoverage = false
302                 ext.enableUnitTestCoverage = false
303 
304                 // Since minifyEnabled is `false`, no need to copy proguard files.
305 
306                 copySigningConfigIfNotSpecified(base, ext, extension.debugSigningConfig)
307             },
308             overrideConfigureBlock = { base, ext ->
309 
310                 // Properties applied when the build type exists.
311                 // For baseline profile build type it's the same of `newConfigureBlock`.
312                 ext.isJniDebuggable = false
313                 ext.isDebuggable = false
314                 ext.isMinifyEnabled = false
315                 ext.isShrinkResources = false
316                 ext.isProfileable = true
317                 ext.enableAndroidTestCoverage = false
318                 ext.enableUnitTestCoverage = false
319 
320                 // Since minifyEnabled is `false`, no need to copy proguard files.
321 
322                 copySigningConfigIfNotSpecified(base, ext, extension.debugSigningConfig)
323             },
324         )
325 
326         // Copies the source sets for the newly created build types
327         copyBuildTypeSources(
328             extensionSourceSets = extension.sourceSets,
329             fromToMapping = baselineProfileExtendedToOriginalTypeMap
330         )
331 
332         // Creates benchmark build types extending the currently existing ones.
333         // They're named `<BUILD_TYPE_BENCHMARK_PREFIX><originalBuildTypeName>`.
334         // Note that if the build type already does not exist, the `newConfigureBlock` is applied,
335         // while if it exist the `overrideConfigureBlock` is applied.
336         createExtendedBuildTypes(
337             project = project,
338             extensionBuildTypes = extension.buildTypes,
339             newBuildTypePrefix = BUILD_TYPE_BENCHMARK_PREFIX,
340             extendedBuildTypeToOriginalBuildTypeMapping = benchmarkExtendedToOriginalTypeMap,
341             filterBlock = {
342                 // Create benchmark type for non debuggable types, and without considering
343                 // baseline profiles build types. Note that it's possible to override
344                 // benchmarkRelease and nonMinifiedRelease, so we also want to make sure we don't
345                 // extended these again.
346                 !it.isDebuggable &&
347                     it.name !in baselineProfileExtendedToOriginalTypeMap &&
348                     !it.name.startsWith(BUILD_TYPE_BASELINE_PROFILE_PREFIX) &&
349                     !it.name.startsWith(BUILD_TYPE_BENCHMARK_PREFIX)
350             },
351             newConfigureBlock = { base, ext ->
352 
353                 // Properties applied when the build type does not exist.
354                 ext.isJniDebuggable = false
355                 ext.isDebuggable = false
356                 ext.isMinifyEnabled = base.isMinifyEnabled
357                 ext.isShrinkResources = base.isShrinkResources
358                 ext.isProfileable = true
359                 ext.enableAndroidTestCoverage = false
360                 ext.enableUnitTestCoverage = false
361 
362                 copySigningConfigIfNotSpecified(base, ext, extension.debugSigningConfig)
363             },
364             overrideConfigureBlock = { base, ext ->
365 
366                 // Properties applied when the build type exists.
367                 ext.isJniDebuggable = false
368                 ext.isDebuggable = false
369                 ext.isProfileable = true
370                 ext.enableAndroidTestCoverage = false
371                 ext.enableUnitTestCoverage = false
372 
373                 copySigningConfigIfNotSpecified(base, ext, extension.debugSigningConfig)
374             }
375         )
376 
377         // Copies the source sets for the newly created build types
378         copyBuildTypeSources(
379             extensionSourceSets = extension.sourceSets,
380             fromToMapping = benchmarkExtendedToOriginalTypeMap
381         )
382     }
383 }
384