Feat: Android client Phase 0-3 full implementation
This commit was merged in pull request #3.
This commit is contained in:
307
android/build_apk.sh
Normal file
307
android/build_apk.sh
Normal file
@@ -0,0 +1,307 @@
|
||||
#!/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/<you>/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/<ver>/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 '(?<=<url>)[^<]+' | 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" <<EOF
|
||||
sdk.dir=$SDK_DIR
|
||||
EOF
|
||||
if $IS_WSL && [ -n "${NDK_DIR:-}" ]; then
|
||||
echo "ndk.dir=$NDK_DIR" >> "$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"
|
||||
|
||||
# ── 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
|
||||
Reference in New Issue
Block a user