The Rust core was already portable (caller-provided db paths, rustls rather than openssl) and the shell already had the mobile entry point, so this is mostly toolchain, provisioning, and platform-gating. Toolchain: - scripts/android-setup.sh installs what Android Studio doesn't: SDK cmdline-tools, a pinned stable NDK, and the Rust Android targets. The NDK is pinned rather than "latest" because sdkmanager's newest is often a beta (30.0.15729638 is r30-beta2) -- not something to compile SQLite with. Prefers Android Studio's bundled JDK 21, since Gradle rejects newer JDKs. - Track src-tauri/gen/android (excluding build output): AndroidManifest.xml is hand-edited and Android permissions have no tauri.conf.json equivalent. - vite: serve on TAURI_DEV_HOST so a device/emulator can reach the dev server. Card data: downloading streams ~740MB to build a ~73MB db, which is rough on a phone. Add import_reference_db to adopt a desktop-built file instead, validated by db::inspect_reference_db first so picking the wrong file is a clean error rather than a broken install. Installs via copy-then-rename so an interrupted copy can't leave a partial db. Ingest hardening (helps desktop too): - Give ureq connect/read timeouts. Cancellation is only polled per parsed record, so a stalled socket previously hung forever with no way to cancel. - Drop the remove_file before rename; POSIX rename already replaces atomically, and the delete-first window could leave no reference.db at all if the process was killed -- much likelier on Android. UI: hide Open/New Library on mobile (scoped storage has no arbitrary-path equivalent, and SQLite can't open a content:// URI), reported via a cfg!(mobile) flag on Status rather than user-agent sniffing. Use 100dvh so the soft keyboard doesn't break the shell. Verified: 70/70 core tests, desktop build clean, and the aarch64 APK builds (bundled SQLite C and ring both cross-compile under NDK 28). Not yet run on a device.
197 lines
7.6 KiB
Bash
Executable File
197 lines
7.6 KiB
Bash
Executable File
#!/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 <<EOF
|
|
|
|
$(info "Setup complete.")
|
|
|
|
Add these to your shell profile (or export them before building):
|
|
|
|
export ANDROID_HOME="$ANDROID_HOME"
|
|
export NDK_HOME="$NDK_DIR"
|
|
export JAVA_HOME="$JAVA_HOME" # Gradle rejects the newer system default
|
|
|
|
Then, from the project root:
|
|
|
|
cargo tauri android init # once — scaffolds src-tauri/gen/android
|
|
cargo tauri android dev # run on an emulator or adb-connected device
|
|
cargo tauri android build # build APK/AAB
|
|
|
|
Devices/emulators: $ANDROID_HOME/platform-tools/adb devices
|
|
EOF
|