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.producer 18 19 import androidx.baselineprofile.gradle.configuration.ConfigurationManager 20 import androidx.baselineprofile.gradle.producer.tasks.CollectBaselineProfileTask 21 import androidx.baselineprofile.gradle.producer.tasks.InstrumentationTestTaskWrapper 22 import androidx.baselineprofile.gradle.utils.AgpFeature.CONFIGURATION_CACHE_FIX_B348136774 23 import androidx.baselineprofile.gradle.utils.AgpFeature.TEST_MODULE_SUPPORTS_MULTIPLE_BUILD_TYPES 24 import androidx.baselineprofile.gradle.utils.AgpFeature.TEST_VARIANT_SUPPORTS_INSTRUMENTATION_RUNNER_ARGUMENTS 25 import androidx.baselineprofile.gradle.utils.AgpFeature.TEST_VARIANT_TESTED_APKS 26 import androidx.baselineprofile.gradle.utils.AgpPlugin 27 import androidx.baselineprofile.gradle.utils.AgpPluginId 28 import androidx.baselineprofile.gradle.utils.AndroidTestModuleWrapper 29 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BASELINE_PROFILE_PREFIX 30 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BENCHMARK_PREFIX 31 import androidx.baselineprofile.gradle.utils.CONFIGURATION_ARTIFACT_TYPE 32 import androidx.baselineprofile.gradle.utils.CONFIGURATION_NAME_BASELINE_PROFILES 33 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_ENABLED_RULES 34 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_ENABLED_RULES_BASELINE_PROFILE 35 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_ENABLED_RULES_BENCHMARK 36 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_SKIP_ON_EMULATOR 37 import androidx.baselineprofile.gradle.utils.INSTRUMENTATION_ARG_TARGET_PACKAGE_NAME 38 import androidx.baselineprofile.gradle.utils.InstrumentationTestRunnerArgumentsAgp82 39 import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE 40 import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED_INCLUSIVE 41 import androidx.baselineprofile.gradle.utils.RELEASE 42 import androidx.baselineprofile.gradle.utils.TestedApksAgp83 43 import androidx.baselineprofile.gradle.utils.camelCase 44 import androidx.baselineprofile.gradle.utils.createBuildTypeIfNotExists 45 import androidx.baselineprofile.gradle.utils.createExtendedBuildTypes 46 import com.android.build.api.dsl.TestBuildType 47 import com.android.build.api.dsl.TestExtension 48 import com.android.build.api.variant.TestVariant 49 import com.android.build.api.variant.TestVariantBuilder 50 import com.android.build.api.variant.Variant 51 import org.gradle.api.GradleException 52 import org.gradle.api.Plugin 53 import org.gradle.api.Project 54 55 /** 56 * This is the producer plugin for baseline profile generation. In order to generate baseline 57 * profiles three plugins are needed: one is applied to the app or the library that should consume 58 * the baseline profile when building (consumer), one is applied to the module that should supply 59 * the under test app (app target) and the last one is applied to a test module containing the ui 60 * test that generate the baseline profile on the device (producer). 61 */ 62 class BaselineProfileProducerPlugin : Plugin<Project> { 63 override fun apply(project: Project) = BaselineProfileProducerAgpPlugin(project).onApply() 64 } 65 66 private class BaselineProfileProducerAgpPlugin(private val project: Project) : 67 AgpPlugin( 68 project = project, 69 supportedAgpPlugins = setOf(AgpPluginId.ID_ANDROID_TEST_PLUGIN), 70 minAgpVersionInclusive = MIN_AGP_VERSION_REQUIRED_INCLUSIVE, 71 maxAgpVersionExclusive = MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE 72 ) { 73 74 companion object { 75 private const val PROP_ENABLED_RULES = 76 "android.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules" 77 } 78 79 private val baselineProfileExtension = BaselineProfileProducerExtension.register(project) 80 private val configurationManager = ConfigurationManager(project) <lambda>null81 private val shouldSkipGeneration by lazy { 82 project.providers.gradleProperty(PROP_SKIP_GENERATION).isPresent 83 } <lambda>null84 private val forceOnlyConnectedDevices: Boolean by lazy { 85 project.providers.gradleProperty(PROP_FORCE_ONLY_CONNECTED_DEVICES).isPresent 86 } <lambda>null87 private val addEnabledRulesInstrumentationArgument by lazy { 88 !project.providers.gradleProperty(PROP_DONT_DISABLE_RULES).isPresent 89 } <lambda>null90 private val addTargetPackageNameInstrumentationArgument by lazy { 91 !project.providers.gradleProperty(PROP_SEND_TARGET_PACKAGE_NAME).isPresent 92 } 93 94 // This maps all the extended build types to the original ones. Note that release does not 95 // exist by default so we need to create nonMinifiedRelease and map it manually to `release`. 96 private val nonObfuscatedReleaseName = camelCase(BUILD_TYPE_BASELINE_PROFILE_PREFIX, RELEASE) 97 private val baselineProfileExtendedToOriginalTypeMap = 98 mutableMapOf(nonObfuscatedReleaseName to RELEASE) 99 100 private val benchmarkReleaseName = camelCase(BUILD_TYPE_BENCHMARK_PREFIX, RELEASE) 101 private val benchmarkExtendedToOriginalTypeMap = mutableMapOf(benchmarkReleaseName to RELEASE) 102 onAgpPluginFoundnull103 override fun onAgpPluginFound(pluginIds: Set<AgpPluginId>) { 104 project.logger.debug( 105 "[BaselineProfileProducerPlugin] afterEvaluate check: app plugin was applied" 106 ) 107 } 108 onAgpPluginNotFoundnull109 override fun onAgpPluginNotFound(pluginIds: Set<AgpPluginId>) { 110 throw IllegalStateException( 111 """ 112 The module ${project.name} does not have the `com.android.test` plugin applied. Currently, 113 the `androidx.baselineprofile.producer` plugin supports only android test modules. In future this 114 plugin will also support library modules (https://issuetracker.google.com/issue?id=259737450). 115 Please review your build.gradle to ensure this plugin is applied to the correct module. 116 """ 117 .trimIndent() 118 ) 119 } 120 onBeforeFinalizeDslnull121 override fun onBeforeFinalizeDsl() { 122 123 // We need the instrumentation apk to run as a separate process 124 AndroidTestModuleWrapper(project).setSelfInstrumenting(true) 125 } 126 onTestFinalizeDslnull127 override fun onTestFinalizeDsl(extension: TestExtension) { 128 129 // Creates the new build types to match the app target. All the existing build types beside 130 // `debug`, that is the default one, are added manually in the configuration so we can 131 // assume they've been added for the purpose of generating baseline profiles. We don't 132 // need to create a nonMinified build type from `debug` since there will be no matching 133 // configuration with the app target module. 134 135 // The test build types need to be debuggable and have the same singing config key to 136 // be installed. We also disable the test coverage tracking since it's not important 137 // here. 138 val configureBlock: TestBuildType.() -> (Unit) = { 139 isDebuggable = true 140 enableAndroidTestCoverage = false 141 enableUnitTestCoverage = false 142 signingConfig = extension.buildTypes.getByName("debug").signingConfig 143 144 // TODO: The matching fallback is causing a circular dependency when app target plugin 145 // is not applied. Normally this is not used, but if an app defines only the consumer 146 // plugin the `nonMinified` build won't exist. If the provider points to it, the 147 // matching fallback will kick in and will use the `release` build instead of the 148 // `nonMinifiedRelease`. When this happens, since we depend on 149 // `mergeArtReleaseProfile` to ensure that a profile is copied is the baseline profile 150 // src sets before the build is complete, it will trigger a circular task dependency: 151 // collectNonMinifiedReleaseBaselineProfile -> 152 // connectedNonMinifiedReleaseAndroidTest -> 153 // packageRelease -> 154 // compileReleaseArtProfile -> 155 // mergeReleaseArtProfile -> 156 // copyReleaseBaselineProfileIntoSrc -> 157 // mergeReleaseBaselineProfile -> 158 // collectNonMinifiedReleaseBaselineProfile 159 // Note that the error is expected but we should handle it gracefully with proper 160 // explanation. (b/272851616) 161 matchingFallbacks += listOf(RELEASE) 162 } 163 164 // The variant names are used by the test module to request a specific apk artifact to 165 // the under test app module (using configuration attributes). This is all handled by 166 // the com.android.test plugin, as long as both modules have the same variants. 167 // Unfortunately the test module cannot determine which variants are present in the 168 // under test app module. As a result we need to replicate the same build types and 169 // flavors, so that the same variant names are created. 170 createExtendedBuildTypes( 171 project = project, 172 extensionBuildTypes = extension.buildTypes, 173 newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX, 174 extendedBuildTypeToOriginalBuildTypeMapping = baselineProfileExtendedToOriginalTypeMap, 175 newConfigureBlock = { _, ext -> configureBlock(ext) }, 176 overrideConfigureBlock = { _, _ -> 177 // Properties are not overridden if the build type already exists. 178 }, 179 filterBlock = { 180 // All the build types that have been added to the test module should be 181 // extended. This is because we can't know here which ones are actually 182 // release in the under test module. We can only exclude debug for sure. 183 it.name != "debug" 184 } 185 ) 186 createBuildTypeIfNotExists( 187 project = project, 188 extensionBuildTypes = extension.buildTypes, 189 buildTypeName = nonObfuscatedReleaseName, 190 configureBlock = configureBlock 191 ) 192 193 // Similarly to baseline profile build types we also create benchmark build types if this 194 // version of AGP has the support for it. 195 if (supportsFeature(TEST_MODULE_SUPPORTS_MULTIPLE_BUILD_TYPES)) { 196 createExtendedBuildTypes( 197 project = project, 198 extensionBuildTypes = extension.buildTypes, 199 newBuildTypePrefix = BUILD_TYPE_BENCHMARK_PREFIX, 200 extendedBuildTypeToOriginalBuildTypeMapping = benchmarkExtendedToOriginalTypeMap, 201 newConfigureBlock = { _, ext -> configureBlock(ext) }, 202 overrideConfigureBlock = { _, _ -> 203 // Properties are not overridden if the build type already exists. 204 }, 205 filterBlock = { 206 // Note that at this point we already have created the baseline profile build 207 // types that we don't want to extend again. 208 it.name != "debug" && it.name !in baselineProfileExtendedToOriginalTypeMap 209 } 210 ) 211 createBuildTypeIfNotExists( 212 project = project, 213 extensionBuildTypes = extension.buildTypes, 214 buildTypeName = benchmarkReleaseName, 215 configureBlock = configureBlock 216 ) 217 } 218 219 if (shouldSkipGeneration) { 220 logger.info( 221 """ 222 Property `$PROP_SKIP_GENERATION` set. Baseline profile generation will be skipped. 223 """ 224 .trimIndent() 225 ) 226 } 227 } 228 onTestBeforeVariantsnull229 override fun onTestBeforeVariants(variantBuilder: TestVariantBuilder) { 230 231 // Makes sure that only the non obfuscated build type variant selected is enabled 232 val buildType = variantBuilder.buildType 233 234 val isBaselineProfileBuildType = buildType in baselineProfileExtendedToOriginalTypeMap.keys 235 val isBenchmarkBuildType = buildType in benchmarkExtendedToOriginalTypeMap.keys 236 variantBuilder.enable = 237 variantBuilder.enable && (isBaselineProfileBuildType || isBenchmarkBuildType) 238 } 239 onTestVariantsnull240 override fun onTestVariants(variant: TestVariant) { 241 242 // Creates all the configurations, one per variant for the newly created build type. 243 // Note that for this version of the plugin is not possible to rely entirely on the variant 244 // api so the actual creation of the tasks is postponed to be executed when all the 245 // agp tasks have been created, using the old api. 246 247 // The enabled rules property is passed automatically according to the variant, if it was 248 // not set by the user. 249 val enabledRulesNotSet = 250 !project.gradle.startParameter.projectProperties.any { 251 it.key!!.contentEquals(PROP_ENABLED_RULES) 252 } 253 254 // If this is a benchmark variant sets the instrumentation runner argument to run only 255 // tests with MacroBenchmark rules. 256 if ( 257 variant.buildType in benchmarkExtendedToOriginalTypeMap.keys && 258 supportsFeature(TEST_VARIANT_SUPPORTS_INSTRUMENTATION_RUNNER_ARGUMENTS) 259 ) { 260 261 InstrumentationTestRunnerArgumentsAgp82.set( 262 variant = variant, 263 arguments = 264 listOf( 265 INSTRUMENTATION_ARG_SKIP_ON_EMULATOR to 266 baselineProfileExtension.skipBenchmarksOnEmulator.toString() 267 ) 268 ) 269 270 if (addEnabledRulesInstrumentationArgument && enabledRulesNotSet) { 271 InstrumentationTestRunnerArgumentsAgp82.set( 272 variant = variant, 273 arguments = 274 listOf( 275 INSTRUMENTATION_ARG_ENABLED_RULES to 276 INSTRUMENTATION_ARG_ENABLED_RULES_BENCHMARK, 277 ) 278 ) 279 } 280 } 281 282 // If AGP api support it, the application id of the target app is sent to instrumentation 283 // app as an instrumentation runner argument. BaselineProfileRule and MacrobenchmarkRule 284 // can pick that up during the test execution. 285 if ( 286 addTargetPackageNameInstrumentationArgument && 287 supportsFeature(TEST_VARIANT_TESTED_APKS) && 288 supportsFeature(CONFIGURATION_CACHE_FIX_B348136774) 289 ) { 290 InstrumentationTestRunnerArgumentsAgp82.set( 291 variant = variant, 292 key = INSTRUMENTATION_ARG_TARGET_PACKAGE_NAME, 293 value = TestedApksAgp83.getTargetAppApplicationId(variant) 294 ) 295 } 296 297 // If this is a baseline profile variant sets the instrumentation runner argument to run 298 // only tests with BaselineProfileRule, create the consumable configurations to expose 299 // the baseline profile artifacts and the tasks to generate the baseline profile artifacts. 300 // Configuration and tasks are created only for baseline profile variants. 301 if (variant.buildType in baselineProfileExtendedToOriginalTypeMap.keys) { 302 303 // If this is a benchmark variant sets the instrumentation runner argument to run only 304 // tests with MacroBenchmark rules. 305 if ( 306 addEnabledRulesInstrumentationArgument && 307 enabledRulesNotSet && 308 supportsFeature(TEST_VARIANT_SUPPORTS_INSTRUMENTATION_RUNNER_ARGUMENTS) 309 ) { 310 InstrumentationTestRunnerArgumentsAgp82.set( 311 variant = variant, 312 arguments = 313 listOf( 314 INSTRUMENTATION_ARG_ENABLED_RULES to 315 INSTRUMENTATION_ARG_ENABLED_RULES_BASELINE_PROFILE 316 ) 317 ) 318 } 319 320 // Creates the configuration to handle this variant. Note that in the attributes 321 // to match the configuration we use the original build type without `nonObfuscated`. 322 val configuration = 323 createConfigurationForVariant( 324 variant = variant, 325 originalBuildTypeName = 326 baselineProfileExtendedToOriginalTypeMap[variant.buildType] ?: "", 327 ) 328 329 // Prepares a block to execute later that creates the tasks for this variant 330 afterVariants { 331 createTasksForVariant( 332 project = project, 333 variant = variant, 334 configurationName = configuration.name, 335 baselineProfileExtension = baselineProfileExtension 336 ) 337 } 338 } 339 } 340 createConfigurationForVariantnull341 private fun createConfigurationForVariant(variant: Variant, originalBuildTypeName: String) = 342 configurationManager.maybeCreate( 343 nameParts = 344 listOf( 345 variant.flavorName ?: "", 346 originalBuildTypeName, 347 CONFIGURATION_NAME_BASELINE_PROFILES 348 ), 349 canBeConsumed = true, 350 canBeResolved = false, 351 buildType = originalBuildTypeName, 352 productFlavors = variant.productFlavors 353 ) 354 355 private fun createTasksForVariant( 356 project: Project, 357 variant: TestVariant, 358 configurationName: String, 359 baselineProfileExtension: BaselineProfileProducerExtension 360 ) { 361 362 // Prepares the devices list to use to generate the baseline profile. 363 // Note that when running gradle with 364 // `androidx.baselineprofile.forceonlyconnecteddevices=false` 365 // this DSL specification is not respected. This is used by Android Studio to run 366 // baseline profile generation only on the selected devices. 367 val devices = mutableSetOf<String>() 368 if (forceOnlyConnectedDevices) { 369 devices.add("connected") 370 } else { 371 devices.addAll(baselineProfileExtension.managedDevices) 372 if (baselineProfileExtension.useConnectedDevices) devices.add("connected") 373 } 374 375 // The test task runs the ui tests 376 val testTasks = 377 devices 378 .map { device -> 379 val task = 380 InstrumentationTestTaskWrapper.getByName( 381 project = project, 382 device = device, 383 variantName = variant.name 384 ) 385 386 // The task is null if the managed device name does not exist 387 if (task == null) { 388 389 // If gradle is syncing don't throw any exception and simply stop here. This 390 // plugin will fail at build time instead. This allows not breaking project 391 // sync in ide. 392 if (isGradleSyncRunning()) return 393 394 throw GradleException( 395 """ 396 No managed device named `$device` was found. Please check your GMD configuration 397 and make sure that the `baselineProfile.managedDevices` property contains only 398 existing gradle managed devices. Example: 399 400 android { 401 testOptions.managedDevices.allDevices { 402 pixel6Api31(ManagedVirtualDevice) { 403 device = "Pixel 6" 404 apiLevel = 31 405 systemImageSource = "aosp" 406 } 407 } 408 } 409 410 baselineProfile { 411 managedDevices = ["pixel6Api31"] 412 useConnectedDevices = false 413 } 414 415 """ 416 .trimIndent() 417 ) 418 } 419 420 task 421 } 422 .onEach { 423 it.setEnableEmulatorDisplay(baselineProfileExtension.enableEmulatorDisplay) 424 if (shouldSkipGeneration) it.setTaskEnabled(false) 425 } 426 427 // The collect task collects the baseline profile files from the ui test results 428 val collectTaskProvider = 429 CollectBaselineProfileTask.registerForVariant( 430 project = project, 431 variant = variant, 432 testTaskDependencies = testTasks, 433 shouldSkipGeneration = shouldSkipGeneration 434 ) 435 436 // The artifacts are added to the configuration that exposes the generated baseline profile 437 addArtifactToConfiguration( 438 configurationName = configurationName, 439 taskProvider = collectTaskProvider, 440 artifactType = CONFIGURATION_ARTIFACT_TYPE 441 ) 442 } 443 } 444