// ───────────────────────────────────────────────────────────────────────────── // :macrobenchmark (A35) — the macrobenchmark harness module. // // `com.android.test`, NOT `com.android.library`: macrobenchmark must drive the app // OUT of process (its own APK, its own process, UiAutomator across the boundary) so // that measured startup/frame timings are the real ones and not perturbed by the // test framework sharing the app's process. // // It instruments :app's `benchmark` variant — non-debuggable, R8'd and // resource-shrunk like release, but signed with the debug key so no release keystore // is needed (see app/build.gradle.kts → buildTypes.benchmark). // // This module currently has NO sources: the benchmark itself (pair → attach → type → // approve, plus a cold-start timing) is the next task's deliverable and belongs in // src/main/kotlin/. The module exists so that work is a matter of adding a file. // // Run (device/emulator required — `./gradlew :app:assembleDebug` style CI cannot): // ./gradlew :macrobenchmark:connectedBenchmarkAndroidTest // // AGP 9 has built-in Kotlin → apply ONLY the android plugin (no kotlin.android). // The plugin id is applied BARE (no catalog alias / no version): the root build script // already puts AGP on the classpath via the android.library + android.application // aliases, and Gradle rejects a third versioned request for the same plugin artifact // ("already on the classpath with an unknown version"). The version is still pinned in // one place — `agp` in gradle/libs.versions.toml. Same pattern as :terminal-view. // ───────────────────────────────────────────────────────────────────────────── plugins { id("com.android.test") } android { namespace = "wang.yaojia.webterm.macrobenchmark" compileSdk = 36 defaultConfig { // Macrobenchmark itself needs API 23+; matching :app's 29 keeps the variant // matrix honest (a benchmark on an SDK :app cannot run on is meaningless). minSdk = 29 targetSdk = 35 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } buildTypes { // The only variant this module is built in. `isDebuggable = true` applies to the // TEST apk (never to :app) — required so the harness can be instrumented at all. create("benchmark") { isDebuggable = true signingConfig = getByName("debug").signingConfig // :app publishes debug/release/benchmark; libraries publish debug/release. matchingFallbacks += listOf("release") } } targetProjectPath = ":app" // The harness runs in its OWN process and reaches the app only through UiAutomator / // shell, never by linking against its classes. Without this, AGP's // `checkTestedAppObfuscationBenchmark` refuses to build a non-minified test module // against a minified app — a check that is correct for Espresso-style tests (which do // link app symbols) and a false positive for macrobenchmark. experimentalProperties["android.experimental.self-instrumenting"] = true } kotlin { jvmToolchain(17) } // Only the `benchmark` variant is meaningful; disable the stock debug/release ones so // `./gradlew build` does not try to assemble a harness against a variant of :app that // does not exist. androidComponents { beforeVariants { variant -> variant.enable = variant.buildType == "benchmark" } } dependencies { // A `com.android.test` module's `main` source set IS its test code — deps go in // implementation, not androidTestImplementation. implementation(libs.androidx.test.ext.junit) implementation(libs.androidx.test.uiautomator) implementation(libs.androidx.benchmark.macro.junit4) }