Files
CST-agent/scripts/build_apk_android.sh
T
WHCandCursor 5538ed3412
ci / tcc-check (push) Successful in 1m8s
ci / linux (aarch64, 0) (push) Failing after 1h0m3s
ci / linux (x86, 1) (push) Failing after 1h0m3s
ci / linux (x86_64, 1) (push) Failing after 1h0m2s
ci / windows (push) Failing after 50s
refactor: archive GUI clients; keep minimal TUI with hooks/API
Move Qt/Tauri/full Web SPA under archive/, ship hermit as CLI/TUI plus
libcstcore and cst-webui/1 HTTP+SSE (turn lifecycle hooks, capabilities).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 22:49:23 +08:00

170 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# ARCHIVED: Qt APK front end under archive/qt-app; not part of default Hermit builds.
# Build the Qt Quick Android APK of archive/qt-app/ (the engine is linked in-process, so
# the APK opens no port). This is the front end counterpart of
# scripts/build_android.sh, which cross-builds the engine alone.
#
# scripts/build_apk_android.sh [--install] [--logs] [--clean]
#
# --install adb install -r the debug APK on the connected device
# --logs stream logcat after installing (Qt + engine tags)
# --clean wipe the build directory first
#
# Needs, all overridable through the environment:
# QT_ANDROID Qt for Android (default ~/Qt/<ver>/android_arm64_v8a)
# QT_HOST matching host Qt (default ~/Qt/<ver>/gcc_64)
# ANDROID_SDK_ROOT SDK with platforms/ + build-tools/ (see docs/android.md)
# ANDROID_NDK_HOME NDK, same one as scripts/build_third_party_android.sh
# JAVA_HOME JDK 17+ for Gradle (falls back to the javac on PATH)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
ABI="${ABI:-arm64-v8a}"
API_LEVEL="${API_LEVEL:-24}"
BUILD="$ROOT/build-apk-android"
PREFIX="$ROOT/third_party/prefix-android-$ABI"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
DO_INSTALL=0
DO_LOGS=0
for a in "$@"; do
case "$a" in
--install) DO_INSTALL=1 ;;
--logs) DO_INSTALL=1; DO_LOGS=1 ;;
--clean) rm -rf "$BUILD" ;;
*) echo "usage: $0 [--install] [--logs] [--clean]" >&2; exit 1 ;;
esac
done
# --- Qt: Android target first, then the host tools of the same version ----
if [[ -z "${QT_ANDROID:-}" ]]; then
# aqt names the directory after the ABI with underscores: android_arm64_v8a
for q in "$HOME"/Qt/*/android_"${ABI//-/_}"/lib/cmake/Qt6/qt.toolchain.cmake; do
[[ -f "$q" ]] && QT_ANDROID="${q%/lib/cmake/Qt6/qt.toolchain.cmake}"
done
fi
if [[ -z "${QT_ANDROID:-}" || ! -x "$QT_ANDROID/bin/qt-cmake" ]]; then
echo "set QT_ANDROID to a Qt for Android install ('aqt install-qt all_os android ...')" >&2
exit 1
fi
QT_VER="$(basename "$(dirname "$QT_ANDROID")")"
QT_HOST="${QT_HOST:-$(dirname "$QT_ANDROID")/gcc_64}"
if [[ ! -x "$QT_HOST/bin/qmake" && ! -x "$QT_HOST/bin/androiddeployqt" ]]; then
echo "set QT_HOST to the host Qt $QT_VER (same version, needed for androiddeployqt)" >&2
exit 1
fi
NDK="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
if [[ -z "$NDK" || ! -f "$NDK/build/cmake/android.toolchain.cmake" ]]; then
echo "set ANDROID_NDK_HOME to an NDK (see scripts/build_third_party_android.sh)" >&2
exit 1
fi
if [[ -z "${ANDROID_SDK_ROOT:-}" || ! -d "$ANDROID_SDK_ROOT/platforms" ]]; then
echo "set ANDROID_SDK_ROOT to an SDK with platforms/android-<n> and build-tools/" >&2
exit 1
fi
if [[ -z "${JAVA_HOME:-}" ]]; then
JAVAC="$(command -v javac || true)"
[[ -n "$JAVAC" ]] && JAVA_HOME="$(dirname "$(dirname "$(readlink -f "$JAVAC")")")"
fi
if [[ -z "${JAVA_HOME:-}" || ! -x "$JAVA_HOME/bin/java" ]]; then
echo "set JAVA_HOME to a JDK 17+ (Gradle needs it)" >&2
exit 1
fi
if [[ ! -f "$PREFIX/lib/libcurl.a" ]]; then
echo "missing $PREFIX/lib/libcurl.a -- run:" >&2
echo " scripts/build_third_party_android.sh $ABI all" >&2
exit 1
fi
export ANDROID_SDK_ROOT ANDROID_NDK_HOME="$NDK" ANDROID_NDK_ROOT="$NDK" JAVA_HOME
export PATH="$JAVA_HOME/bin:$PATH"
# androiddeployqt shells out to the generated project's gradlew, which picks up
# init scripts from $GRADLE_USER_HOME/init.d. Install ours (release lint off)
# so every build behaves the same. See the file for why lint has to go.
GRADLE_INIT_DIR="${GRADLE_USER_HOME:-$HOME/.gradle}/init.d"
mkdir -p "$GRADLE_INIT_DIR"
if ! cmp -s "$ROOT/scripts/gradle-android-init.gradle" "$GRADLE_INIT_DIR/cst-android.gradle"; then
install -m644 "$ROOT/scripts/gradle-android-init.gradle" "$GRADLE_INIT_DIR/cst-android.gradle"
echo "installed $GRADLE_INIT_DIR/cst-android.gradle"
fi
# The release APK has to be signed to install. With CST_ANDROID_KEYSTORE the
# init script uses that keystore; otherwise it falls back to the standard
# Android debug key, created here on first use (same one Android Studio makes).
KEYSTORE="${CST_ANDROID_KEYSTORE:-$HOME/.android/debug.keystore}"
if [[ ! -f "$KEYSTORE" && -z "${CST_ANDROID_KEYSTORE:-}" ]]; then
mkdir -p "$(dirname "$KEYSTORE")"
keytool -genkeypair -keystore "$KEYSTORE" -storepass android -keypass android \
-alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=Android Debug,O=Android,C=US" >/dev/null
chmod 600 "$KEYSTORE"
echo "created debug keystore: $KEYSTORE"
fi
# androiddeployqt reads these (the path/alias/passwords never reach the CMake
# command line, only the QT_ANDROID_SIGN_APK flag does).
export QT_ANDROID_KEYSTORE_PATH="$KEYSTORE"
export QT_ANDROID_KEYSTORE_ALIAS="${CST_ANDROID_KEY_ALIAS:-androiddebugkey}"
export QT_ANDROID_KEYSTORE_STORE_PASS="${CST_ANDROID_KEYSTORE_PASS:-android}"
export QT_ANDROID_KEYSTORE_KEY_PASS="${CST_ANDROID_KEY_PASS:-android}"
# NixOS has no /bin/bash, and build-tools' shell wrappers (apksigner is the one
# androiddeployqt calls, d8 and friends are the rest) start with #!/bin/bash.
# Point them at the bash that exists here; the SDK is a local copy, so this
# sticks.
BT_DIR="$(ls -d "$ANDROID_SDK_ROOT"/build-tools/* 2>/dev/null | sort -V | tail -1)"
BASH_BIN="$(command -v bash || echo /bin/sh)"
if [[ -n "$BT_DIR" ]]; then
while IFS= read -r f; do
sed -i "1s|^#!/bin/bash|#!$BASH_BIN|" "$f" && echo "fixed shebang: ${f#"$ANDROID_SDK_ROOT"/}"
done < <(grep -rls '^#!/bin/bash' "$BT_DIR" 2>/dev/null)
fi
# Qt6Gui wants GLESv2/EGL: the NDK sysroot has both, but Qt's Find modules
# only search CMAKE_FIND_ROOT_PATH prefixes (<sysroot>/include), while the
# headers live in <sysroot>/usr/include -- point them there directly.
NDK_SYSROOT_INC="$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include"
[[ -d "$NDK_SYSROOT_INC/GLES2" ]] || echo "warning: no GLES2 headers under $NDK_SYSROOT_INC" >&2
echo "Qt for Android : $QT_ANDROID ($QT_VER)"
echo "host Qt : $QT_HOST"
echo "SDK : $ANDROID_SDK_ROOT ($(ls "$ANDROID_SDK_ROOT/build-tools" | tr '\n' ' '))"
echo "NDK : $NDK"
echo "JDK : $JAVA_HOME"
echo "ABI : $ABI, minSdk $API_LEVEL"
# The engine comes from libcstcore.a, exactly as on the desktop: no TUI, no
# HTTP server (src/webui_stub.c stands in), no bundled busybox inside an APK.
"$QT_ANDROID/bin/qt-cmake" -S "$ROOT" -B "$BUILD" -G Ninja \
-DQT_HOST_PATH="$QT_HOST" \
-DANDROID_ABI="$ABI" -DANDROID_PLATFORM="android-$API_LEVEL" \
-DANDROID_SDK_ROOT="$ANDROID_SDK_ROOT" -DANDROID_NDK="$NDK" \
-DGLESv2_INCLUDE_DIR="$NDK_SYSROOT_INC" -DEGL_INCLUDE_DIR="$NDK_SYSROOT_INC" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$PREFIX" \
-DCURL_INCLUDE_DIR="$PREFIX/include" -DCURL_LIBRARY="$PREFIX/lib/libcurl.a" \
-DCMAKE_EXE_LINKER_FLAGS="-L$PREFIX/lib" \
-DCMAKE_SHARED_LINKER_FLAGS="-L$PREFIX/lib" \
-DCMAKE_MODULE_LINKER_FLAGS="-L$PREFIX/lib" \
-DCST_BUILD_TUI=OFF -DCST_BUILD_WEBUI=OFF -DCST_BUILD_APP=ON \
-DCST_BUNDLE_BUSYBOX=OFF
cmake --build "$BUILD" --target apk -j"$JOBS"
# AGP names the file after the variant and signing state
# (android-build-release.apk vs ...-unsigned.apk), so take the newest one.
APK="$(find "$BUILD/archive/qt-app/android-build/build/outputs/apk" "$BUILD" -name '*.apk' \
-printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)"
[[ -n "$APK" && -f "$APK" ]] || { echo "no APK produced" >&2; exit 1; }
echo
echo "APK: $APK ($(du -h "$APK" | cut -f1))"
[[ "$DO_INSTALL" == 1 ]] || exit 0
ADB="${ADB:-adb}"
command -v "$ADB" >/dev/null || { echo "adb not found (nix-shell -p android-tools)" >&2; exit 1; }
"$ADB" install -r "$APK"
echo "installed: $("$ADB" shell pm list packages | grep cstagent)"
[[ "$DO_LOGS" == 1 ]] || exit 0
echo "--- am start (Ctrl-C to stop; logcat follows)"
"$ADB" shell monkey -p xyz.whcself.cstagent -c android.intent.category.LAUNCHER 1 >/dev/null
sleep 2
exec "$ADB" logcat -s cst-agent:V Qt:V AndroidRuntime:E libc:V DEBUG:V