Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998ce75fb6 | ||
|
|
4b392cb4c4 | ||
|
|
8d2109b87e | ||
|
|
b741b01cdf |
@@ -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=<serial>` 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
|
||||
|
||||
|
||||
@@ -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 <apk>` 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.
|
||||
|
||||
+25
-55
@@ -4,18 +4,28 @@ includes:
|
||||
common: ../Taskfile.yml
|
||||
|
||||
vars:
|
||||
# The *installed* package name, which every adb-driven task below uses
|
||||
# to uninstall, launch and filter. It must agree with `applicationId`
|
||||
# in app/build.gradle, and nothing enforces that.
|
||||
# APP_ID is an *assertion*, not a setting, and it has no default.
|
||||
#
|
||||
# ANDROID.md says to set this in build/config.yml. That does not work
|
||||
# in beta.8, checked both ways: `wails3 task` builds its var set from
|
||||
# CLI KEY=VALUE arguments and the Taskfile tree only -- nothing reads
|
||||
# config.yml -- and even when set it feeds only these adb commands,
|
||||
# never Gradle. So the identity is declared twice, here and in
|
||||
# build.gradle, and a change to one alone means the official run and
|
||||
# deploy tasks address a package that is not installed.
|
||||
APP_ID: '{{.APP_ID | default "app.yellowjacket"}}'
|
||||
# It used to be the id every adb-driven task below uninstalled,
|
||||
# launched and filtered, defaulting to "app.yellowjacket". It could
|
||||
# never have been a setting: `wails3 task` builds its var set from CLI
|
||||
# KEY=VALUE arguments and the Taskfile tree only -- nothing reads
|
||||
# build/config.yml, contrary to ANDROID.md, checked with --dry -- and
|
||||
# even when set it fed only the adb commands, never Gradle. So the
|
||||
# identity was declared twice, here and as `applicationId` in
|
||||
# app/build.gradle, with nothing enforcing that they agree.
|
||||
#
|
||||
# They did not agree. The debug buildType carries
|
||||
# `applicationIdSuffix ".dev"`, so the tasks that assemble a debug APK
|
||||
# addressed the *release* id -- on a device, the user's installed app
|
||||
# and their library (#159).
|
||||
#
|
||||
# The id is now read back from the built APK by scripts/android-
|
||||
# pkgid.sh, so the thing installed and the thing launched agree by
|
||||
# construction. Passing APP_ID= says "this build had better declare
|
||||
# that id", and the deploy refuses before touching anything if it does
|
||||
# not -- which is the check that would have caught #159 statically.
|
||||
APP_ID: '{{.APP_ID | default ""}}'
|
||||
MIN_SDK: '21'
|
||||
TARGET_SDK: '35'
|
||||
# The emulator runs the host architecture; physical devices are arm64
|
||||
@@ -372,9 +382,7 @@ tasks:
|
||||
ARCH: '{{.ARCH | default .HOST_ARCH}}'
|
||||
cmds:
|
||||
- task: ensure-emulator
|
||||
- '"{{.ADB}}" uninstall {{.APP_ID}} 2>/dev/null || true'
|
||||
- '"{{.ADB}}" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"'
|
||||
- '"{{.ADB}}" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity'
|
||||
- './scripts/android-deploy.sh --apk "{{.BIN_DIR}}/{{.APP_NAME}}.apk" --target emulator{{if .APP_ID}} --expect "{{.APP_ID}}"{{end}}'
|
||||
|
||||
run:
|
||||
summary: Build, install and launch a debug build in the Android Emulator
|
||||
@@ -383,9 +391,7 @@ tasks:
|
||||
- task: build
|
||||
cmds:
|
||||
- task: assemble:apk
|
||||
- '"{{.ADB}}" uninstall {{.APP_ID}} 2>/dev/null || true'
|
||||
- '"{{.ADB}}" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"'
|
||||
- '"{{.ADB}}" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity'
|
||||
- './scripts/android-deploy.sh --apk "{{.BIN_DIR}}/{{.APP_NAME}}.apk" --target emulator{{if .APP_ID}} --expect "{{.APP_ID}}"{{end}}'
|
||||
|
||||
device:list:
|
||||
summary: Lists connected Android devices and emulators (serials)
|
||||
@@ -400,25 +406,7 @@ tasks:
|
||||
ARCH: arm64
|
||||
cmds:
|
||||
- task: assemble:apk
|
||||
- |
|
||||
DEVICE='{{.DEVICE_ID | default ""}}'
|
||||
if [ -z "$DEVICE" ]; then
|
||||
DEVICE="${DEVICE_ID:-}"
|
||||
fi
|
||||
if [ -z "$DEVICE" ]; then
|
||||
DEVICE=$("{{.ADB}}" devices | awk 'NR > 1 && $2 == "device" && $1 !~ /^emulator-/ { print $1; exit }')
|
||||
fi
|
||||
if [ -z "$DEVICE" ]; then
|
||||
echo "Error: no connected physical Android device found."
|
||||
echo "Pass DEVICE_ID=<serial> to target a device explicitly."
|
||||
echo "Find connected device serials with: {{.ADB}} devices"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying {{.BIN_DIR}}/{{.APP_NAME}}.apk to device $DEVICE..."
|
||||
"{{.ADB}}" -s "$DEVICE" uninstall {{.APP_ID}} 2>/dev/null || true
|
||||
"{{.ADB}}" -s "$DEVICE" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"
|
||||
"{{.ADB}}" -s "$DEVICE" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity
|
||||
- './scripts/android-deploy.sh --apk "{{.BIN_DIR}}/{{.APP_NAME}}.apk" --target device{{if .DEVICE_ID}} --serial "{{.DEVICE_ID}}"{{end}}{{if .APP_ID}} --expect "{{.APP_ID}}"{{end}}'
|
||||
preconditions:
|
||||
- sh: '[ -x "{{.ADB}}" ] || command -v adb'
|
||||
msg: "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)"
|
||||
@@ -430,25 +418,7 @@ tasks:
|
||||
vars:
|
||||
ARCH: arm64
|
||||
cmds:
|
||||
- |
|
||||
DEVICE='{{.DEVICE_ID | default ""}}'
|
||||
if [ -z "$DEVICE" ]; then
|
||||
DEVICE="${DEVICE_ID:-}"
|
||||
fi
|
||||
if [ -z "$DEVICE" ]; then
|
||||
DEVICE=$("{{.ADB}}" devices | awk 'NR > 1 && $2 == "device" && $1 !~ /^emulator-/ { print $1; exit }')
|
||||
fi
|
||||
if [ -z "$DEVICE" ]; then
|
||||
echo "Error: no connected physical Android device found."
|
||||
echo "Pass DEVICE_ID=<serial> to target a device explicitly."
|
||||
echo "Find connected device serials with: {{.ADB}} devices"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying {{.BIN_DIR}}/{{.APP_NAME}}.apk to device $DEVICE..."
|
||||
"{{.ADB}}" -s "$DEVICE" uninstall {{.APP_ID}} 2>/dev/null || true
|
||||
"{{.ADB}}" -s "$DEVICE" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"
|
||||
"{{.ADB}}" -s "$DEVICE" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity
|
||||
- './scripts/android-deploy.sh --apk "{{.BIN_DIR}}/{{.APP_NAME}}.apk" --target device{{if .DEVICE_ID}} --serial "{{.DEVICE_ID}}"{{end}}{{if .APP_ID}} --expect "{{.APP_ID}}"{{end}}'
|
||||
preconditions:
|
||||
- sh: '[ -x "{{.ADB}}" ] || command -v adb'
|
||||
msg: "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)"
|
||||
|
||||
Executable
+196
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Install a built APK onto an Android target and launch it, under the
|
||||
# package id the APK itself declares.
|
||||
#
|
||||
# This is the whole body of build/android/Taskfile.yml's four adb-driven
|
||||
# tasks — deploy-emulator, run, run:device, deploy-device — which were
|
||||
# three lines each, written out four times, and wrong in two ways in all
|
||||
# four (#159):
|
||||
#
|
||||
# adb uninstall app.yellowjacket # the RELEASE id, unconditionally
|
||||
# adb install bin/yellowjacket.apk
|
||||
# adb shell am start -n app.yellowjacket/com.wails.app.MainActivity
|
||||
#
|
||||
# **The uninstall is not here and does not come back.** It was there 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:
|
||||
# an error message carrying the command is a decision the person at the
|
||||
# keyboard gets to make, which is the same answer scripts/android-
|
||||
# emulator.sh already reached for `make android-install`.
|
||||
#
|
||||
# **The id is read back from the artifact**, never defaulted, so the
|
||||
# thing installed and the thing launched cannot disagree — see
|
||||
# scripts/android-pkgid.sh for why that is by construction rather than
|
||||
# by discipline.
|
||||
#
|
||||
# **The target is checked against the task's own name.** The emulator
|
||||
# tasks used a bare `adb`, which with one device attached picks that
|
||||
# device whatever it is — so `wails3 task android:run`, whose summary
|
||||
# says "in the Android Emulator", installed on the phone when a phone
|
||||
# was the only thing plugged in. A task addressing something other than
|
||||
# what it says is the same fault as the package id, one level up.
|
||||
#
|
||||
# Usage:
|
||||
# android-deploy.sh --apk <path> --target emulator|device|any \
|
||||
# [--expect <id>] [--serial <s>] [--no-launch]
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
|
||||
ADB="$(command -v adb || echo "$SDK/platform-tools/adb")"
|
||||
|
||||
# **Not "$PKG/.MainActivity".** A leading-dot activity is resolved
|
||||
# against the applicationId, and the scaffold's activity lives in the
|
||||
# Java package com.wails.app, which is deliberately not it. The short
|
||||
# form fails with a class-not-found that reads like a broken build.
|
||||
ACTIVITY="${YJ_ANDROID_ACTIVITY:-com.wails.app.MainActivity}"
|
||||
|
||||
APK=""
|
||||
TARGET="any"
|
||||
EXPECT=""
|
||||
SERIAL="${ANDROID_SERIAL:-${DEVICE_ID:-}}"
|
||||
LAUNCH=1
|
||||
|
||||
die() { echo "android-deploy: $*" >&2; exit 1; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--apk) APK="${2:-}"; shift 2 ;;
|
||||
--target) TARGET="${2:-}"; shift 2 ;;
|
||||
--expect) EXPECT="${2:-}"; shift 2 ;;
|
||||
--serial) SERIAL="${2:-}"; shift 2 ;;
|
||||
--no-launch) LAUNCH=0; shift ;;
|
||||
*) die "unknown option $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$APK" ] || die "--apk is required"
|
||||
[ -f "$APK" ] || die "no such APK: $APK
|
||||
Build one first: wails3 task android:assemble:apk (debug)
|
||||
wails3 task android:package (release)"
|
||||
[ -x "$ADB" ] || command -v adb >/dev/null ||
|
||||
die "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)"
|
||||
|
||||
case "$TARGET" in
|
||||
emulator | device | any) ;;
|
||||
*) die "--target must be emulator, device or any (got '$TARGET')" ;;
|
||||
esac
|
||||
|
||||
# ---------------------------------------------------------------- #
|
||||
# Which package
|
||||
# ---------------------------------------------------------------- #
|
||||
|
||||
# This runs *before* a target is chosen, deliberately: the guard is a
|
||||
# question about the artifact, so it can be answered — and 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.
|
||||
#
|
||||
# An unreadable APK, or an id that is not the one the caller named, is a
|
||||
# hard stop before anything is installed or launched. Spelled as two
|
||||
# calls rather than one with a conditional argument: an empty array under
|
||||
# `set -u` is an unbound variable in bash 3.2, which is what macOS ships.
|
||||
if [ -n "$EXPECT" ]; then
|
||||
PKG="$(./scripts/android-pkgid.sh "$APK" --expect "$EXPECT")"
|
||||
else
|
||||
PKG="$(./scripts/android-pkgid.sh "$APK")"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- #
|
||||
# Which target
|
||||
# ---------------------------------------------------------------- #
|
||||
|
||||
# An emulator serial is "emulator-<port>"; anything else online is a
|
||||
# physical device. That is the same test the device tasks already made,
|
||||
# and the emulator tasks did not make at all.
|
||||
online_matching() {
|
||||
case "$TARGET" in
|
||||
emulator) "$ADB" devices | awk 'NR > 1 && $2 == "device" && $1 ~ /^emulator-/ { print $1 }' ;;
|
||||
device) "$ADB" devices | awk 'NR > 1 && $2 == "device" && $1 !~ /^emulator-/ { print $1 }' ;;
|
||||
any) "$ADB" devices | awk 'NR > 1 && $2 == "device" { print $1 }' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ -z "$SERIAL" ]; then
|
||||
matches="$(online_matching)"
|
||||
count="$(printf '%s' "$matches" | grep -c . || true)"
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
echo "android-deploy: no ${TARGET/any/attached} target is online." >&2
|
||||
"$ADB" devices | sed '1d;/^$/d;s/^/ /' >&2 || true
|
||||
if [ "$TARGET" = "emulator" ]; then
|
||||
echo " Start one with: make android-emulator" >&2
|
||||
elif [ "$TARGET" = "device" ]; then
|
||||
echo " Plug a phone in and authorise the adb key." >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Several is ambiguous, and picking the first silently is how a
|
||||
# build lands on a target nobody named. The old run:device did
|
||||
# exactly that.
|
||||
if [ "$count" -gt 1 ]; then
|
||||
echo "android-deploy: several $TARGET targets are online — name one." >&2
|
||||
printf '%s\n' "$matches" | sed 's/^/ /' >&2
|
||||
echo " Pass DEVICE_ID=<serial>, or set ANDROID_SERIAL." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERIAL="$matches"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- #
|
||||
# Install
|
||||
# ---------------------------------------------------------------- #
|
||||
|
||||
echo "android-deploy: $APK ($PKG) -> $SERIAL"
|
||||
|
||||
if ! out="$("$ADB" -s "$SERIAL" install -r "$APK" 2>&1)"; then
|
||||
printf '%s\n' "$out"
|
||||
case "$out" in
|
||||
*INSTALL_FAILED_UPDATE_INCOMPATIBLE* | *"signatures do not match"*)
|
||||
cat >&2 <<EOF
|
||||
|
||||
The copy of $PKG already installed was signed with a different key, and
|
||||
Android never allows that as an update.
|
||||
|
||||
The only way forward is an uninstall — **which deletes that app's data**,
|
||||
and for this app that is the user's library, irreversibly. So it is not
|
||||
done for you. If the installed copy is disposable:
|
||||
|
||||
$ADB -s $SERIAL uninstall $PKG
|
||||
|
||||
If it is not — if this is a released build with a real library on it —
|
||||
install the debug variant instead, which carries applicationIdSuffix
|
||||
".dev" and so sits beside it rather than replacing it:
|
||||
|
||||
wails3 task android:assemble:apk
|
||||
EOF
|
||||
;;
|
||||
*INSTALL_FAILED_VERSION_DOWNGRADE*)
|
||||
cat >&2 <<EOF
|
||||
|
||||
The installed copy of $PKG has a higher versionCode than this build.
|
||||
A bare 'make android' builds versionCode 1; a versioned one builds e.g.
|
||||
10301. Either build with a version:
|
||||
|
||||
YJ_VERSION=1.3.1 YJ_VERSION_CODE=10301 make android
|
||||
|
||||
or, if the installed copy is disposable, remove it:
|
||||
|
||||
$ADB -s $SERIAL uninstall $PKG
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$out"
|
||||
|
||||
[ "$LAUNCH" -eq 1 ] || exit 0
|
||||
|
||||
"$ADB" -s "$SERIAL" shell am start -n "$PKG/$ACTIVITY"
|
||||
@@ -34,7 +34,21 @@ cd "$(dirname "$0")/.."
|
||||
|
||||
AVD="${YJ_AVD:-yj-test}"
|
||||
SDK="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-$HOME/Android/Sdk}}"
|
||||
PKG="${YJ_ANDROID_PKG:-app.yellowjacket}"
|
||||
# The third declaration of the app's identity, and the one #159 did not
|
||||
# cash out in -- but the same hazard, so it is derived rather than
|
||||
# written down too. The APK in bin/ is what `make android-install` is
|
||||
# about to install and what `android-launch`, `logs` and `smoke` are
|
||||
# about to address, so it is the authority; whatever Gradle resolved the
|
||||
# applicationId to, suffix included, is in the file.
|
||||
#
|
||||
# The literal survives only as the answer for a tree with no APK built
|
||||
# yet, where these commands are asking about whatever is already on the
|
||||
# device and there is nothing to read. YJ_ANDROID_PKG still overrides.
|
||||
PKG="${YJ_ANDROID_PKG:-}"
|
||||
if [ -z "$PKG" ] && [ -f bin/yellowjacket.apk ]; then
|
||||
PKG="$(./scripts/android-pkgid.sh bin/yellowjacket.apk 2>/dev/null || true)"
|
||||
fi
|
||||
PKG="${PKG:-app.yellowjacket}"
|
||||
# Where `make android-inspect` forwards the WebView's devtools socket.
|
||||
CDP_PORT="${YJ_ANDROID_CDP_PORT:-9222}"
|
||||
# **Not "$PKG/.MainActivity".** A leading-dot activity is resolved
|
||||
@@ -281,15 +295,24 @@ cmd_inspect() {
|
||||
need_sdk
|
||||
pick_device || die "no device -- plug a phone in (USB debugging on) or run 'make android-emulator'"
|
||||
|
||||
local pkg pid
|
||||
local pkg pid candidates
|
||||
pid=""
|
||||
|
||||
for pkg in "$PKG.dev" "$PKG"; do
|
||||
# Debug sibling first, release second, whichever way round $PKG was
|
||||
# resolved -- it is read from the built APK now, so it is already the
|
||||
# .dev id whenever a debug build is what is in bin/, and appending a
|
||||
# second ".dev" to it would probe a package that cannot exist.
|
||||
case "$PKG" in
|
||||
*.dev) candidates="$PKG ${PKG%.dev}" ;;
|
||||
*) candidates="$PKG.dev $PKG" ;;
|
||||
esac
|
||||
|
||||
for pkg in $candidates; do
|
||||
pid=$("$ADB" shell pidof "$pkg" 2>/dev/null | tr -d '\r' | awk '{print $1}')
|
||||
[ -n "$pid" ] && break
|
||||
done
|
||||
|
||||
[ -n "$pid" ] || die "neither $PKG.dev nor $PKG is running; launch it first"
|
||||
[ -n "$pid" ] || die "none of: $candidates is running; launch it first"
|
||||
|
||||
"$ADB" forward --remove-all >/dev/null 2>&1 || true
|
||||
"$ADB" forward "tcp:$CDP_PORT" "localabstract:webview_devtools_remote_$pid" >/dev/null \
|
||||
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Print the package id an APK actually declares — and, given --expect,
|
||||
# refuse when that is not the id the caller was about to act on.
|
||||
#
|
||||
# This exists because the identity is declared twice and nothing made
|
||||
# the two agree. `applicationId` in build/android/app/build.gradle is
|
||||
# what Gradle installs; `APP_ID` in build/android/Taskfile.yml was what
|
||||
# every adb-driven task uninstalled, launched and filtered. They differ
|
||||
# for a reason nobody has to get wrong: the debug buildType carries
|
||||
# `applicationIdSuffix ".dev"`, so a debug build is app.yellowjacket.dev
|
||||
# while the default was app.yellowjacket — the *release* id, and on a
|
||||
# real phone the released app with the user's library on it (#159).
|
||||
#
|
||||
# So the id is read back from the artifact rather than written down a
|
||||
# third time. 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.
|
||||
#
|
||||
# Usage:
|
||||
# android-pkgid.sh <apk> [--expect <id>]
|
||||
#
|
||||
# Exit codes: 0 printed the id; 1 could not read it; 2 --expect failed.
|
||||
set -euo pipefail
|
||||
|
||||
die() { echo "android-pkgid: $*" >&2; exit 1; }
|
||||
|
||||
APK=""
|
||||
EXPECT=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--expect) EXPECT="${2:-}"; shift 2 ;;
|
||||
-*) die "unknown option $1" ;;
|
||||
*) APK="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$APK" ] || die "usage: android-pkgid.sh <apk> [--expect <id>]"
|
||||
[ -f "$APK" ] || die "no such APK: $APK"
|
||||
|
||||
# aapt2 lives under build-tools/<version>/, which is versioned, so it is
|
||||
# resolved rather than pinned. PATH first, so a system aapt2 (Arch ships
|
||||
# one) works without an SDK layout at all.
|
||||
find_aapt() {
|
||||
local sdk name
|
||||
for name in "$@"; do
|
||||
command -v "$name" 2>/dev/null && return 0
|
||||
done
|
||||
sdk="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
|
||||
for name in "$@"; do
|
||||
ls "$sdk"/build-tools/*/"$name" 2>/dev/null | sort -V | tail -1 | grep . && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
pkg=""
|
||||
|
||||
# `aapt2 dump packagename` answers in one word and is the cheapest of
|
||||
# the three. aapt1 is the fallback because it is what older build-tools
|
||||
# carry and what the issue's own measurement used.
|
||||
if AAPT2="$(find_aapt aapt2)"; then
|
||||
pkg="$("$AAPT2" dump packagename "$APK" 2>/dev/null | head -1 | tr -d '\r')" || true
|
||||
fi
|
||||
|
||||
if [ -z "$pkg" ] && AAPT="$(find_aapt aapt)"; then
|
||||
pkg="$("$AAPT" dump badging "$APK" 2>/dev/null |
|
||||
sed -n "s/^package: name='\([^']*\)'.*/\1/p" | head -1)" || true
|
||||
fi
|
||||
|
||||
# Guessing here is the bug this file exists to prevent, so an unreadable
|
||||
# APK is a hard failure and never a fallback to a written-down default.
|
||||
if [ -z "$pkg" ]; then
|
||||
die "could not read a package name from $APK.
|
||||
Install the SDK build-tools (aapt2), or set ANDROID_HOME to an SDK
|
||||
that carries them: sdkmanager 'build-tools;34.0.0'"
|
||||
fi
|
||||
|
||||
if [ -n "$EXPECT" ] && [ "$EXPECT" != "$pkg" ]; then
|
||||
cat >&2 <<EOF
|
||||
android-pkgid: refusing to act on a package this APK does not declare.
|
||||
|
||||
the APK declares: $pkg
|
||||
the task expects: $EXPECT
|
||||
APK: $APK
|
||||
|
||||
These must agree, and when they do not it is the *expectation* that is
|
||||
wrong: the APK is what Gradle built. A debug build carries
|
||||
applicationIdSuffix ".dev" (app/build.gradle), so a task that assembles
|
||||
a debug APK and then addresses the unsuffixed id is addressing the
|
||||
released app — which on a real device is the user's install, with their
|
||||
library in it (#159).
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
printf '%s\n' "$pkg"
|
||||
Reference in New Issue
Block a user