Counts Gradle tasks: --dry-run gives the total, the build gives one '> Task :x' line each as it goes. Gradle offers no direct route -- an init script using taskGraph.afterTask is rejected by the configuration cache, and whenReady never fires on a cache hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQz6R4kBQcWSHBNZMgBnjL
103 lines
4.4 KiB
Bash
Executable File
103 lines
4.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# Builds the app's APK, ready to install on a phone through Dev Updater.
|
|
#
|
|
# ./build-apk.sh
|
|
#
|
|
# The APK pins the CA on *this* machine ($XDG_CONFIG_HOME/ai-app/certs/ca.pem,
|
|
# or AI_APP_CA), so build it on the machine that runs the backend: an app
|
|
# built somewhere else trusts a CA that backend can't present, and simply
|
|
# won't connect. Start ai-server once first if there are no certificates
|
|
# yet -- it generates them; the build stops with that instruction if it
|
|
# can't find one.
|
|
#
|
|
# Unlike ./run-android.sh, this touches no emulator: it only produces the
|
|
# file. Installing on a real phone goes through Dev Updater, which serves
|
|
# whatever is under this project's build directory.
|
|
set -eu
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# Prefer an SDK this machine has already configured -- the host and the dev
|
|
# VM don't keep it in the same place, and android-env.sh is written for the
|
|
# VM's layout (it also installs missing packages, which isn't wanted here).
|
|
if [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}" ]; then
|
|
echo "==> Using ANDROID_HOME=$ANDROID_HOME"
|
|
elif [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "${ANDROID_SDK_ROOT}" ]; then
|
|
ANDROID_HOME="$ANDROID_SDK_ROOT"
|
|
export ANDROID_HOME
|
|
echo "==> Using ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
|
|
elif [ -d "$HOME/Android/Sdk" ]; then
|
|
ANDROID_HOME="$HOME/Android/Sdk"
|
|
ANDROID_SDK_ROOT="$ANDROID_HOME"
|
|
export ANDROID_HOME ANDROID_SDK_ROOT
|
|
echo "==> Using $ANDROID_HOME"
|
|
else
|
|
echo "No Android SDK found. Set ANDROID_HOME to it, or install one" >&2
|
|
echo "(Android Studio's default location is ~/Android/Sdk)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
CA="${AI_APP_CA:-${XDG_CONFIG_HOME:-$HOME/.config}/ai-app/certs/ca.pem}"
|
|
if [ -f "$CA" ]; then
|
|
# Printed so a wrong or stale certificate is visible here rather than
|
|
# as a handshake failure on the phone -- compare it against the CA the
|
|
# backend is actually presenting.
|
|
FINGERPRINT=$(openssl x509 -in "$CA" -pubkey -noout 2>/dev/null \
|
|
| openssl pkey -pubin -outform der 2>/dev/null \
|
|
| openssl dgst -sha256 -binary 2>/dev/null \
|
|
| openssl base64 2>/dev/null || echo "(openssl unavailable)")
|
|
echo "==> Pinning the CA at $CA"
|
|
echo " fingerprint: $FINGERPRINT"
|
|
else
|
|
echo "No CA certificate at $CA -- start ai-server once on this machine" >&2
|
|
echo "(it generates them), or set AI_APP_CA. The APK embeds it at build time." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Dev Updater draws a real progress bar from "@@progress done/total" lines,
|
|
# and ignores anything that isn't exactly that shape. Gradle can't be asked
|
|
# for this directly: an init script using taskGraph.afterTask is rejected
|
|
# outright by the configuration cache, and whenReady never fires on a cache
|
|
# hit. --dry-run costs about a second, is cache-friendly, and prints one
|
|
# ":task SKIPPED" line per task the real build will run, which is exactly
|
|
# the total. The build then prints one "> Task :x" line per task as it
|
|
# goes, so counting those against it is the whole mechanism.
|
|
#
|
|
# Task count is not time -- compileDebugKotlin and dexBuilder are most of
|
|
# the wall clock -- so the bar moves unevenly. It is still counted work
|
|
# rather than a guess at how long last time took.
|
|
TASKS=$(./gradlew :androidApp:assembleDebug --dry-run --console=plain 2>/dev/null \
|
|
| grep -c '^:[A-Za-z:]* SKIPPED' || true)
|
|
|
|
echo "==> Building"
|
|
if [ "${TASKS:-0}" -gt 0 ]; then
|
|
echo "@@progress 0/$TASKS"
|
|
DONE=0
|
|
./gradlew :androidApp:assembleDebug --console=plain 2>&1 | while IFS= read -r line; do
|
|
echo "$line"
|
|
case "$line" in
|
|
"> Task "*)
|
|
DONE=$((DONE + 1))
|
|
echo "@@progress $DONE/$TASKS"
|
|
;;
|
|
esac
|
|
done
|
|
# The pipeline's exit status is the shell's, not gradle's, so ask
|
|
# gradle again rather than reporting a failed build as a success. It is
|
|
# up to date by now, so this is a second or two.
|
|
./gradlew :androidApp:assembleDebug --console=plain >/dev/null
|
|
else
|
|
./gradlew :androidApp:assembleDebug
|
|
fi
|
|
|
|
APK="$SCRIPT_DIR/androidApp/build/outputs/apk/debug/androidApp-debug.apk"
|
|
echo
|
|
echo "==> Built $APK"
|
|
[ -f "$APK" ] && ls -lh "$APK" | awk '{print " " $5}'
|
|
echo
|
|
echo "To get it onto the phone: add this project to Dev Updater (or hit"
|
|
echo "Update on it if it's already there) and install from there."
|
|
echo "Then start the backend and scan the enrollment QR it prints:"
|
|
echo " ./server/target/release/ai-server --rotate-token"
|