#!/usr/bin/env bash # # android-setup.sh — one-time Android build environment setup for mtgc. # # Installs the pieces `cargo tauri android` needs that Android Studio does NOT # install by default: the SDK command-line tools, the NDK (required to # cross-compile the Rust core — see "Why the NDK is mandatory" below), and the # Rust Android targets. # # Assumes the Android SDK itself already exists (Android Studio's first-run # wizard creates it at ~/Android/Sdk). Set ANDROID_HOME to override. # # Safe to re-run: every step is skipped if already satisfied. # # Usage: # ./scripts/android-setup.sh # ANDROID_HOME=/opt/android-sdk ./scripts/android-setup.sh # NDK_VERSION=27.2.12479018 ./scripts/android-setup.sh # pin instead of latest # # Why the NDK is mandatory (not just nice to have): mtgc-core builds rusqlite # with the `bundled` feature, which compiles the SQLite C amalgamation, and # rustls pulls in `ring` (C/asm). Both need a C cross-toolchain. The bundled # SQLite can't be swapped for Android's system libsqlite3 either — the card # schema uses FTS5 with the `trigram` tokenizer, which platform SQLite on many # Android versions lacks entirely. # set -euo pipefail info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; } err() { printf '\033[1;31m[err]\033[0m %s\n' "$*" >&2; } ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}" # Bump if Google publishes a newer bundle; the URL pattern is stable. CMDLINE_TOOLS_BUILD="${CMDLINE_TOOLS_BUILD:-13114758}" CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_BUILD}_latest.zip" # Pinned to a known-good *stable* NDK on purpose. sdkmanager also publishes beta # and canary channels whose version numbers sort higher (e.g. 30.0.15729638 is # r30-beta2), so "install the newest" silently lands you on a prerelease # toolchain — not what you want compiling SQLite and ring. Override with # NDK_VERSION=... if you need a different one. DEFAULT_NDK_VERSION="28.2.13676358" RUST_TARGETS=( aarch64-linux-android # 64-bit ARM — virtually all real devices armv7-linux-androideabi # 32-bit ARM — older devices i686-linux-android # 32-bit x86 emulator x86_64-linux-android # 64-bit x86 emulator (the fast one on a desktop) ) # --- 1. Prerequisites ------------------------------------------------------ if ! command -v rustc >/dev/null 2>&1; then err "Rust not found. Install from https://rustup.rs and re-run." exit 1 fi info "Rust: $(rustc --version)" if cargo tauri --version >/dev/null 2>&1; then info "Tauri CLI: $(cargo tauri --version 2>&1 | head -1)" else err "Tauri CLI not found. Run ./scripts/devel-setup.sh first." exit 1 fi # --- 2. A JDK the Android Gradle Plugin actually accepts ------------------- # # Fedora's default JDK is newer than AGP/Gradle supports (a JDK 25 default fails # the Gradle build outright), so pin JAVA_HOME to 21 (or 17) for Android work. # # Android Studio's bundled JetBrains Runtime is preferred: it's the same JDK # Studio itself runs Gradle with, so it's guaranteed AGP-compatible, and it # needs no system package install. Note we test for bin/javac specifically — # a JRE-only or leftover-empty /usr/lib/jvm directory must not count. find_jdk() { # Explicit override wins. if [ -n "${ANDROID_JAVA_HOME:-}" ] && [ -x "${ANDROID_JAVA_HOME}/bin/javac" ]; then echo "$ANDROID_JAVA_HOME"; return 0 fi # Android Studio's bundled JBR (Toolbox or standalone install locations). for d in \ "$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \ "$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \ "$HOME/android-studio/jbr" \ /opt/android-studio/jbr do [ -x "$d/bin/javac" ] && { echo "$d"; return 0; } done # Fall back to a system JDK of an accepted major version. for v in 21 17; do for d in "/usr/lib/jvm/java-${v}-openjdk" /usr/lib/jvm/java-${v}-openjdk-*; do [ -x "$d/bin/javac" ] && { echo "$d"; return 0; } done done return 1 } if JDK_PATH="$(find_jdk)"; then export JAVA_HOME="$JDK_PATH" info "Android JDK: $JAVA_HOME ($("$JAVA_HOME/bin/java" -version 2>&1 | head -1))" else err "No JDK 21 or 17 found (Gradle rejects the newer system default)." err "Easiest fix: install Android Studio (its bundled JBR is used automatically)," err "or: sudo dnf install java-21-openjdk-devel" err "or: ANDROID_JAVA_HOME=/path/to/jdk ./scripts/android-setup.sh" exit 1 fi # --- 3. Android SDK -------------------------------------------------------- if [ ! -d "$ANDROID_HOME" ]; then err "No Android SDK at $ANDROID_HOME" err "Install Android Studio and run it once (its setup wizard downloads the SDK)," err "or set ANDROID_HOME to an existing SDK." exit 1 fi info "Android SDK: $ANDROID_HOME" # --- 4. SDK command-line tools (provides sdkmanager) ----------------------- SDKMANAGER="$(find "$ANDROID_HOME/cmdline-tools" -name sdkmanager -type f 2>/dev/null | head -1 || true)" if [ -z "$SDKMANAGER" ]; then info "cmdline-tools not present — installing (build $CMDLINE_TOOLS_BUILD)…" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT curl -fL --progress-bar -o "$tmp/cmdline-tools.zip" "$CMDLINE_TOOLS_URL" unzip -q "$tmp/cmdline-tools.zip" -d "$tmp" # sdkmanager expects to live at cmdline-tools/latest/, not cmdline-tools/. mkdir -p "$ANDROID_HOME/cmdline-tools" rm -rf "$ANDROID_HOME/cmdline-tools/latest" mv "$tmp/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" fi info "sdkmanager: $SDKMANAGER" sdk() { "$SDKMANAGER" --sdk_root="$ANDROID_HOME" "$@"; } # Accept licenses up front so the installs below don't block on a prompt. info "Accepting SDK licenses…" yes 2>/dev/null | sdk --licenses >/dev/null || true # --- 5. NDK ---------------------------------------------------------------- NDK_VERSION="${NDK_VERSION:-$DEFAULT_NDK_VERSION}" NDK_DIR="$ANDROID_HOME/ndk/$NDK_VERSION" if [ -d "$NDK_DIR" ]; then info "NDK already installed: $NDK_DIR" else info "Installing NDK $NDK_VERSION (large download)…" sdk --install "ndk;$NDK_VERSION" fi [ -d "$NDK_DIR" ] || { err "NDK install did not produce $NDK_DIR"; exit 1; } # Surface the actual release name so a prerelease can't slip by unnoticed. NDK_RELEASE="$(sed -n 's/^Pkg.Revision *= *//p' "$NDK_DIR/source.properties" 2>/dev/null)" info "NDK: $NDK_DIR (revision ${NDK_RELEASE:-unknown})" case "$NDK_RELEASE" in *beta*|*rc*|*canary*) warn "This NDK is a prerelease — pin a stable NDK_VERSION if builds misbehave." ;; esac # --- 6. Rust Android targets ---------------------------------------------- installed="$(rustup target list --installed)" for t in "${RUST_TARGETS[@]}"; do if grep -qx "$t" <<<"$installed"; then info "rust target present: $t" else info "adding rust target: $t" rustup target add "$t" fi done # --- 7. The environment the build needs ------------------------------------ cat <