🏗️ Build and Test JellyTau / Run Tests (push) Successful in 25m24s
🏗️ Build and Test JellyTau / Supply Chain (push) Successful in 54s
📱 Test APK / Build test APK (push) Failing after 50m57s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 9m47s
Traceability Validation / Check Requirement Traces (push) Successful in 25s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 7m18s
build-android.sh hardcoded `export ANDROID_HOME="$HOME/Android/Sdk"`, which discarded whatever the caller had set. In the builder image the SDK is at /opt/android-sdk and the job exports exactly that, so the script looked for an NDK under /root/Android/Sdk, found nothing, and the build died a minute in with "Android SDK not found" -- the first automatic `latest` APK build failed on this and nothing else. Now a default rather than an override, matching what test-player-conformance.sh already did. NDK_HOME likewise prefers an explicitly pinned ANDROID_NDK_HOME over guessing with `ls | head -1`, which is how CI pins an exact NDK revision. A missing SDK now fails immediately and says which variable to set, instead of letting `ls` print its own error and the real failure surface a minute later inside the tauri CLI. android-dev.sh had the same override and gets the same treatment.
193 lines
8.0 KiB
Bash
Executable File
193 lines
8.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build Android APK
|
|
|
|
set -e
|
|
|
|
# Source Rust environment
|
|
source "$HOME/.cargo/env.fish" 2>/dev/null || source "$HOME/.cargo/env" 2>/dev/null || true
|
|
|
|
# Set Android environment variables.
|
|
#
|
|
# Defaults, not overrides. A developer's SDK is at ~/Android/Sdk, but CI runs in
|
|
# the builder image where it lives at /opt/android-sdk and the job sets
|
|
# ANDROID_HOME accordingly — hardcoding the home-directory path here silently
|
|
# discarded that and the build died with "Android SDK not found" a minute in.
|
|
# `test-player-conformance.sh` already had this right; this script did not.
|
|
export ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}"
|
|
export ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
|
|
|
|
if [ ! -d "$ANDROID_HOME/ndk" ]; then
|
|
echo "❌ No NDK directory at $ANDROID_HOME/ndk" >&2
|
|
echo " Set ANDROID_HOME to your SDK location, or install the NDK." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Respect an NDK the caller has already picked (CI pins an exact revision via
|
|
# ANDROID_NDK_HOME); otherwise take whatever is installed.
|
|
export NDK_HOME="${NDK_HOME:-${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/$(ls "$ANDROID_HOME/ndk" | head -1)}}"
|
|
export ANDROID_NDK_HOME="$NDK_HOME"
|
|
|
|
echo "🤖 Building Android APK..."
|
|
echo "Android SDK: $ANDROID_HOME"
|
|
echo "NDK: $NDK_HOME"
|
|
echo ""
|
|
|
|
# Parse args: build type (debug/release) and optional --clean flag.
|
|
# By default the build is INCREMENTAL — Cargo and Vite reuse their caches.
|
|
# Pass --clean (or CLEAN=1) to wipe all caches for a from-scratch build.
|
|
#
|
|
# ABI selection: by default Tauri builds all four ABIs (arm64/arm/x86/x86_64),
|
|
# which is what a distributable universal APK needs — but for an on-device test
|
|
# it means three wasted Rust compiles. Pass --device (or ABI=aarch64) to build
|
|
# only the connected device's architecture; --abi <t> targets one explicitly.
|
|
#
|
|
# Side-by-side: the `debug` build type always installs as
|
|
# com.dtourolle.jellytau.debug ("JellyTau Debug"), so it never collides with a
|
|
# real install. `release --debug` puts a *release* build — R8-minified, exactly
|
|
# what ships — into that same slot, signed with the local debug keystore. That
|
|
# is how you validate minification (R8 stripping JNI-loaded classes has broken
|
|
# release APKs here before) without the real signing key and without
|
|
# uninstalling the app you actually use.
|
|
BUILD_TYPE="debug"
|
|
CLEAN="${CLEAN:-0}"
|
|
ABI="${ABI:-}"
|
|
SIDE_BY_SIDE="${SIDE_BY_SIDE:-0}"
|
|
next_is_abi=0
|
|
for arg in "$@"; do
|
|
if [ "$next_is_abi" = "1" ]; then
|
|
ABI="$arg"
|
|
next_is_abi=0
|
|
continue
|
|
fi
|
|
case "$arg" in
|
|
--clean) CLEAN=1 ;;
|
|
--abi) next_is_abi=1 ;;
|
|
--device) ABI="device" ;;
|
|
--debug|--side-by-side) SIDE_BY_SIDE=1 ;;
|
|
debug|release) BUILD_TYPE="$arg" ;;
|
|
esac
|
|
done
|
|
|
|
# The debug build type is side-by-side unconditionally; the flag only means
|
|
# something for a release build.
|
|
if [ "$BUILD_TYPE" = "debug" ]; then
|
|
SIDE_BY_SIDE=1
|
|
fi
|
|
|
|
# Resolve --device to the attached device's Rust target triple.
|
|
if [ "$ABI" = "device" ]; then
|
|
device_abi="$(adb shell getprop ro.product.cpu.abi 2>/dev/null | tr -d '\r\n')"
|
|
case "$device_abi" in
|
|
arm64-v8a) ABI="aarch64" ;;
|
|
armeabi-v7a) ABI="armv7" ;;
|
|
x86_64) ABI="x86_64" ;;
|
|
x86) ABI="i686" ;;
|
|
*)
|
|
echo "⚠️ Could not detect device ABI (got '${device_abi:-none}') — building all targets."
|
|
ABI=""
|
|
;;
|
|
esac
|
|
[ -n "$ABI" ] && echo "🎯 Device ABI $device_abi → building only '$ABI'"
|
|
fi
|
|
|
|
TARGET_ARGS=()
|
|
if [ -n "$ABI" ]; then
|
|
TARGET_ARGS=(--target "$ABI")
|
|
fi
|
|
|
|
# Step 0: Optionally clear build caches for a fully fresh build.
|
|
if [ "$CLEAN" = "1" ]; then
|
|
echo "🧹 Clearing build caches (clean build)..."
|
|
rm -rf node_modules/.vite dist .svelte-kit .next build target src-tauri/target 2>/dev/null || true
|
|
# `bun install`, NOT `npm install`. This is a bun project (see packageManager
|
|
# in package.json) and bun.lock is the lockfile that is committed; npm
|
|
# ignores it, re-resolves the tree from package.json alone, and writes a
|
|
# package-lock.json that .gitignore then hides.
|
|
#
|
|
# That is not cosmetic. The Tauri CLI refuses to build when a plugin's Rust
|
|
# crate and npm package differ by minor version, so the JS side is pinned
|
|
# exactly to match Cargo.lock; a re-resolve is precisely how those halves
|
|
# drift apart again. A clean build must not be able to change what gets
|
|
# installed.
|
|
bun install > /dev/null 2>&1
|
|
fi
|
|
|
|
# Step 1: Sync Android source files
|
|
echo "🔄 Syncing Android sources..."
|
|
./scripts/sync-android-sources.sh
|
|
|
|
# Step 2: Build the frontend first to avoid dev server issues
|
|
echo "🎨 Building frontend..."
|
|
bun run build
|
|
|
|
# Step 2: Build Android APK
|
|
# `--apk` is a boolean flag, NOT `--apk true`.
|
|
#
|
|
# tauri-cli took a value here until 2.10; from 2.11 it is a plain flag and the
|
|
# stray `true` is parsed as a positional argument, failing with
|
|
# "error: unexpected argument 'true' found" before the build starts. Found by
|
|
# deploying to a device after the Tauri 2.9.5 -> 2.11.5 upgrade.
|
|
if [ "$BUILD_TYPE" = "release" ] && [ "$SIDE_BY_SIDE" = "1" ]; then
|
|
# A release build in the debug slot: R8 still runs, but the applicationId is
|
|
# suffixed and the debug keystore signs it (read by build.gradle.kts from
|
|
# JT_SIDE_BY_SIDE), so the real key is not needed and it replaces any other
|
|
# .debug install cleanly. Deliberately does NOT write keystore.properties.
|
|
echo "📦 Building side-by-side release APK (com.dtourolle.jellytau.debug)..."
|
|
JT_SIDE_BY_SIDE=1 bun run tauri android build --apk "${TARGET_ARGS[@]}"
|
|
elif [ "$BUILD_TYPE" = "release" ]; then
|
|
# Configure release signing from .env (single source of truth). Must run
|
|
# after sync-android-sources.sh, since gen/android is (re)generated there.
|
|
./scripts/write-keystore-properties.sh
|
|
echo "📦 Building release APK..."
|
|
bun run tauri android build --apk "${TARGET_ARGS[@]}"
|
|
else
|
|
echo "📦 Building debug APK..."
|
|
bun run tauri android build --apk --debug "${TARGET_ARGS[@]}"
|
|
fi
|
|
|
|
# The applicationId the APK actually carries — not the one build.gradle.kts asks
|
|
# for. `tauri android build` rewrites the debug `buildTypes` block in the
|
|
# generated gradle file to inject its keepDebugSymbols entries, and that rewrite
|
|
# used to drop `applicationIdSuffix` with it, silently producing a debug APK
|
|
# under the release applicationId. Installing that over a real release build
|
|
# fails with INSTALL_FAILED_UPDATE_INCOMPATIBLE, whose only obvious remedy is
|
|
# uninstalling the release app and losing its data — so this fails the build
|
|
# instead. The suffix now lives outside the rewritten block (see
|
|
# src-tauri/android/app/build.gradle.kts); this checks that it survived.
|
|
assert_application_id() {
|
|
local variant="$1" expected="$2"
|
|
local metadata="src-tauri/gen/android/app/build/outputs/apk/universal/$variant/output-metadata.json"
|
|
|
|
[ -f "$metadata" ] || return 0
|
|
|
|
local actual
|
|
actual=$(sed -n 's/.*"applicationId"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata" | head -1)
|
|
|
|
if [ -n "$actual" ] && [ "$actual" != "$expected" ]; then
|
|
echo ""
|
|
echo "❌ APK applicationId is '$actual', expected '$expected'."
|
|
echo " A build meant for the side-by-side slot came out under the"
|
|
echo " release applicationId; installing it would collide with a real"
|
|
echo " install. Check that the applicationIdSuffix at the bottom of"
|
|
echo " src-tauri/android/app/build.gradle.kts survived into"
|
|
echo " src-tauri/gen/android/app/build.gradle.kts."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
if [ "$BUILD_TYPE" = "debug" ]; then
|
|
assert_application_id debug "com.dtourolle.jellytau.debug"
|
|
elif [ "$SIDE_BY_SIDE" = "1" ]; then
|
|
assert_application_id release "com.dtourolle.jellytau.debug"
|
|
else
|
|
assert_application_id release "com.dtourolle.jellytau"
|
|
fi
|
|
|
|
echo ""
|
|
echo "✅ APK build complete!"
|
|
echo "📱 APK location: src-tauri/gen/android/app/build/outputs/apk/"
|
|
|
|
# Containerised builds run as root against a bind-mounted tree; hand the
|
|
# artifacts back to the host user. No-op when not root. See DR-213.
|
|
"$(dirname "$0")/restore-ownership.sh"
|