#!/usr/bin/env bash # Build YAMA Android APK # Works on WSL, native Linux, macOS # Usage: ./build_apk.sh [debug|release] (default: debug) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # ── Detect WSL ─────────────────────────────────────────────────────────────── IS_WSL=false grep -qi microsoft /proc/version 2>/dev/null && IS_WSL=true # ── Find Android SDK ───────────────────────────────────────────────────────── if [ -n "${ANDROID_HOME:-}" ] && [ -d "${ANDROID_HOME}" ]; then SDK_DIR="$ANDROID_HOME" elif [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "${ANDROID_SDK_ROOT}" ]; then SDK_DIR="$ANDROID_SDK_ROOT" elif $IS_WSL; then # Auto-detect from Windows %USERPROFILE% WIN_USER=$(cmd.exe /c "echo %USERPROFILE%" 2>/dev/null | tr -d '\r') WSL_USER=$(wslpath "$WIN_USER" 2>/dev/null || \ echo "$WIN_USER" | sed 's|\\|/|g; s|^\([A-Za-z]\):|/mnt/\L\1|') SDK_DIR="${WSL_USER}/AppData/Local/Android/Sdk" elif [ -d "$HOME/Android/Sdk" ]; then SDK_DIR="$HOME/Android/Sdk" elif [ -d "$HOME/Library/Android/sdk" ]; then SDK_DIR="$HOME/Library/Android/sdk" else SDK_DIR="" fi if [ -z "${SDK_DIR:-}" ] || [ ! -d "$SDK_DIR" ]; then echo "ERROR: Android SDK not found." echo "" echo " WSL: auto-detected from Windows %USERPROFILE%" echo " or set: export ANDROID_HOME=/mnt/c/Users//AppData/Local/Android/Sdk" echo " Native Linux: install SDK to ~/Android/Sdk or set ANDROID_HOME" exit 1 fi echo "SDK : $SDK_DIR" # ── Check Java 17+ ─────────────────────────────────────────────────────────── if ! command -v java &>/dev/null; then echo "ERROR: Java not found." echo " sudo apt install openjdk-17-jdk" exit 1 fi JAVA_VER=$(java -version 2>&1 | grep -oP '(?<=version ")[0-9]+' | head -1) if [ "${JAVA_VER:-0}" -lt 17 ]; then echo "ERROR: Java 17+ required (found Java ${JAVA_VER:-unknown})." echo " sudo apt install openjdk-17-jdk" exit 1 fi echo "Java: $(java -version 2>&1 | head -1)" # ── Check cmake ─────────────────────────────────────────────────────────────── # The SDK ships cmake.exe (Windows-only); on Linux/WSL we need system cmake. if ! command -v cmake &>/dev/null; then echo "ERROR: cmake not found." echo " sudo apt install cmake" echo "" echo " The SDK's cmake is a Windows .exe and cannot run on Linux/WSL." exit 1 fi echo "CMake: $(cmake --version | head -1)" # ── Check ninja ────────────────────────────────────────────────────────────── if ! command -v ninja &>/dev/null; then echo "ERROR: ninja not found." echo " sudo apt install ninja-build" exit 1 fi # ── WSL: create Linux stubs for Windows build-tools ────────────────────────── # Windows SDK ships .exe files only; Linux Gradle needs plain-name wrappers. # WSL binfmt_misc executes .exe files transparently from bash. # We FORCE-WRITE stubs (don't rely on prior state) so broken/dir artifacts # from previous attempts don't block the check. if $IS_WSL; then BT_DIR=$(find "$SDK_DIR/build-tools" -maxdepth 1 -mindepth 1 -type d 2>/dev/null \ | sort -V | tail -1) # Stub every installed build-tools version — the project may reference any of them BT_VERSIONS=$(find "$SDK_DIR/build-tools" -maxdepth 1 -mindepth 1 -type d 2>/dev/null \ | sort -V) LATEST_BT="" if [ -z "$BT_VERSIONS" ]; then echo "WARNING: build-tools directory not found under $SDK_DIR/build-tools" else for BT_DIR in $BT_VERSIONS; do LATEST_BT="$BT_DIR" # last in sorted list = newest VER=$(basename "$BT_DIR") # 1. Wrap every .exe found — covers aapt, aapt2, aidl, split-select, etc. for EXE in "${BT_DIR}"/*.exe; do [ -f "$EXE" ] || continue TOOL=$(basename "$EXE" .exe) BIN="${BT_DIR}/${TOOL}" [ -e "$BIN" ] && rm -rf "$BIN" printf '#!/bin/sh\nexec "%s" "$@"\n' "$EXE" > "$BIN" chmod +x "$BIN" echo "Stubbed: ${VER}/${TOOL} → $(basename $EXE)" done # 2. Ensure tools AGP validates that may not have .exe versions for TOOL in aapt aapt2 aidl split-select dexdump zipalign apksigner d8; do BIN="${BT_DIR}/${TOOL}" if [ ! -f "$BIN" ]; then printf '#!/bin/sh\nexit 0\n' > "$BIN" chmod +x "$BIN" echo "Noop: ${VER}/${TOOL}" fi done done fi # If build.gradle requests a buildToolsVersion that isn't installed, create a # stub directory pointing all tools to the latest installed version. if [ -n "$LATEST_BT" ]; then REQ_VER=$(grep -oP '(?<=buildToolsVersion\s")[^"]+' \ "$SCRIPT_DIR/app/build.gradle" 2>/dev/null | head -1 || true) if [ -n "$REQ_VER" ]; then REQ_DIR="$SDK_DIR/build-tools/$REQ_VER" if [ ! -d "$REQ_DIR" ]; then echo "build-tools $REQ_VER not installed — creating stub dir from $(basename $LATEST_BT)" mkdir -p "$REQ_DIR" # Copy source.properties so AGP recognises the version if [ -f "${LATEST_BT}/source.properties" ]; then sed "s/Pkg.Revision=.*/Pkg.Revision=$REQ_VER/" \ "${LATEST_BT}/source.properties" > "${REQ_DIR}/source.properties" else printf 'Pkg.Desc=Android SDK Build-tools\nPkg.Revision=%s\n' \ "$REQ_VER" > "${REQ_DIR}/source.properties" fi for TOOL in aapt aapt2 aidl split-select dexdump zipalign apksigner d8; do BIN="${REQ_DIR}/${TOOL}" SRC="${LATEST_BT}/${TOOL}" # already stubbed above if [ -f "$SRC" ]; then cp "$SRC" "$BIN" chmod +x "$BIN" else printf '#!/bin/sh\nexit 0\n' > "$BIN" chmod +x "$BIN" fi echo "Forwarded: ${REQ_VER}/${TOOL} → $(basename $LATEST_BT)" done fi fi fi # ── WSL: stub SDK cmake ──────────────────────────────────────────────────── # AGP resolves cmake from SDK cmake//bin/cmake (a Windows .exe). # Create plain-name stubs that call system cmake/ninja instead. SYS_CMAKE=$(command -v cmake 2>/dev/null || true) SYS_NINJA=$(command -v ninja 2>/dev/null || true) CMAKE_VERSIONS=$(find "$SDK_DIR/cmake" -maxdepth 1 -mindepth 1 -type d 2>/dev/null \ | sort -V || true) for SDK_CMAKE_DIR in $CMAKE_VERSIONS; do CVER=$(basename "$SDK_CMAKE_DIR") CBIN="${SDK_CMAKE_DIR}/bin" mkdir -p "$CBIN" for TOOL in cmake ninja; do BIN="${CBIN}/${TOOL}" [ -e "$BIN" ] && rm -rf "$BIN" if [ "$TOOL" = "cmake" ] && [ -n "$SYS_CMAKE" ]; then printf '#!/bin/sh\nexec "%s" "$@"\n' "$SYS_CMAKE" > "$BIN" echo "CMake stub: ${CVER}/cmake → $SYS_CMAKE" elif [ "$TOOL" = "ninja" ] && [ -n "$SYS_NINJA" ]; then printf '#!/bin/sh\nexec "%s" "$@"\n' "$SYS_NINJA" > "$BIN" echo "CMake stub: ${CVER}/ninja → $SYS_NINJA" else printf '#!/bin/sh\nexit 0\n' > "$BIN" echo "CMake noop: ${CVER}/${TOOL}" fi chmod +x "$BIN" done done # ── WSL: verify Linux NDK ───────────────────────────────────────────────── # Windows NDK (.exe tools) cannot compile from WSL — clang.exe doesn't # understand /mnt/c/ Linux paths. A real Linux NDK is required. NDK_DIR="" # 1. Honour ANDROID_NDK_HOME if set if [ -n "${ANDROID_NDK_HOME:-}" ] && \ [ -f "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" ]; then NDK_DIR="$ANDROID_NDK_HOME" fi # 2. Scan SDK ndk/ for a version that has real Linux ELF binaries if [ -z "$NDK_DIR" ]; then for CANDIDATE in $(find "$SDK_DIR/ndk" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort -V); do CBIN="${CANDIDATE}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" if [ -f "$CBIN" ] && file -L "$CBIN" 2>/dev/null | grep -q ELF; then NDK_DIR="$CANDIDATE" fi done fi # 3. Check common install location ~/android-ndk/ if [ -z "$NDK_DIR" ]; then for CANDIDATE in "$HOME/android-ndk" "$HOME/android-ndk-linux"; do CBIN="${CANDIDATE}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang" if [ -f "$CBIN" ] && file -L "$CBIN" 2>/dev/null | grep -q ELF; then NDK_DIR="$CANDIDATE" break fi done fi if [ -z "$NDK_DIR" ]; then # Determine the required NDK version from build.gradle for the download hint REQ_NDK=$(grep -oP '(?<=ndkVersion\s")[^"]+' \ "$SCRIPT_DIR/app/build.gradle" 2>/dev/null | head -1 || true) NDK_MAJ=${REQ_NDK%%.*} echo "" echo "ERROR: Linux NDK not found." echo " The Windows NDK cannot compile native code in WSL." echo "" # Look up the exact Linux zip name from the repository manifest ZIP_NAME=$(curl -s 'https://dl.google.com/android/repository/repository2-3.xml' 2>/dev/null \ | grep -A 10 "ndk;${REQ_NDK}" | grep 'linux\.zip' | grep -oP '(?<=)[^<]+' | head -1) ZIP_NAME="${ZIP_NAME:-android-ndk-r${NDK_MAJ:-30}-linux.zip}" echo " Install the Linux NDK (requires ~600 MB):" echo " wget https://dl.google.com/android/repository/${ZIP_NAME}" echo " unzip ${ZIP_NAME} -d \$HOME" echo " mv \$HOME/\$(basename ${ZIP_NAME} .zip) \$HOME/android-ndk" echo "" echo " Then re-run ./build_apk.sh" echo " (Or set ANDROID_NDK_HOME to your Linux NDK path)" exit 1 fi echo "NDK : $NDK_DIR" fi # ── Write local.properties (backup & restore to avoid clobbering Android Studio) ── PROPS="$SCRIPT_DIR/local.properties" PROPS_BAK="$SCRIPT_DIR/local.properties.bak" [ -f "$PROPS" ] && cp "$PROPS" "$PROPS_BAK" cat > "$PROPS" <> "$PROPS" fi echo "local.properties written (original backed up to local.properties.bak)" restore_props() { if [ -f "$PROPS_BAK" ]; then mv "$PROPS_BAK" "$PROPS" echo "local.properties restored" else rm -f "$PROPS" fi } trap restore_props EXIT # ── Build ───────────────────────────────────────────────────────────────────── BUILD_TYPE="${1:-release}" case "$BUILD_TYPE" in release) TASK="assembleRelease" ;; debug) TASK="assembleDebug" ;; clean) chmod +x gradlew ./gradlew clean rm -rf "$SCRIPT_DIR/app/.cxx" rm -f "$SCRIPT_DIR/ghost.apk" "$SCRIPT_DIR/ghost-debug.apk" echo "Clean done." exit 0 ;; *) echo "Usage: $0 [release|debug|clean]"; exit 1 ;; esac # ── Release: keystore check ─────────────────────────────────────────────────── if [ "$BUILD_TYPE" = "release" ]; then JKS="$SCRIPT_DIR/yama-release.jks" if [ ! -f "$JKS" ]; then echo "ERROR: $JKS not found." echo " Generate once with:" echo " keytool -genkeypair -keystore android/yama-release.jks \\" echo " -alias yama -keyalg RSA -keysize 2048 -validity 10000 \\" echo " -dname 'CN=YAMA,O=Internal,C=CN'" exit 1 fi if [ -z "${YAMA_PWD:-}" ]; then read -rsp "Keystore password: " YAMA_PWD echo export YAMA_PWD fi fi echo "" echo "Building $BUILD_TYPE APK..." chmod +x gradlew ./gradlew "$TASK" -PincludeEmulatorAbis=false # ── Output ──────────────────────────────────────────────────────────────────── APK=$(find "app/build/outputs/apk/${BUILD_TYPE}" -name "*.apk" 2>/dev/null | head -1 || true) if [ -n "$APK" ]; then [ "$BUILD_TYPE" = "release" ] && OUT="$SCRIPT_DIR/ghost.apk" || OUT="$SCRIPT_DIR/ghost-${BUILD_TYPE}.apk" cp "$APK" "$OUT" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "APK: $OUT" if $IS_WSL; then WIN=$(wslpath -w "$OUT" 2>/dev/null || true) [ -n "$WIN" ] && echo "WIN: $WIN" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" fi