From 998ce75fb64e7b0ef08789a14fcfc6edc3c75a8b Mon Sep 17 00:00:00 2001 From: Logan Date: Thu, 20 Aug 2026 14:35:16 -0400 Subject: [PATCH] docs(android): the identity is read back, not declared twice android-tier.md carried a warning block telling the reader not to use run:device or deploy-device, and offered a manual sequence instead. Both are wrong now: the tasks are the way in, and the warning would read as a live hazard. It becomes a note about what changed, and the manual sequence stays as the smallest thing that works when you want no script between you and adb. "The identity is declared twice" was the section this file had carried for five phases saying nothing enforced that the two ids agree. It describes the enforcement now, plus what APP_ID means since it stopped being a setting it never was. NOTES.md takes the four measurements: that the uninstall existed only to cover a missing -r (which is what makes deleting it a fix rather than a trade), that the emulator tasks installed on a phone, what reading the id back costs, and the boot-wait race filed as #162. --- .../references/android-tier.md | 106 ++++++++++++++---- .planning/NOTES.md | 87 ++++++++++++++ 2 files changed, 169 insertions(+), 24 deletions(-) diff --git a/.pi/skills/yellowjacket-dev/references/android-tier.md b/.pi/skills/yellowjacket-dev/references/android-tier.md index 1e417f5..62ff776 100644 --- a/.pi/skills/yellowjacket-dev/references/android-tier.md +++ b/.pi/skills/yellowjacket-dev/references/android-tier.md @@ -258,20 +258,25 @@ one. `build/android/Taskfile.yml` ships more than the Makefile wraps, and they are the right thing to reach for when you want something one-off: -> **Do not run `android:run:device` or `android:deploy-device` -> against a device that has the released app on it (#159).** Both begin -> with `adb uninstall {{.APP_ID}}`, and `APP_ID` defaults to -> `app.yellowjacket` — the **release** id — while `run:device` builds -> the **debug** variant, whose id is `app.yellowjacket.dev`. So it -> uninstalls the user's app, taking the library with it, installs a -> different package, and then fails to launch the one it removed. This -> is "the identity is declared twice" (below) cashing out. The safe -> sequence is at the end of this section. +> **These four were unsafe until #159 and are now the way in.** All of +> them began with `adb uninstall {{.APP_ID}}`, where `APP_ID` defaulted +> to `app.yellowjacket` — the **release** id — while `run` and +> `run:device` build the **debug** variant, whose id is +> `app.yellowjacket.dev`. So they uninstalled the user's app, taking +> the library with it, installed a different package, and then failed +> to launch the one they had removed. +> +> They share `scripts/android-deploy.sh` now, which **never** +> uninstalls (`install -r`, and a changed signing certificate is +> reported with the command rather than acted on), reads the package id +> back out of the built APK, and refuses a target that is not the kind +> the task names. There is nothing left to avoid; the manual sequence +> below is kept because it is still the smallest thing that works. ``` wails3 task android:run # debug build + emulator install + launch -wails3 task android:run:device # UNSAFE, see #159 -wails3 task android:deploy-device # UNSAFE, see #159 +wails3 task android:run:device # debug build + install + launch on a phone +wails3 task android:deploy-device # release build, same wails3 task android:bundle:fat # AAB, for a Play Store upload wails3 task android:studio # open build/android/ in Android Studio wails3 task android:device:list @@ -279,6 +284,16 @@ wails3 task android:logs:all wails3 task android:clean ``` +**`run` and `deploy-emulator` mean the emulator, and now say so to +adb.** They used a bare `adb install`, which with exactly one device +attached picks that device whatever it is — so with a phone plugged in +and no emulator running, the task whose summary reads "in the Android +Emulator" installed on the phone. They pass `--target emulator` and +refuse with `make android-emulator` as the remedy. + +**`DEVICE_ID=` still names a device, and several attached +devices is now an error rather than a silent pick of the first.** + Two are deliberately **not** wrapped. `android:logs` greps logcat for `(Wails|yellowjacket)`, which catches the `WailsBridge` tag but misses the app's own process tag (`app.yellowjacket` — lowercase, so `Wails` @@ -288,16 +303,51 @@ instead. And `ensure-emulator` boots whatever `-list-avds | tail -1` returns, with no pidfile and no boot wait, so it cannot be stopped or sequenced. -## The identity is declared twice +## The identity is read back from the APK +It used to be **declared twice**, and that is what #159 was. `applicationId` in `build/android/app/build.gradle` is what Gradle -installs. `APP_ID` in `build/android/Taskfile.yml` is what every -adb-driven task uninstalls, launches and filters. **Nothing enforces -that they agree**, and `ANDROID.md`'s advice to set `APP_ID` in -`build/config.yml` does not work in beta.8 — `wails3 task` never reads -that file (verified with `--dry`), and even when set it feeds only the -adb commands, never Gradle. Change both or the official `run`/`deploy` -tasks address a package that is not installed. +installs; `APP_ID` in `build/android/Taskfile.yml` was what every +adb-driven task uninstalled, launched and filtered, and nothing +enforced that they agree. They did not: the debug buildType carries +`applicationIdSuffix ".dev"`, so every task that assembles a debug APK +addressed the release id. This file flagged the hazard for five phases +and it cashed out twice — once as a wrong `am start`, once as an +uninstall of the user's library. + +**`scripts/android-pkgid.sh` is the one answer now.** It prints the +package id an APK declares (`aapt2 dump packagename`, falling back to +`aapt dump badging`), and the deploy path installs and launches *that*. +The APK is the authority because the task that installs it has just +built it: whatever Gradle resolved the applicationId to, suffixes and +flavours included, is in the file, and no default can disagree with it. +An APK it cannot read is a hard failure, never a fallback to a written +down default — guessing is the bug. + +**`APP_ID` survives as an assertion, not a setting**, and has no +default. `wails3 task android:run APP_ID=app.yellowjacket` says "this +build had better declare that id" and is refused, naming both, *before* +anything is installed or a device is even chosen. It could never have +been a setting: `ANDROID.md`'s advice to put it in `build/config.yml` +does not work in beta.8 — `wails3 task` never reads that file (verified +with `--dry`) — and even when set it fed only the adb commands, never +Gradle. + +`scripts/android-emulator.sh` derives `PKG` the same way, from +`bin/yellowjacket.apk` when one is built, so `make android-install`, +`android-launch`, `android-logs` and `android-smoke` follow whichever +variant is actually in `bin/`. `YJ_ANDROID_PKG` still overrides, and +the old literal survives only for a tree with no APK built yet. + +**The uninstall is gone and is not coming back.** It existed to make +the bare `install` on the next line work at all — without `-r` Android +refuses an install over an existing package — so `install -r` removes +the *reason* for it rather than merely removing it. What is left is the +one case an uninstall really is the remedy, a changed signing +certificate, and that is exactly the case where performing it silently +costs the user their library. So it is named and not done, which is the +answer `scripts/android-emulator.sh` had already reached for +`make android-install`. Related, and it will bite once: the launcher activity is `com.wails.app.MainActivity` and the applicationId is @@ -306,8 +356,10 @@ resolves the leading dot against the *applicationId* and fails with a class-not-found that reads like a broken build. Always the fully-qualified form. -**The safe way to put a debug build on a real device**, which is what -#52 used and what #159 exists to make unnecessary: +**`wails3 task android:run:device` is the way to put a debug build on a +real device**, since #159. What #52 used, before it was safe, was the +longer form, and it is still the smallest thing that works if you want +no script between you and adb: ```bash wails3 task android:build ARCH=arm64 && wails3 task android:assemble:apk @@ -315,9 +367,15 @@ adb install -r bin/yellowjacket.apk # -r, never uninstall adb shell am start -n app.yellowjacket.dev/com.wails.app.MainActivity ``` -`YJ_ANDROID_PKG=app.yellowjacket.dev` points `scripts/android-emulator.sh` -— and therefore `make android-smoke`, `android-logs`, `android-launch` -— at the debug id, which is otherwise `app.yellowjacket`. +The id in that last line is the one thing to keep an eye on by hand — +`./scripts/android-pkgid.sh bin/yellowjacket.apk` is what the tasks ask, +and it is a good habit before any `am start` written out in full. + +`YJ_ANDROID_PKG=app.yellowjacket.dev` still overrides what +`scripts/android-emulator.sh` — and therefore `make android-smoke`, +`android-logs`, `android-launch` — addresses, but it is rarely needed +now: that default is read from `bin/yellowjacket.apk`, so it already +follows whichever variant was built last. ## What only a device can answer diff --git a/.planning/NOTES.md b/.planning/NOTES.md index 630793f..7c507e1 100644 --- a/.planning/NOTES.md +++ b/.planning/NOTES.md @@ -4155,3 +4155,90 @@ This is the hazard that file already names — "The identity is declared twice ... **Nothing enforces that they agree**" — reached by a second route: the two ids differ not because someone edited one, but because the debug buildType suffixes it. + +## The uninstall was there to make a bare `install` work (measured 2026-08-20) + +Fixing #159 turned up *why* the `adb uninstall` was in all four tasks, +which the issue does not say and which decides whether it can simply be +deleted. The line under it was `adb install`, with **no `-r`** — and +Android refuses an install over an existing package without it. So the +uninstall was not a deliberate clean-slate step; it was the price of +the missing flag, paid on every run, and `install -r` removes the +reason for it rather than merely removing it. + +That matters because "should the uninstall go at all" looked like a +trade — drop it and a signing-certificate change fails with +`INSTALL_FAILED_UPDATE_INCOMPATIBLE` instead of being handled. It is +not a trade: nothing else was relying on it. The certificate case is +reported with the command to run, which is what +`scripts/android-emulator.sh` already did for `make android-install`, +so this is one existing judgement applied consistently rather than a +new one. + +## `wails3 task android:run` installs on a phone (measured 2026-08-20) + +The emulator tasks (`run`, `deploy-emulator`) used a bare `adb install` +with no `-s`. adb with exactly one device attached uses that device +whatever kind it is, so with a phone plugged in and no emulator +running, the task whose summary reads "in the Android Emulator" +installed on the phone — and, before #159 was fixed, ran +`adb uninstall app.yellowjacket` against it first. The reported data +loss was reachable from the *emulator* task, which is not what the +issue describes and is worse, because nothing in the name warns you. + +Measured after the fix, phone attached and emulator stopped: + +``` +$ ./scripts/android-deploy.sh --apk bin/yellowjacket.apk --target emulator +android-deploy: no emulator target is online. + LP3LHMA531900746 device + Start one with: make android-emulator +``` + +The general form: **a task that names a target has to say so to adb.** +The device tasks always filtered on `$1 !~ /^emulator-/`; the emulator +tasks filtered on nothing at all. + +## The package id can be read back, and costs nothing (2026-08-20) + +`aapt2 dump packagename ` answers in one word and ~40 ms, from +`$ANDROID_HOME/build-tools/*/aapt2` (versioned, so resolved not +pinned); `aapt dump badging` is the fallback for older build-tools and +is what #159's own measurement used. That is cheap enough to do on +every deploy, which is what makes "the two ids agree by construction" +affordable rather than aspirational — the alternative considered was +giving the debug-flavoured tasks `APP_ID` + `.dev`, which is one line +and leaves the class of bug alive for the next flavour or suffix. + +The guard runs **before** a target is chosen, deliberately: it is a +question about the artifact, so it can be exercised with nothing +plugged in, and a build whose id is wrong should be refused whether or +not there is anything to install it onto. That is what let the negative +test run safely with the user's phone attached: + +``` +$ ./scripts/android-deploy.sh --apk bin/yellowjacket.apk \ + --target device --expect app.yellowjacket +android-pkgid: refusing to act on a package this APK does not declare. + the APK declares: app.yellowjacket.dev + the task expects: app.yellowjacket +rc=2 +``` + +That is exactly #159's configuration — debug APK, release id, real +device — refused with no adb call made. + +## `make android-emulator`'s boot wait can be satisfied by a phone (2026-08-20) + +Noticed while booting the emulator for #159's verification, with a +phone also attached. `scripts/android-emulator.sh start` reported +`waiting for boot ok / android 14` about **eight seconds** after +launching the emulator, which had not appeared in `adb devices` yet — +`pick_device`'s last resort is "exactly one device online", and at that +moment the one online device was the phone. So it waited for the +phone's boot, found it long since booted, and returned. The emulator +took another ~10 s to come up. + +Harmless here (the emulator was up before anything used it) and a +straightforward race otherwise: `start` should wait for a device that +is an emulator, not for whatever `pick_device` returns. Filed as #162.