BenchmarkPlugin.kt
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.benchmark.gradle
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.TestedExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.StopExecutionException
private const val ADDITIONAL_TEST_OUTPUT_KEY = "android.enableAdditionalTestOutput"
class BenchmarkPlugin : Plugin<Project> {
private var foundAndroidPlugin = false
override fun apply(project: Project) {
// NOTE: Although none of the configuration code depends on a reference to the Android
// plugin here, there is some implicit coupling behind the scenes, which ensures that the
// required BaseExtension from AGP can be found by registering project configuration as a
// PluginManager callback.
project.pluginManager.withPlugin("com.android.application") {
configureWithAndroidPlugin(project)
}
project.pluginManager.withPlugin("com.android.library") {
configureWithAndroidPlugin(project)
}
// Verify that the configuration from this plugin dependent on AGP was successfully applied.
project.afterEvaluate {
if (!foundAndroidPlugin) {
throw StopExecutionException(
"""A required plugin, com.android.application or com.android.library was not
found. The androidx.benchmark plugin currently only supports android
application or library modules. Ensure that a required plugin is applied
in the project build.gradle file."""
.trimIndent()
)
}
}
}
private fun configureWithAndroidPlugin(project: Project) {
if (!foundAndroidPlugin) {
foundAndroidPlugin = true
val extension = project.extensions.getByType(TestedExtension::class.java)
val componentsExtension = project.extensions.getByType(
AndroidComponentsExtension::class.java
)
configureWithAndroidExtension(project, extension, componentsExtension)
}
}
private fun configureWithAndroidExtension(
project: Project,
extension: TestedExtension,
componentsExtension: AndroidComponentsExtension<*, *, *>
) {
val defaultConfig = extension.defaultConfig
val testBuildType = "release"
val testInstrumentationArgs = defaultConfig.testInstrumentationRunnerArguments
defaultConfig.testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
extension.buildTypes.configureEach {
// Disable overhead from test coverage by default, even if we use a debug variant.
it.isTestCoverageEnabled = false
// Reduce setup friction by setting signingConfig to debug for buildType benchmarks
// will run in.
if (it.name == testBuildType) {
it.signingConfig = extension.signingConfigs.getByName("debug")
}
}
extension.testBuildType = testBuildType
extension.buildTypes.named(testBuildType).configure { it.isDefault = true }
if (!project.rootProject.hasProperty("android.injected.invoked.from.ide") &&
!testInstrumentationArgs.containsKey("androidx.benchmark.output.enable")
) {
// NOTE: This argument is checked by ResultWriter to enable CI reports.
defaultConfig.testInstrumentationRunnerArguments["androidx.benchmark.output.enable"] =
"true"
if (!project.findProperty(ADDITIONAL_TEST_OUTPUT_KEY).toString().toBoolean()) {
defaultConfig.testInstrumentationRunnerArguments["no-isolated-storage"] = "1"
}
}
val adbPathProvider = componentsExtension.sdkComponents.adb.map { it.asFile.absolutePath }
if (project.rootProject.tasks.findByName("lockClocks") == null) {
project.rootProject.tasks.register("lockClocks", LockClocksTask::class.java).configure {
it.adbPath.set(adbPathProvider)
}
}
if (project.rootProject.tasks.findByName("unlockClocks") == null) {
project.rootProject.tasks.register("unlockClocks", UnlockClocksTask::class.java)
.configure {
it.adbPath.set(adbPathProvider)
}
}
val extensionVariants = when (extension) {
is AppExtension -> extension.applicationVariants
is LibraryExtension -> extension.libraryVariants
else -> throw StopExecutionException(
"""Missing required Android extension in project ${project.name}, this typically
means you are missing the required com.android.application or
com.android.library plugins or they could not be found. The
androidx.benchmark plugin currently only supports android application or
library modules. Ensure that the required plugin is applied in the project
build.gradle file.
""".trimIndent()
)
}
// NOTE: .all here is a Gradle API, which will run the callback passed to it after the
// extension variants have been resolved.
var applied = false
extensionVariants.all {
if (!applied) {
applied = true
if (!project.properties[ADDITIONAL_TEST_OUTPUT_KEY].toString().toBoolean()) {
// Only enable pulling benchmark data through this plugin on older versions of
// AGP that do not yet enable this flag.
project.tasks.register("benchmarkReport", BenchmarkReportTask::class.java)
.configure {
it.adbPath.set(adbPathProvider)
it.dependsOn(project.tasks.named("connectedAndroidTest"))
}
project.tasks.named("connectedAndroidTest").configure {
// The task benchmarkReport must be registered by this point, and is
// responsible for pulling report data from all connected devices onto host
// machine through adb.
it.finalizedBy("benchmarkReport")
}
} else {
val projectBuildDir = project.buildDir.path
project.tasks.named("connectedAndroidTest").configure {
it.doLast {
it.logger.info(
"Benchmark",
"Benchmark report files generated at $projectBuildDir" +
"/outputs/connected_android_test_additional_output"
)
}
}
}
// Check for legacy runner to provide a more helpful error message as it would
// normally print "No tests found" otherwise.
val legacyRunner = "androidx.benchmark.AndroidBenchmarkRunner"
if (defaultConfig.testInstrumentationRunner == legacyRunner) {
throw StopExecutionException(
"""Detected usage of the testInstrumentationRunner,
androidx.benchmark.AndroidBenchmarkRunner, in project ${project.name},
which is no longer valid as it has been moved to
androidx.benchmark.junit4.AndroidBenchmarkRunner."""
.trimIndent()
)
}
}
}
}
}