diff --git a/src-tauri/android/verify-icon.sh b/src-tauri/android/verify-icon.sh index 19ca06d..47aab5a 100644 --- a/src-tauri/android/verify-icon.sh +++ b/src-tauri/android/verify-icon.sh @@ -37,6 +37,15 @@ # If anyone ever sets isCrunchPngs = true on the debug build type, the debug # path fails on byte-equality with the file named; --crunched is the answer. set -uo pipefail +# Every membership test below reads its haystack from a HERESTRING, never from +# `echo "$VAR" | grep -q`. Measured 2026-09-15: that pipeline plus the +# `pipefail` above is a race. `grep -q` exits the instant it matches, `echo` is +# still pushing 70 KB of `unzip -l` down a closed pipe, takes SIGPIPE and exits +# 141 — and pipefail promotes the dead writer's status to the whole pipeline, +# so a check that FOUND what it wanted reports failure. Whether it fires +# depends on where the match falls in the stream, so three runs against one +# unchanged APK gave fail / pass / fail. A herestring has no writer process and +# nothing to kill. APK="${1:-}" test -n "$APK" || { echo "usage: verify-icon.sh [--crunched]"; exit 2; } # resolved before the cd, so a relative path typed by hand still means what it @@ -102,6 +111,14 @@ SDKU="" [ -n "${ANDROID_HOME:-}" ] && SDKU="$(cygpath -u "$ANDROID_HOME" 2>/dev/null || echo "")" [ -z "$SDKU" ] && [ -n "${LOCALAPPDATA:-}" ] && SDKU="$(cygpath -u "$LOCALAPPDATA" 2>/dev/null || echo "")/Android/Sdk" A2="$(ls -1 "$SDKU"/build-tools/*/aapt2.exe 2>/dev/null | sort -V | tail -1 || true)" +# aapt2 is a WINDOWS binary and $APK is an MSYS path. Normally MSYS rewrites +# one into the other on the way across, but deploy.sh exports +# MSYS_NO_PATHCONV=1 for the adb calls, and that switch is global — so inside +# a deploy the path arrives as /f/Coding/... and aapt2 answers "failed opening +# zip: I/O error", which reads exactly like a corrupt APK. Hand aapt2 the +# Windows form explicitly and keep $APK for unzip and cmp, which want the +# MSYS one. (Checked standalone this never fires; it only bites under deploy.) +APKW="$(cygpath -w "$APK" 2>/dev/null || echo "$APK")" # 2. the ground, in BOTH build types. Not "is the file in the APK" — the # adaptive icon's is a REFERENCE, so what matters is what it @@ -113,7 +130,7 @@ A2="$(ls -1 "$SDKU"/build-tools/*/aapt2.exe 2>/dev/null | sort -V | tail -1 || t # of a path: a release APK has had res/mipmap-anydpi-v26/ic_launcher.xml # renamed to res/BW.xml, and the table is the only thing that still knows. TBL="" -[ -n "$A2" ] && { TBL="$("$A2" dump resources "$APK" 2>/dev/null)" || bad "aapt2 could not read the resource table of $APK"; } +[ -n "$A2" ] && { TBL="$("$A2" dump resources "$APKW" 2>/dev/null)" || bad "aapt2 could not read the resource table of $APK"; } # name [config] -> the file that resource points at. The config is not # optional detail: mipmap/ic_launcher has SIX entries — the anydpi adaptive # XML and five legacy PNGs — so a name-only lookup hands back @@ -130,12 +147,12 @@ name_of() { echo "$TBL" | awk -v id="$1" '$1 == "resource" && $2 == id { print $ if [ -z "$TBL" ]; then echo "== ground: NOT CHECKED — no aapt2 under ${SDKU:-(no SDK path)}/build-tools." echo " What points at is a resource id; only the table reads it." -elif echo "$TBL" | grep -q "0_resource_name_obfuscated"; then +elif grep -q "0_resource_name_obfuscated" <<< "$TBL"; then echo "== ground: NOT CHECKED — this APK's resource names are collapsed." else ADAPT="$(path_of mipmap/ic_launcher anydpi)" test -n "$ADAPT" || bad "mipmap/ic_launcher has no anydpi entry in this APK — the adaptive icon is not in it, so $RES never reached src-tauri/gen/android/app/src/main/res" - BGID="$("$A2" dump xmltree "$APK" --file "$ADAPT" 2>/dev/null \ + BGID="$("$A2" dump xmltree "$APKW" --file "$ADAPT" 2>/dev/null \ | awk '/E: background/ { f = 1 } f && /android:drawable\(/ { sub(/.*=@?/, ""); print; exit }')" test -n "$BGID" || bad "the adaptive icon in this APK ($ADAPT) declares no " BGNAME="$(name_of "$BGID")" @@ -147,7 +164,7 @@ else # the $name__N filenames aapt2 happens to give them this version. GPATH="$(path_of drawable/ic_launcher_ground)" test -n "$GPATH" || bad "drawable/ic_launcher_ground resolves to no file in this APK" - GTREE="$("$A2" dump xmltree "$APK" --file "$GPATH" 2>/dev/null)" || bad "aapt2 cannot read $GPATH out of this APK" + GTREE="$("$A2" dump xmltree "$APKW" --file "$GPATH" 2>/dev/null)" || bad "aapt2 cannot read $GPATH out of this APK" IDS="$(echo "$GTREE" | grep -oE 'fillColor\(0x[0-9a-f]+\)=@0x[0-9a-f]+' | grep -oE '0x[0-9a-f]+$')" NFILL="$(echo "$IDS" | grep -c . || true)" [ "$NFILL" = 2 ] || bad "the ground in this APK has $NFILL gradient fills, not 2 — concept 04 is a surface and the glow over it" @@ -155,10 +172,10 @@ else for id in $IDS; do g="$(path_of "$(name_of "$id")")" test -n "$g" || bad "the ground's gradient $id has no file in the resource table" - STOPS="$STOPS $("$A2" dump xmltree "$APK" --file "$g" | grep -oE 'color\(0x[0-9a-f]+\)=#[0-9a-f]{8}' | grep -oE '[0-9a-f]{8}$' | tr '\n' ' ')" + STOPS="$STOPS $("$A2" dump xmltree "$APKW" --file "$g" | grep -oE 'color\(0x[0-9a-f]+\)=#[0-9a-f]{8}' | grep -oE '[0-9a-f]{8}$' | tr '\n' ' ')" done for c in ff0a2330 ff07141f ff040a10 2e31c0cb 0d1f8fc9 0007141f; do - echo "$STOPS" | grep -qw "$c" || bad "the ground in this APK has lost concept 04's stop #$c" + grep -qw "$c" <<< "$STOPS" || bad "the ground in this APK has lost concept 04's stop #$c" done echo "== ground: = $BGNAME, concept 04's six stops, read back out of the APK" fi @@ -172,16 +189,16 @@ if [ "$CRUNCH" = 0 ]; then # Nothing but the overlay makes a mipmap-anydpi-v26 file, so its absence # is exactly the failure this script exists for. for x in ic_launcher ic_launcher_round; do - echo "$LIST" | grep -q " res/mipmap-anydpi-v26/$x\.xml$" \ + grep -q " res/mipmap-anydpi-v26/$x\.xml$" <<< "$LIST" \ || bad "res/mipmap-anydpi-v26/$x.xml is not in the APK — $RES never reached src-tauri/gen/android/app/src/main/res" done # and the ground by name as well. In release the paths are shortened # away and only the table above can find it; here it is still a file. - echo "$LIST" | grep -q " res/drawable/ic_launcher_ground\.xml$" \ + grep -q " res/drawable/ic_launcher_ground\.xml$" <<< "$LIST" \ || bad "res/drawable/ic_launcher_ground.xml is not in the APK — the adaptive background never reached src-tauri/gen/android/app/src/main/res" for d in $DPI; do for n in $NAMES; do got="res/mipmap-$d-v4/$n.png" - echo "$LIST" | grep -q " $got$" || bad "$got is not in the APK" + grep -q " $got$" <<< "$LIST" || bad "$got is not in the APK" unzip -p "$APK" "$got" | cmp -s - "$RES/mipmap-$d/$n.png" \ || bad "$got in the APK is not $RES/mipmap-$d/$n.png — a stale res in gen/, the overlay lost a file, or this build crunched its PNGs (then: --crunched)" done; done @@ -207,7 +224,7 @@ MAP="$(echo "$TBL" | awk ' # the other half of the same optimization pass: with resource shrinking on, # aapt2 also collapses the NAMES, and then even the table cannot say which # entry is the launcher icon -if echo "$MAP" | grep -q "0_resource_name_obfuscated"; then +if grep -q "0_resource_name_obfuscated" <<< "$MAP"; then echo "== icon: NOT CHECKED — this APK's resource names are collapsed" echo " (aapt2 optimize --collapse-resource-names, which comes with resource" echo " shrinking). Nothing left to match on. Unverified here, not missing."