name: Android APK (debug) # Triggers: # - push v*-tauri tag → build debug APK, upload artifact + attach to GitHub Release # - workflow_dispatch → build debug APK, upload artifact only (no release) # # Scope: full overlay/accessibility APK for ADB testing (v1 RECORD_AUDIO + overlay manifest merge). on: push: tags: - 'v*-tauri' workflow_dispatch: jobs: build-android-apk: permissions: contents: write runs-on: ubuntu-latest env: CI: true steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Setup Java 17 (Zulu) uses: actions/setup-java@v4 with: distribution: zulu java-version: '17' - name: Setup Android SDK uses: android-actions/setup-android@v3 with: packages: platform-tools - name: Install NDK and accept SDK licenses shell: bash run: | set -euo pipefail sdkmanager "ndk;26.1.10909125" ndk_dir="$ANDROID_HOME/ndk/26.1.10909125" if [ ! -d "$ndk_dir" ]; then echo "::error::NDK not found at $ndk_dir" exit 1 fi echo "ANDROID_NDK_HOME=$ndk_dir" >> "$GITHUB_ENV" echo "NDK_HOME=$ndk_dir" >> "$GITHUB_ENV" set +o pipefail yes | sdkmanager --licenses sdkmanager_exit=${PIPESTATUS[1]} set -o pipefail if [ "$sdkmanager_exit" -ne 0 ]; then echo "::error::sdkmanager --licenses failed with exit code $sdkmanager_exit" exit "$sdkmanager_exit" fi - uses: actions/setup-node@v4 with: node-version: 20 cache: npm cache-dependency-path: openless-all/app/package-lock.json - uses: dtolnay/rust-toolchain@stable with: targets: >- aarch64-linux-android, armv7-linux-androideabi, i686-linux-android, x86_64-linux-android - name: Cache Cargo uses: swatinem/rust-cache@v2 with: workspaces: openless-all/app/src-tauri -> target - uses: gradle/actions/setup-gradle@v4 - name: Install npm deps working-directory: openless-all/app run: npm ci - name: Build frontend working-directory: openless-all/app run: npm run build - name: Initialize Android project working-directory: openless-all/app run: npm run tauri -- android init --ci - name: Copy Android scaffolding (Kotlin + XML) working-directory: openless-all/app run: node scripts/copy-android-scaffolding.mjs - name: Merge APK v1 manifest permissions working-directory: openless-all/app run: node scripts/merge-android-v1-manifest.mjs - name: Merge overlay / accessibility manifest working-directory: openless-all/app run: node scripts/merge-android-overlay-manifest.mjs - name: Prime Gradle wrapper working-directory: openless-all/app shell: bash run: | set -euo pipefail for attempt in 1 2 3; do if src-tauri/gen/android/gradlew --project-dir src-tauri/gen/android --version --no-daemon; then exit 0 fi if [ "$attempt" -lt 3 ]; then sleep $([ "$attempt" -eq 1 ] && echo 15 || echo 30) fi done exit 1 - name: Build Android debug APK working-directory: openless-all/app run: npm run tauri:android:build - name: Free disk before artifact upload shell: bash working-directory: openless-all/app run: | set -euo pipefail rm -rf src-tauri/target rm -rf ~/.cargo/registry ~/.cargo/git ~/.gradle/caches df -h - name: Collect split debug APKs id: apk shell: bash working-directory: openless-all/app run: | set -euo pipefail if [[ "${{ github.ref }}" == refs/tags/v* ]] && [[ "${{ github.ref_name }}" == *-tauri ]]; then label="${{ github.ref_name }}" else label="run-${{ github.run_number }}" fi export OPENLESS_APK_LABEL="$label" python - <<'PY' import os import shutil import sys import zipfile from pathlib import Path expected = { "arm64-v8a": "arm64_v8a", "armeabi-v7a": "armeabi_v7a", "x86": "x86", "x86_64": "x86_64", } root = Path("src-tauri/gen/android") label = os.environ["OPENLESS_APK_LABEL"] out_dir = Path(os.environ["RUNNER_TEMP"]) / "openless-android-debug-split" out_dir.mkdir(parents=True, exist_ok=True) found = {} candidates = [ apk for apk in sorted(root.rglob("*.apk")) if "outputs" in apk.parts ] if not candidates: print("::error::No APK found under src-tauri/gen/android/**/outputs/") for apk in sorted(root.rglob("*.apk")): print(apk) sys.exit(1) for apk in candidates: with zipfile.ZipFile(apk) as archive: abis = sorted({ name.split("/")[1] for name in archive.namelist() if name.startswith("lib/") and len(name.split("/")) >= 3 }) if len(abis) != 1: print(f"::error::{apk} contains ABI directories {abis}; expected exactly one ABI per APK") sys.exit(1) abi = abis[0] if abi not in expected: print(f"::error::{apk} contains unexpected ABI {abi}") sys.exit(1) if abi in found: print(f"::error::Duplicate APKs for ABI {abi}: {found[abi]} and {apk}") sys.exit(1) dest = out_dir / f"OpenLess-android-debug-{abi}-{label}.apk" shutil.copy2(apk, dest) found[abi] = dest print(f"Collected {abi}: {apk} -> {dest}") missing = sorted(set(expected) - set(found)) if missing: print(f"::error::Missing split APKs for ABI(s): {', '.join(missing)}") sys.exit(1) with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: for abi, key in expected.items(): output.write(f"{key}_path={found[abi]}\n") output.write("release_files<