#!/usr/bin/env bash # Global device launcher — always runs on Android (Waydroid or adb device). # # nix run .#global # just run # # Pipeline: # 1. cargo apk build --target x86_64-linux-android --no-default-features # 2. adb install -r target/debug/apk/global-dist-ui.apk # 3. show device UI + Gleam dist_driver over erl_dist # # Env: # COOKIE Erlang cookie (default: global) # GLOBAL_ROOT checkout root # GLOBAL_VIDYA_SRC flake-locked vidya tree (set by nix run .#global) # ANDROID_NDK_HOME default: ~/.local/share/android-ndk-r29 # ANDROID_HOME default: ~/.local/share/android-sdk # GLOBAL_APK_SKIP_BUILD=1 skip cargo apk (use existing APK only) # GLOBAL_SKIP_SHOW_UI=1 skip waydroid show-full-ui set -euo pipefail # Accept legacy args for a while, then ignore them. case "${1:-}" in host | desktop | waydroid | local | device) shift ;; esac COOKIE="${COOKIE:-global}" PKG="uk.nandi.global.distui" ACTIVITY="${PKG}/android.app.NativeActivity" APK_REL="dist-ui/target/debug/apk/global-dist-ui.apk" need() { command -v "$1" >/dev/null || { echo "missing: $1" >&2; exit 1; }; } resolve_root() { if [[ -n "${GLOBAL_ROOT:-}" ]]; then echo "$GLOBAL_ROOT" return fi if git rev-parse --show-toplevel >/dev/null 2>&1; then git rev-parse --show-toplevel return fi if [[ -d ./dist_driver && -d ./dist-ui ]]; then pwd return fi echo "Run from the Global git checkout (needs dist_driver/ + dist-ui/)." >&2 exit 1 } ROOT="$(resolve_root)" cd "$ROOT" # Stage flake-input vidya for cargo path dep: dist-ui → ../.flake/vidya ensure_vidya() { local dest="$ROOT/.flake/vidya" mkdir -p "$ROOT/.flake" if [[ -n "${GLOBAL_VIDYA_SRC:-}" && -d "${GLOBAL_VIDYA_SRC}" ]]; then rm -rf "$dest" cp -aL "$GLOBAL_VIDYA_SRC" "$dest" chmod -R u+w "$dest" 2>/dev/null || true echo "[global] vidya ← flake input ($GLOBAL_VIDYA_SRC)" >&2 return 0 fi # Local overrides for non-nix cargo apk if [[ -d "$HOME/code/vidya" && -f "$HOME/code/vidya/Cargo.toml" ]]; then rm -rf "$dest" ln -sfn "$HOME/code/vidya" "$dest" echo "[global] vidya ← $HOME/code/vidya" >&2 return 0 fi if [[ -d "$ROOT/vidya" && -f "$ROOT/vidya/Cargo.toml" ]]; then rm -rf "$dest" ln -sfn "$ROOT/vidya" "$dest" echo "[global] vidya ← $ROOT/vidya (in-tree fallback)" >&2 return 0 fi echo "[global] vidya sources missing. Use nix run .#global, or clone nandi.uk/vidya." >&2 echo " override: nix run .#global --override-input vidya path:\$HOME/code/vidya" >&2 exit 1 } waydroid_ip() { waydroid status 2>/dev/null | awk -F': *' '/IP address/{gsub(/[[:space:]]/,"",$2); print $2; exit}' } ensure_waydroid() { need waydroid if ! waydroid status 2>/dev/null | grep -q 'Session:.*RUNNING'; then echo "[global] starting waydroid session…" waydroid session start >/dev/null 2>&1 & local i for i in $(seq 1 60); do waydroid status 2>/dev/null | grep -q 'Session:.*RUNNING' && break sleep 1 done fi waydroid status 2>/dev/null | grep -q 'Session:.*RUNNING' || { echo "[global] Waydroid session not RUNNING" >&2 waydroid status >&2 || true exit 1 } local ip i for i in $(seq 1 45); do ip="$(waydroid_ip)" if [[ -n "$ip" && "$ip" != "UNKNOWN" ]]; then echo "$ip" return 0 fi sleep 1 done echo "192.168.240.112" } ensure_adb() { local ip="$1" need adb mkdir -p "$HOME/.android" "$HOME/.local/share/waydroid/data/misc/adb" if [[ ! -f "$HOME/.android/adbkey" ]]; then adb keygen "$HOME/.android/adbkey" fi cp -f "$HOME/.android/adbkey.pub" "$HOME/.local/share/waydroid/data/misc/adb/adb_keys" 2>/dev/null || true waydroid adb connect >/dev/null 2>&1 || adb connect "${ip}:5555" >/dev/null 2>&1 || true local _ for _ in $(seq 1 40); do if adb devices 2>/dev/null | tr -d '\r' | grep -qE "${ip}:5555[[:space:]]+device"; then export ANDROID_SERIAL="${ip}:5555" return 0 fi adb connect "${ip}:5555" >/dev/null 2>&1 || true sleep 0.5 done echo "[global] ADB not ready for ${ip}:5555" >&2 adb devices -l >&2 || true exit 1 } setup_android_ndk() { export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$HOME/.local/share/android-ndk-r29}" export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" export ANDROID_HOME="${ANDROID_HOME:-$HOME/.local/share/android-sdk}" unset ANDROID_SDK_ROOT 2>/dev/null || true local prebuilt="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64" if [[ ! -d "$prebuilt" ]]; then echo "[global] ANDROID_NDK_HOME missing toolchain: $ANDROID_NDK_HOME" >&2 echo " Install NDK r29 (or set ANDROID_NDK_HOME)." >&2 exit 1 fi # Prefer rustup toolchain with android std over nixpkgs rustc (no android target). if command -v rustup >/dev/null 2>&1; then rustup target add x86_64-linux-android >/dev/null 2>&1 || true fi local rust_bin="" tc if command -v rustup >/dev/null 2>&1; then local c c="$(rustup which cargo 2>/dev/null || true)" if [[ -n "$c" && -x "$c" ]]; then local sr sr="$("$(dirname "$c")/rustc" --print sysroot 2>/dev/null || true)" if [[ -d "${sr}/lib/rustlib/x86_64-linux-android/lib" ]]; then rust_bin="$(dirname "$c")" fi fi fi if [[ -z "$rust_bin" && -d "${HOME}/.rustup/toolchains" ]]; then for tc in "${HOME}/.rustup/toolchains"/*; do [[ -x "${tc}/bin/cargo" ]] || continue if [[ -d "${tc}/lib/rustlib/x86_64-linux-android/lib" ]]; then rust_bin="${tc}/bin" break fi done fi if [[ -z "$rust_bin" ]]; then echo "[global] error: no rustc with x86_64-linux-android std (nixpkgs rustc is not enough)" >&2 echo " rustup target add x86_64-linux-android" >&2 exit 1 fi export PATH="${rust_bin}:${HOME}/.cargo/bin:${prebuilt}/bin:${ANDROID_HOME}/platform-tools:${PATH}" export CC_x86_64_linux_android="${CC_x86_64_linux_android:-x86_64-linux-android28-clang}" export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$CC_x86_64_linux_android" export AR_x86_64_linux_android="${AR_x86_64_linux_android:-llvm-ar}" echo "[global] cargo=$(command -v cargo) $(cargo --version 2>/dev/null || true)" >&2 echo "[global] rustc=$(command -v rustc) sysroot=$(rustc --print sysroot)" >&2 } build_apk() { ensure_vidya setup_android_ndk need cargo if ! cargo apk --help >/dev/null 2>&1; then echo "[global] cargo-apk not found; installing…" >&2 cargo install cargo-apk --locked fi echo "[global] cargo apk build --lib --target x86_64-linux-android --no-default-features" >&2 local before after apk apk="$ROOT/$APK_REL" before=0 [[ -f "$apk" ]] && before=$(stat -c %Y "$apk" 2>/dev/null || echo 0) ( cd "$ROOT/dist-ui" # --lib: only the cdylib (avoids cargo-apk "Bin is not compatible with Cdylib"). # All cargo/apk chatter → stderr; stdout is only the APK path. cargo apk build --lib --target x86_64-linux-android --no-default-features >&2 ) || { echo "[global] cargo apk build failed — not installing a stale APK" >&2 exit 1 } [[ -f "$apk" ]] || { echo "[global] APK missing after build: $apk" >&2 exit 1 } after=$(stat -c %Y "$apk" 2>/dev/null || echo 0) if [[ "$after" -le "$before" ]]; then echo "[global] error: APK mtime unchanged after build — refusing stale install" >&2 exit 1 fi echo "[global] APK updated: $apk" >&2 printf '%s\n' "$apk" } ensure_show_full_ui() { [[ "${GLOBAL_SKIP_SHOW_UI:-0}" == "1" ]] && return 0 export DISPLAY="${DISPLAY:-:0}" export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" if pgrep -a waydroid 2>/dev/null | grep -q show-full-ui; then echo "[global] show-full-ui already running" return 0 fi echo "[global] waydroid show-full-ui" setsid waydroid show-full-ui /tmp/waydroid-full-ui.log 2>&1 & sleep 2 } stop_prior_driver() { local p for p in $(pgrep -f 'dist_driver@@main' 2>/dev/null || true); do kill "$p" 2>/dev/null || true done sleep 0.3 } echo "[global] device pipeline (APK → adb install → UI → Gleam)" need adb need gleam need erl need waydroid ip="$(ensure_waydroid)" ensure_adb "$ip" if [[ "${GLOBAL_APK_SKIP_BUILD:-0}" == "1" ]]; then apk="$ROOT/$APK_REL" [[ -f "$apk" ]] || { echo "[global] GLOBAL_APK_SKIP_BUILD set but no APK at $apk" >&2 exit 1 } else apk="$(build_apk)" fi echo "[global] adb install -r $apk" adb install -r "$apk" ensure_show_full_ui # Single launch only — a second start kills winit ("EventLoop can't be recreated"). adb shell am force-stop "$PKG" 2>/dev/null || true sleep 0.4 if ! adb shell am start -n "$ACTIVITY" >/dev/null 2>&1; then waydroid app launch "$PKG" >/dev/null 2>&1 || true fi echo "[global] launched ${ACTIVITY} on ${ip}" stop_prior_driver ui="ui@${ip}" echo "[global] Gleam → ${ui} (cookie=${COOKIE})" cd "$ROOT/dist_driver" export ERL_FLAGS="-name global@127.0.0.1 -setcookie ${COOKIE}" gleam build erlc -o build/dev/erlang/dist_driver/ebin src/dist_driver_ffi.erl 2>/dev/null || true exec gleam run -- "$ui"