1 /* <lambda>null2 * Copyright 2019 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.benchmark.gradle 18 19 import com.android.build.api.AndroidPluginVersion 20 import com.android.build.api.variant.AndroidComponentsExtension 21 import com.android.build.api.variant.LibraryAndroidComponentsExtension 22 import com.android.build.gradle.AppExtension 23 import com.android.build.gradle.LibraryExtension 24 import com.android.build.gradle.TestedExtension 25 import org.gradle.api.Plugin 26 import org.gradle.api.Project 27 import org.gradle.api.Task 28 import org.gradle.api.tasks.StopExecutionException 29 import org.gradle.api.tasks.TaskContainer 30 31 private const val ADDITIONAL_TEST_OUTPUT_KEY = "android.enableAdditionalTestOutput" 32 33 class BenchmarkPlugin : Plugin<Project> { 34 35 companion object { 36 37 private const val PROP_FORCE_AOT_COMPILATION = "androidx.benchmark.forceaotcompilation" 38 } 39 40 private var foundAndroidPlugin = false 41 42 override fun apply(project: Project) { 43 // NOTE: Although none of the configuration code depends on a reference to the Android 44 // plugin here, there is some implicit coupling behind the scenes, which ensures that the 45 // required BaseExtension from AGP can be found by registering project configuration as a 46 // PluginManager callback. 47 48 project.pluginManager.withPlugin("com.android.library") { 49 configureWithAndroidPlugin(project) 50 } 51 52 // Verify that the configuration from this plugin dependent on AGP was successfully applied. 53 project.afterEvaluate { 54 if (!foundAndroidPlugin) { 55 throw StopExecutionException( 56 """ 57 The androidx.benchmark plugin currently supports only android library 58 modules. Ensure that `com.android.library` is applied in the project 59 build.gradle file. Note that to run macrobenchmarks, this plugin is not 60 required. 61 """ 62 .trimIndent() 63 ) 64 } 65 } 66 } 67 68 private fun configureWithAndroidPlugin(project: Project) { 69 if (!foundAndroidPlugin) { 70 foundAndroidPlugin = true 71 val extension = project.extensions.getByType(TestedExtension::class.java) 72 val componentsExtension = 73 project.extensions.getByType(AndroidComponentsExtension::class.java) 74 configureWithAndroidExtension(project, extension, componentsExtension) 75 } 76 } 77 78 private fun configureWithAndroidExtension( 79 project: Project, 80 extension: TestedExtension, 81 componentsExtension: AndroidComponentsExtension<*, *, *> 82 ) { 83 val defaultConfig = extension.defaultConfig 84 val testBuildType = "release" 85 val testInstrumentationArgs = defaultConfig.testInstrumentationRunnerArguments 86 87 defaultConfig.testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner" 88 89 extension.buildTypes.configureEach { 90 // Disable overhead from test coverage by default, even if we use a debug variant. 91 it.isTestCoverageEnabled = false 92 93 // Reduce setup friction by setting signingConfig to debug for buildType benchmarks 94 // will run in. 95 if (it.name == testBuildType) { 96 it.signingConfig = extension.signingConfigs.getByName("debug") 97 } 98 } 99 100 extension.testBuildType = testBuildType 101 extension.buildTypes.named(testBuildType).configure { it.isDefault = true } 102 103 if ( 104 !project.providers.gradleProperty("android.injected.invoked.from.ide").isPresent && 105 !testInstrumentationArgs.containsKey("androidx.benchmark.output.enable") 106 ) { 107 // NOTE: This argument is checked by ResultWriter to enable CI reports. 108 defaultConfig.testInstrumentationRunnerArguments["androidx.benchmark.output.enable"] = 109 "true" 110 111 if ( 112 !project.providers 113 .gradleProperty(ADDITIONAL_TEST_OUTPUT_KEY) 114 .getOrElse("false") 115 .toBoolean() 116 ) { 117 defaultConfig.testInstrumentationRunnerArguments["no-isolated-storage"] = "1" 118 } 119 } 120 121 val adbPathProvider = componentsExtension.sdkComponents.adb.map { it.asFile.absolutePath } 122 123 project.tasks.maybeRegister("lockClocks", LockClocksTask::class.java).configure { 124 it.adbPath.set(adbPathProvider) 125 it.coresArg.set( 126 project.providers.gradleProperty("androidx.benchmark.lockClocks.cores").orElse("") 127 ) 128 } 129 130 project.tasks.maybeRegister("unlockClocks", UnlockClocksTask::class.java).configure { 131 it.adbPath.set(adbPathProvider) 132 } 133 134 val extensionVariants = 135 when (extension) { 136 is AppExtension -> extension.applicationVariants 137 is LibraryExtension -> extension.libraryVariants 138 else -> 139 throw StopExecutionException( 140 """Missing required Android extension in project ${project.name}, this typically 141 means you are missing the required com.android.application or 142 com.android.library plugins or they could not be found. The 143 androidx.benchmark plugin currently only supports android application or 144 library modules. Ensure that the required plugin is applied in the project 145 build.gradle file. 146 """ 147 .trimIndent() 148 ) 149 } 150 151 // NOTE: .configureEach here is a Gradle API, which will run the callback passed to it after 152 // the extension variants have been resolved. 153 var applied = false 154 extensionVariants.configureEach { 155 if (!applied) { 156 applied = true 157 158 // Note, this directory is hard-coded in AGP 159 val outputDir = 160 project.layout.buildDirectory.dir( 161 "outputs/connected_android_test_additional_output" 162 ) 163 if ( 164 !project.providers 165 .gradleProperty(ADDITIONAL_TEST_OUTPUT_KEY) 166 .getOrElse("false") 167 .toBoolean() 168 ) { 169 // Only enable pulling benchmark data through this plugin on older versions of 170 // AGP that do not yet enable this flag. 171 project.tasks 172 .register("benchmarkReport", BenchmarkReportTask::class.java) 173 .configure { reportTask -> 174 reportTask.benchmarkReportDir.set(outputDir) 175 reportTask.adbPath.set(adbPathProvider) 176 reportTask.dependsOn(project.tasks.named("connectedAndroidTest")) 177 } 178 179 project.tasks.named("connectedAndroidTest").configure { 180 // The task benchmarkReport must be registered by this point, and is 181 // responsible for pulling report data from all connected devices onto host 182 // machine through adb. 183 it.finalizedBy("benchmarkReport") 184 } 185 } else { 186 project.tasks.named("connectedAndroidTest").configure { 187 it.doLast { 188 it.logger.info( 189 "Benchmark", 190 "Benchmark report files generated at " + 191 outputDir.get().asFile.absolutePath 192 ) 193 } 194 } 195 } 196 197 // Check for legacy runner to provide a more helpful error message as it would 198 // normally print "No tests found" otherwise. 199 val legacyRunner = "androidx.benchmark.AndroidBenchmarkRunner" 200 if (defaultConfig.testInstrumentationRunner == legacyRunner) { 201 throw StopExecutionException( 202 """Detected usage of the testInstrumentationRunner, 203 androidx.benchmark.AndroidBenchmarkRunner, in project ${project.name}, 204 which is no longer valid as it has been moved to 205 androidx.benchmark.junit4.AndroidBenchmarkRunner.""" 206 .trimIndent() 207 ) 208 } 209 } 210 } 211 212 // Enables experimental property `android.experimental.force-aot-compilation` if AGP 213 // version is at least 8.4.0. and `androidx.benchmark.forceaotcompilation` is `true`. 214 // By default this property is `true`. 215 val forceAotCompilation = 216 project.providers 217 .gradleProperty(PROP_FORCE_AOT_COMPILATION) 218 .map { it.toBoolean() } 219 .getOrElse(true) 220 if (forceAotCompilation) { 221 project.extensions.findByType(LibraryAndroidComponentsExtension::class.java)?.let { 222 if (it.pluginVersion < AndroidPluginVersion(8, 4, 0)) { 223 return@let 224 } 225 it.onVariants { v -> 226 @Suppress("UnstableApiUsage") // usage of experimentalProperties 227 v.experimentalProperties.put("android.experimental.force-aot-compilation", true) 228 } 229 } 230 } 231 } 232 233 private fun <T : Task> TaskContainer.maybeRegister(taskName: String, type: Class<T>) = 234 try { 235 named(taskName, type) 236 } catch (e: Exception) { 237 register(taskName, type) 238 } 239 } 240