ci(preflight): use host-GPU auto-no-window for local api37 emulator

Change the api37_e2e.py emulator launch from `-gpu swiftshader_indirect`
to `-gpu auto-no-window`. For a LOCAL run the host GPU is faster and
auto-no-window is the mode that boots cleanly on this machine; CI's
e2e-preview keeps swiftshader_indirect for headless-runner determinism.
This is now the single deliberate divergence from e2e-preview; the image
string, provisioning, boot sequence, and every other emulator flag stay
in lockstep. Updated the script comments/docstring, SKILL.md, CLAUDE.md,
and the build.gradle.kts managed-devices comment to document it.

Syntax-only change (python -m py_compile clean); emulator not run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 16:31:41 -05:00
co-authored by Claude Opus 4.8
parent b5a29a0450
commit ac5ed162ae
4 changed files with 28 additions and 17 deletions
+9 -6
View File
@@ -64,17 +64,20 @@ Managed Device — its only published system image is the nonstandard `android-3
`google_apis_ps16k` pairing, which neither `ManagedVirtualDevice`'s `apiLevel` (Int) nor
`apiPreview` (codename) DSL resolves (see the comment above `testOptions.managedDevices` in
`app/build.gradle.kts`), so there is no `api37DebugAndroidTest` task. The script hand-provisions
it exactly the way CI's `e2e-preview` job does — installs the image with `sdkmanager`, creates
the AVD with `avdmanager`, cold-boots it headless with the same emulator flags, waits for
`sys.boot_completed`, runs `:app:connectedDebugAndroidTest`, then kills the emulator and deletes
the AVD. Because it mirrors `e2e-preview`, local preflight == CI on API 37.
it essentially the way CI's `e2e-preview` job does — installs the image with `sdkmanager`,
creates the AVD with `avdmanager`, cold-boots it headless, waits for `sys.boot_completed`, runs
`:app:connectedDebugAndroidTest`, then kills the emulator and deletes the AVD. The emulator flags
match `e2e-preview` with one deliberate local exception: the **GPU mode**. CI uses
`-gpu swiftshader_indirect` (software rendering, deterministic on a headless CI runner); the
local run uses `-gpu auto-no-window`, which renders on the host GPU — faster, and the mode that
boots cleanly on a dev machine.
All three levels are the E2E that preflight runs locally and all three must pass; CI fans the
same suite out across the whole matrix (API 29–36 in `e2e`, plus API 37 in `e2e-preview`). Keep
`api35DebugAndroidTest` / `api36DebugAndroidTest` in lockstep with the top of the managed-device
list in `app/build.gradle.kts`, and keep `api37_e2e.py` in lockstep with the `e2e-preview` job in
`.github/workflows/ci.yml` (same image string + emulator flags) — when a newer API level is added
there, run the new top levels instead.
`.github/workflows/ci.yml` (same image string + emulator flags, apart from the intentional GPU-mode
difference noted above) — when a newer API level is added there, run the new top levels instead.
## Reporting
+12 -6
View File
@@ -11,9 +11,11 @@ API 37's only published system image is the nonstandard "android-37.0" / google_
"android-<apiLevel:Int>" package id (apiLevel = 37 -> "android-37") or an
"android-<apiPreview:codename>" one -- neither resolves to "android-37.0" -- so there is NO
api37DebugAndroidTest task to run. This script custom-provisions the emulator with
sdkmanager / avdmanager / emulator directly, mirroring CI's `e2e-preview` job in
.github/workflows/ci.yml EXACTLY (same system image string, same emulator flags, same
provisioning/boot sequence) so that local preflight == CI. Keep the two in lockstep: when a
sdkmanager / avdmanager / emulator directly, closely mirroring CI's `e2e-preview` job in
.github/workflows/ci.yml (same system image string, same provisioning/boot sequence, same
emulator flags EXCEPT the GPU mode -- CI uses `-gpu swiftshader_indirect` for headless
determinism, while this local run uses `-gpu auto-no-window` to render on the host GPU, which
is faster; see start_emulator). Keep the two in lockstep on everything but that GPU flag: when a
stable, GMD-compatible API 37 image ships, delete this script, fold api37 into
testOptions.managedDevices, and fold 37 into CI's `e2e` matrix (dropping the `e2e-preview` job).
@@ -117,12 +119,16 @@ def create_avd(avdmanager: str, emulator: str) -> None:
def start_emulator(emulator: str, emu_log: Path, attempt: int) -> subprocess.Popen:
print(f"Starting API 37 emulator (attempt {attempt})...")
# Flags mirror .github/workflows/ci.yml e2e-preview EXACTLY (cold headless boot, SwiftShader
# GPU, hardware accel required, no cameras). Keep in lockstep with that job.
# Flags mirror .github/workflows/ci.yml e2e-preview (cold headless boot, hardware accel
# required, no cameras), with ONE deliberate LOCAL exception -- the GPU mode. CI uses
# `-gpu swiftshader_indirect` (software rendering, deterministic on a headless CI runner);
# locally we use `-gpu auto-no-window`, which renders on the host GPU: faster, and the mode
# that boots cleanly on a dev machine. Keep everything except the GPU mode in lockstep with
# that job.
flags = [
"-avd", AVD_NAME,
"-no-window", "-no-audio", "-no-boot-anim", "-no-snapshot", "-accel", "on",
"-gpu", "swiftshader_indirect", "-camera-back", "none", "-camera-front", "none",
"-gpu", "auto-no-window", "-camera-back", "none", "-camera-front", "none",
]
log = open(emu_log, "wb") # noqa: SIM115 - handed to the child; closed in the parent below
try:
+3 -2
View File
@@ -39,8 +39,9 @@ top two stable API levels in the E2E matrix, each via its own Gradle Managed Dev
and tears down each emulator automatically). API 37 (preview) has no Gradle Managed Device — its
only image is the nonstandard `android-37.0` / `google_apis_ps16k` pairing (see the comment above
`testOptions.managedDevices` in `app/build.gradle.kts`) — so `api37_e2e.py` hand-provisions it,
mirroring CI's `e2e-preview` job (same image + emulator flags), boots it headless, runs
`connectedDebugAndroidTest`, and tears it down. Running all three levels locally is required; the
mirroring CI's `e2e-preview` job (same image + emulator flags, except it uses host-GPU
`-gpu auto-no-window` locally vs CI's headless `-gpu swiftshader_indirect`), boots it headless,
runs `connectedDebugAndroidTest`, and tears it down. Running all three levels locally is required; the
rest of the multi-API matrix (API 29–34) stays CI's job. Emulators need a free hardware
hypervisor (VT-x/WHPX), so shut down VirtualBox/other VMs first or the AVD hangs at 0% CPU.
+4 -3
View File
@@ -159,9 +159,10 @@ android {
// (docs/perf/issue-124-unified-inbox-paging.md) independently corroborates this: its API 37
// cross-check used a physical Pixel, not an emulator/AVD. Locally, preflight covers API 37
// by hand-provisioning it with .claude/skills/preflight/api37_e2e.py, which mirrors the
// e2e-preview job (same image + emulator flags). Once a managed-device-compatible image is
// published, add `api37` here (and to the CI matrix), delete that script, and drop the
// e2e-preview job.
// e2e-preview job (same image + emulator flags, except it renders on the host GPU via
// `-gpu auto-no-window` locally instead of CI's headless `-gpu swiftshader_indirect`). Once
// a managed-device-compatible image is published, add `api37` here (and to the CI matrix),
// delete that script, and drop the e2e-preview job.
managedDevices {
localDevices {
listOf(29, 30, 31, 32, 33, 34, 35, 36).forEach { api ->