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