diff --git a/.tangled/workflows/android-apk.yml b/.tangled/workflows/android-apk.yml index 2fc0cb3..8f3eb1b 100644 --- a/.tangled/workflows/android-apk.yml +++ b/.tangled/workflows/android-apk.yml @@ -2,8 +2,8 @@ # Artifact path: dist-ui/target/debug/apk/global-dist-ui.apk # Local equivalent: ./scripts/ci-build-apk.sh # -# Hosted spindle may not ship Android NDK; steps download NDK when -# GLOBAL_CI_INSTALL_NDK=1. Local verification uses a preinstalled NDK. +# Use microvm+nixos (not nixery): nixery is non-FHS and cannot exec Google +# aapt/NDK ELF prebuilts (ENOENT). steam-run provides an FHS user env. when: - event: ["push", "manual"] @@ -11,38 +11,41 @@ when: - event: ["pull_request"] branch: ["main"] -engine: "nixery" +engine: microvm +image: nixos clone: skip: false depth: 1 submodules: false +# microvm/nixos: flat dependency list → mkShellNoCC for each step dependencies: - nixpkgs: - - bash - - coreutils - - findutils - - curl - - gnutar - - gzip - - unzip - - gcc - - pkg-config - - openssl - - rustup - - file - - which - - git - - jdk - - zlib + - bash + - coreutils + - findutils + - curl + - gnutar + - gzip + - unzip + - gcc + - pkg-config + - openssl + - rustup + - file + - which + - git + - jdk + - zlib + - steam-run + - patchelf environment: CARGO_TERM_COLOR: "always" GLOBAL_CI_INSTALL_NDK: "1" - # NDK + minimal SDK under workspace (ephemeral runner FS). - ANDROID_NDK_HOME: "/tangled/workspace/.android-ndk-r29" - ANDROID_HOME: "/tangled/workspace/.android-sdk" + # Resolved to absolute paths in the build step (repo CWD). + ANDROID_NDK_HOME: ".android-ndk-r29" + ANDROID_HOME: ".android-sdk" steps: - name: "Rust toolchain + android target" @@ -52,6 +55,8 @@ steps: rustup target add x86_64-linux-android cargo --version rustc --version + command -v steam-run + steam-run true - name: "Install cargo-apk" command: | @@ -63,9 +68,19 @@ steps: command: | set -euo pipefail export GLOBAL_ROOT="$PWD" - export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$PWD/.android-ndk-r29}" export GLOBAL_CI_INSTALL_NDK="${GLOBAL_CI_INSTALL_NDK:-1}" - # Prefer in-tree vidya if present (committed fallback). + # Resolve relative SDK/NDK paths against the repo root. + case "${ANDROID_NDK_HOME:-}" in + /*) ;; + *) export ANDROID_NDK_HOME="$PWD/${ANDROID_NDK_HOME:-.android-ndk-r29}" ;; + esac + case "${ANDROID_HOME:-}" in + /*) ;; + *) export ANDROID_HOME="$PWD/${ANDROID_HOME:-.android-sdk}" ;; + esac + export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" + unset ANDROID_SDK_ROOT || true + mkdir -p "$HOME/.android" if [[ -d "$PWD/vidya" ]]; then export GLOBAL_VIDYA_SRC="" fi @@ -75,5 +90,3 @@ steps: test -s "$APK" file "$APK" ls -la "$APK" - -# Spindle association: spindle.tangled.sh (set 2026-07-28) diff --git a/scripts/ci-build-apk.sh b/scripts/ci-build-apk.sh index 01bc0e4..75b693c 100755 --- a/scripts/ci-build-apk.sh +++ b/scripts/ci-build-apk.sh @@ -71,37 +71,54 @@ ensure_vidya() { exit 1 } -ensure_ndk() { +ndk_looks_complete() { local prebuilt="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64" - if [[ -d "$prebuilt" ]]; then + [[ -d "$prebuilt" ]] \ + && [[ -f "$ANDROID_NDK_HOME/source.properties" ]] \ + && [[ -f "$ANDROID_NDK_HOME/build/core/platforms.mk" ]] +} + +ensure_ndk() { + if ndk_looks_complete; then log "NDK ok: $ANDROID_NDK_HOME" return 0 fi if [[ "${GLOBAL_CI_INSTALL_NDK:-0}" != "1" ]]; then - log "ANDROID_NDK_HOME missing toolchain: $ANDROID_NDK_HOME" + log "ANDROID_NDK_HOME incomplete: $ANDROID_NDK_HOME" + log "Need toolchains/llvm/prebuilt/linux-x86_64, source.properties, build/core/platforms.mk" log "Set ANDROID_NDK_HOME or GLOBAL_CI_INSTALL_NDK=1 to download r29." exit 1 fi log "Downloading Android NDK r29 (side-by-side)…" mkdir -p "$(dirname "$ANDROID_NDK_HOME")" - local zip="/tmp/android-ndk-r29-linux.zip" - # Google NDK r29 zip (public) + # Prefer workspace-local zip so small /tmp does not truncate the NDK (~750MB). + local zip_dir + zip_dir="$(dirname "$ANDROID_NDK_HOME")" + local zip="$zip_dir/android-ndk-r29-linux.zip" if [[ ! -f "$zip" ]]; then - curl -fsSL -o "$zip" \ - "https://dl.google.com/android/repository/android-ndk-r29-linux.zip" \ - || curl -fsSL -o "$zip" \ - "https://dl.google.com/android/repository/android-ndk-r27c-linux.zip" + curl -fsSL -o "$zip.partial" \ + "https://dl.google.com/android/repository/android-ndk-r29-linux.zip" + mv "$zip.partial" "$zip" fi local extract_dir - extract_dir="$(mktemp -d)" + extract_dir="$(mktemp -d -p "$zip_dir")" unzip -q "$zip" -d "$extract_dir" - # Prefer bash glob over find(1) — Spindle nixery images may omit findutils. + # Prefer bash glob over find(1) — Spindle images may omit findutils. local found found="$(first_dir "$extract_dir" 'android-ndk-*' || true)" - [[ -n "${found:-}" ]] || { log "NDK extract failed under $extract_dir"; exit 1; } + [[ -n "${found:-}" ]] || { + log "NDK extract failed under $extract_dir" + ls -la "$extract_dir" >&2 || true + exit 1 + } rm -rf "$ANDROID_NDK_HOME" mv "$found" "$ANDROID_NDK_HOME" rm -rf "$extract_dir" + if ! ndk_looks_complete; then + log "NDK install incomplete at $ANDROID_NDK_HOME" + ls -la "$ANDROID_NDK_HOME" >&2 || true + exit 1 + fi log "NDK installed: $ANDROID_NDK_HOME" } @@ -215,14 +232,49 @@ setup_rust_android() { fi } +preflight_cargo_apk_env() { + mkdir -p "${HOME}/.android" + export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME" + unset ANDROID_SDK_ROOT || true + log "preflight ANDROID_HOME=$ANDROID_HOME" + log "preflight ANDROID_NDK_HOME=$ANDROID_NDK_HOME" + log "preflight HOME=$HOME" + [[ -d "$ANDROID_HOME/build-tools" ]] || { log "missing build-tools under $ANDROID_HOME"; exit 1; } + [[ -d "$ANDROID_HOME/platforms" ]] || { log "missing platforms under $ANDROID_HOME"; exit 1; } + [[ -f "$ANDROID_NDK_HOME/build/core/platforms.mk" ]] || { + log "missing platforms.mk (cargo-apk Ndk::from_env needs it)" + exit 1 + } + # Prove aapt is executable on this host (catches non-FHS / wrong libc early). + local aapt="" + local d + shopt -s nullglob + for d in "$ANDROID_HOME"/build-tools/*/; do + if [[ -x "${d}aapt" ]]; then + aapt="${d}aapt" + break + fi + done + shopt -u nullglob + if [[ -n "$aapt" ]]; then + if "$aapt" version >/dev/null 2>&1; then + log "aapt ok: $aapt" + else + log "WARNING: aapt not executable ($aapt) — host may lack glibc loader (use alpine/gcompat or FHS)" + file "$aapt" >&2 || true + ldd "$aapt" >&2 || true + fi + fi +} + build() { ensure_vidya ensure_ndk ensure_sdk setup_rust_android + preflight_cargo_apk_env log "cargo apk build --target x86_64-linux-android --no-default-features" - # Spindle nixery is non-FHS; Google aapt/NDK prebuilts need a dynamic linker. - # Prefer steam-run when available (workflow dep); fall back to bare cargo. + # Prefer steam-run when available (FHS wrap for Google prebuilts on Nix). local runner=() if command -v steam-run >/dev/null 2>&1; then runner=(steam-run) @@ -230,6 +282,7 @@ build() { fi ( cd "$ROOT/dist-ui" + export RUST_BACKTRACE="${RUST_BACKTRACE:-1}" # cargo-apk may panic after packaging; APK is often still written. "${runner[@]}" cargo apk build --target x86_64-linux-android --no-default-features || true ) diff --git a/test/ci_apk_workflow_test.sh b/test/ci_apk_workflow_test.sh index cf828d0..50c47f1 100755 --- a/test/ci_apk_workflow_test.sh +++ b/test/ci_apk_workflow_test.sh @@ -26,11 +26,13 @@ check "workflow exists" test -f "$WF" check "ci-build-apk.sh exists" test -x "$SCRIPT" check "workflow has when:" grep -q '^when:' "$WF" check "workflow has engine:" grep -q 'engine:' "$WF" +check "workflow uses microvm (FHS-capable)" grep -q 'engine: microvm' "$WF" check "workflow has push trigger" grep -q 'push' "$WF" check "workflow has manual trigger" grep -q 'manual' "$WF" check "workflow targets main" grep -q 'main' "$WF" check "workflow invokes ci-build-apk" grep -q 'ci-build-apk' "$WF" check "workflow mentions APK path" grep -q 'global-dist-ui.apk' "$WF" +check "workflow includes steam-run" grep -q 'steam-run' "$WF" check "script emits APK_REL path" grep -q 'dist-ui/target/debug/apk/global-dist-ui.apk' "$SCRIPT" check "script checks ZIP magic" grep -q '504b' "$SCRIPT" check "script tolerates cargo-apk panic" grep -q 'cargo apk build' "$SCRIPT"