Stop Gradle installing on every checkout's emulator at once

The adb wrapper can only aim a call that goes through it, and Gradle's
Android tasks do not: installDebug, uninstallDebug and connectedAndroidTest
ask the adb server for every attached device and act on all of them. One
session ran installDebug with two emulators up and replaced the app on both,
which Gradle reported as success and the other session read as its own build
never landing.

`emu check` answers the question the wrapper already answers for adb --
would a command that reaches every attached device reach a stranger's
emulator from here -- and install.sh links a Gradle init script into
~/.gradle/init.d so every build on this machine asks it, including checkouts
nobody has adopted it in.

It refuses narrowly: a lone emulator, a physical phone and a caller who
named a device are all fine. ANDROID_SERIAL naming another checkout's
emulator is not, because that is what a serial exported into a long-lived
shell decays into once an emulator restarts and another's takes the port.
This commit is contained in:
iris committed 2026-08-31 15:40:58 -04:00
1 parent 1da445ae32
commit 69a22c54e7
4 files changed
+158 -2

No files matched your search

+34 -1
View File
@@ -14,11 +14,12 @@ anything it replaces as `<name>.bak`.
| | |
|---|---|
| `emu` | this checkout's emulator: `name`, `serial`, `list`, `up`, `down` |
| `emu` | this checkout's emulator: `name`, `serial`, `list`, `up`, `down`, `check` |
| `adb` | adb, aimed at this checkout's emulator, with screenshots scaled |
| `ui-trace` | records a screen as text at 60Hz, and drives it |
| `lib/project-avd.sh` | the rules all three share |
| `share/ui-trace/` | the device recorder (`UiTrace.java`) and its build |
| `share/gradle-init/` | the init script that stops Gradle installing on every device |
## One emulator per checkout, named after it
@@ -40,6 +41,38 @@ about one device (`adb devices`) all turn the defaulting off, and nothing is
guessed when this checkout's emulator is not running: adb's own error is
better than a wrapper picking a stranger's device.
## Gradle is the hole the wrapper cannot cover
`adb` can only aim a call that goes through it, and Gradle's Android tasks
do not: `installDebug`, `uninstallDebug` and `connectedAndroidTest` ask the
adb server for every attached device and act on all of them. On 2026-08-31
one session ran `./gradlew :androidApp:installDebug` with two emulators up
and replaced the app on both. Gradle reported success; from the other
session it looked like its own build had never landed.
So `install.sh` also links `share/gradle-init/emulator-tools.gradle` into
`~/.gradle/init.d`, where Gradle applies it to **every** build on this
machine — including checkouts nobody has adopted it in, which are exactly
the ones this is protecting. Before any of those tasks runs it calls
emu check
in the project's own directory. That is the same rule the `adb` wrapper
follows, in one place: what must not be touched is *another checkout's
emulator*. A physical phone is nobody's checkout, one emulator on its own is
nobody else's business, and a caller who named a device has already narrowed
the fan-out to it — so `check` passes all of those, and refuses only when a
stranger's emulator is in range. `ANDROID_SERIAL` pointing at one is refused
too, because that is what a serial exported into a long-lived shell decays
into once an emulator restarts and another checkout's takes the port.
Say which device you mean at the moment you use it rather than exporting it:
ANDROID_SERIAL=$(emu serial) ./gradlew :androidApp:installDebug
`EMU_ANY_DEVICE=1` means every attached device anyway, for the case that is
genuinely what you want.
## Rendering on the GPU with no display
This machine has no screen, and for a long time that meant the emulator