Files
SoulStream/src-tauri/android/build.sh
T
KeelrLeek 4ebc38d5f3 android: the falcon on the launcher — a real adaptive icon, with the monochrome layer
The phone has been wearing tauri's logo. `tauri android init` writes the
generated project's `res/` from its own template and never reads
`src-tauri/icons/android/`, which has carried the falcon since 50afa6a, and the
detour build runs cargo and Gradle directly — so nothing ever copied it across:
the two dominant colours in the packaged `mipmap-xxxhdpi/ic_launcher.png` are
`#24c8db` and `#ffc131`, tauri's own brand cyan and amber. `src-tauri/android/res/`
is the app module's res now, tracked, and `build.sh` and `dev.sh` copy the whole
tree into `gen/android` before Gradle, because that project is regenerated and
gitignored and an asset that only lives there is an asset that disappears.

It is a real adaptive icon: the kit's foreground and its **monochrome** layer at
five densities, the legacy mipmaps under it, and the background as one
`#FF05080B` colour resource rather than five identical flat PNGs, so the gradient
being mocked separately replaces one file. The kit's own XML could not ship — it
points every layer at `@drawable/` while shipping them as PNGs under `mipmap-*`,
and in the generated project two of those three references resolve to Android
Studio's template robot rather than failing — so the overlay's XML says
`@mipmap/` and `@color/`, and its colour file takes the same name tauri's does,
because two files declaring one colour is a hard aapt2 error.

Losing the copy would be silent — the APK still builds, still installs, and wears
the old logo with nothing in the log — so `verify-icon.sh` opens every APK both
scripts make and reads the resources back out: the two `mipmap-anydpi-v26` XMLs
present, and four PNG names at five densities byte for byte from the overlay. A
release APK keeps neither the bytes nor the names — AGP re-encodes every PNG and
shortens every resource path — so that path asks `aapt2 dump resources` what each
name points at and compares pixel dimensions instead; 72×72 at hdpi still tells
the kit's icon from tauri's 49×49. No walk step: a launcher icon is not reachable
from inside the app, and the masking and the themed layer want a minute with the
phone's home screen.
2026-09-15 07:22:00 -05:00

96 lines
5.3 KiB
Bash

#!/usr/bin/env bash
# The detour build (ANDROID.md): cargo → strip → copy → gradle. Run from anywhere.
set -euo pipefail
cd /f/Coding/SoulStream
source src-tauri/android/env.sh
# Which build. The default is exactly what this script has always done, and
# has to stay that way: deploy.sh calls it with no argument every twenty
# minutes, and ship.sh calls deploy.sh. `--release` (or SS_ANDROID_RELEASE=1)
# is the other path, and as of this round nobody has run it — see ANDROID.md
# → "The release build (nobody has made one yet)".
REL=0
case "${1:-}" in
--release) REL=1 ;;
"") ;;
*) echo "usage: build.sh [--release]"; exit 2 ;;
esac
if [ "${SS_ANDROID_RELEASE:-0}" = "1" ]; then REL=1; fi
if [ "$REL" = "1" ]; then
PROFILE=release
VARIANT=Arm64Release
# --features is repeatable and cargo unions them, so custom-protocol on
# the cargo line below is kept. devtools is not a nicety: wry compiles the
# setWebContentsDebuggingEnabled call only under
# cfg(any(debug_assertions, feature = "devtools")), so a plain release APK
# opens no webview_devtools_remote socket and `smoke.mjs --phone` cannot
# attach to the one build that most needs walking. R8 never sees it.
CARGO_EXTRA="--release --features tauri/devtools"
test -d src-tauri/gen/android/app || { echo "no generated Android project — run the CLI's android init first"; exit 1; }
# The app-wide keep rules, into a project that cannot hold them itself:
# gen/ is regenerated and gitignored. The release build type globs
# **/*.pro out of this directory; the debug type reads none of them.
cp src-tauri/android/proguard-soulstream.pro src-tauri/gen/android/app/proguard-soulstream.pro
echo "== RELEASE: R8 on, rules from src-tauri/android/proguard-soulstream.pro + each plugin's consumer-rules.pro"
else
PROFILE=debug
VARIANT=Arm64Debug
CARGO_EXTRA=""
fi
echo "== cargo $(date +%T)"
cargo build --lib -p app --target aarch64-linux-android --features tauri/custom-protocol $CARGO_EXTRA 2>&1 | grep -v "^\s*Compiling\|^\s*Fresh"
# the grep above hides cargo's exit code; a failed compile must not package
# the previous library as if it were this one
test "${PIPESTATUS[0]}" -eq 0 || { echo "cargo failed — not packaging"; exit 1; }
SO=target/aarch64-linux-android/$PROFILE/libapp_lib.so
test -f "$SO" || { echo "no .so built"; exit 1; }
echo "== strip $(date +%T)"
DEST=src-tauri/gen/android/app/src/main/jniLibs/arm64-v8a
mkdir -p "$DEST"
cp "$SO" "$DEST/libapp_lib.so"
"$BIN/llvm-strip.exe" --strip-debug "$DEST/libapp_lib.so"
ls -la "$DEST/libapp_lib.so"
# The launcher identity, into a project that cannot keep it: gen/android is
# regenerated by the tauri CLI and gitignored, and the detour build never runs
# that CLI, so the res it holds is still the one `android init` wrote — tauri's
# own logo. Everything under src-tauri/android/res/ is copied over it on every
# build. The whole tree, not a file list: a later round adding a resource adds
# a file there and this line does not change.
echo "== res overlay $(date +%T)"
test -d src-tauri/android/res || { echo "src-tauri/android/res is gone — the APK would wear tauri's logo and say nothing"; exit 1; }
cp -r src-tauri/android/res/. src-tauri/gen/android/app/src/main/res/
echo "== gradle $(date +%T)"
cd src-tauri/gen/android
./gradlew.bat assemble$VARIANT -x rustBuild$VARIANT -q 2>&1 | grep -v "^$" | tail -20 || true
cd /f/Coding/SoulStream
if [ "$REL" = "1" ]; then
UNSIGNED=src-tauri/gen/android/app/build/outputs/apk/arm64/release/app-arm64-release-unsigned.apk
if [ ! -f "$UNSIGNED" ]; then
echo "no release APK — gradle's tail is above. The two failures we expect:"
echo " R8 missing class — its own list is at src-tauri/gen/android/app/build/outputs/mapping/arm64Release/missing_rules.txt;"
echo " add what it names to src-tauri/android/proguard-soulstream.pro and run again"
echo " lintVital — re-run gradle by hand with -x lintVitalArm64Release"
echo " (AGP 8 splits it; -x lintVitalAnalyzeArm64Release may be the one)"
exit 1
fi
KS="$HOME/.android/debug.keystore"
test -f "$KS" || { echo "no $KS — the release APK is unsigned and no phone will install it"; exit 1; }
BT="$(ls -d "$SDK"/build-tools/* 2>/dev/null | sort -V | tail -1 || true)"
test -n "$BT" || { echo "no build-tools under $SDK — cannot sign"; exit 1; }
APK=src-tauri/gen/android/app/build/outputs/apk/arm64/release/app-arm64-release.apk
echo "== sign $(date +%T) ($(basename "$BT"))"
"$BT/zipalign.exe" -p -f 4 "$UNSIGNED" "$APK"
# The SAME key the debug builds are signed with, deliberately. A release
# APK signed with any other key cannot be installed over the app already
# on the phone, and the obvious next move — uninstall, then install —
# takes the app's data with it. This is a test build, not a distributable
# one; a real one needs a real key and this script is not where that goes.
MSYS_NO_PATHCONV=1 "$BT/apksigner.bat" sign --ks "$(cygpath -w "$KS")" --ks-key-alias androiddebugkey --ks-pass pass:android --key-pass pass:android "$APK"
else
APK=src-tauri/gen/android/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk
fi
ls -la "$APK"
bash src-tauri/android/verify-icon.sh "$APK" $([ "$REL" = "1" ] && echo --crunched)
echo "== done $(date +%T)"