#!/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//android_arm64_v8a) # QT_HOST matching host Qt (default ~/Qt//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- 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 (/include), while the # headers live in /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