Android #8
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Android | |
| on: | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "apps/**" | |
| - "packages/**" | |
| - "package.json" | |
| - "package-lock.json" | |
| - ".github/workflows/android.yml" | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: android-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # Keep these in sync with docs/android-port-spike.md and the local setup. | |
| ANDROID_PLATFORM: "platforms;android-34" | |
| ANDROID_BUILD_TOOLS: "build-tools;34.0.0" | |
| # NDK r27 LTS — Tauri v2's supported line. Bump deliberately. | |
| ANDROID_NDK_VERSION: "27.3.13750724" | |
| jobs: | |
| build: | |
| name: Build Android APK (debug) | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Set up JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "21" | |
| - name: Set up Android SDK | |
| # Third-party action — pin to a commit SHA to match the repo's policy | |
| # for non-official actions before relying on this in protected branches. | |
| uses: android-actions/setup-android@v3 | |
| - name: Install Android NDK, platform, and build-tools | |
| run: | | |
| sdkmanager --install \ | |
| "platform-tools" \ | |
| "$ANDROID_PLATFORM" \ | |
| "$ANDROID_BUILD_TOOLS" \ | |
| "ndk;$ANDROID_NDK_VERSION" | |
| echo "NDK_HOME=$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" >> "$GITHUB_ENV" | |
| - name: Install Rust stable with Android targets | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| toolchain: stable | |
| targets: >- | |
| aarch64-linux-android, | |
| armv7-linux-androideabi, | |
| i686-linux-android, | |
| x86_64-linux-android | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| workspaces: apps/geolibre-desktop/src-tauri -> target | |
| - name: Install frontend dependencies | |
| run: npm ci | |
| - name: Generate Android project | |
| # gen/android is not committed; regenerate it on the clean runner. The | |
| # Tauri CLI is resolved through the geolibre-desktop workspace. | |
| working-directory: apps/geolibre-desktop | |
| run: npx tauri android init | |
| - name: Apply GeoLibre launcher icons | |
| # `tauri android init` writes default Tauri icons; overwrite the generated | |
| # mipmaps with the GeoLibre launcher icons checked in under src-tauri/icons. | |
| working-directory: apps/geolibre-desktop | |
| run: cp -r src-tauri/icons/android/. src-tauri/gen/android/app/src/main/res/ | |
| - name: Build release APKs (per ABI) | |
| # Release (not --debug): the size-optimized + stripped Cargo profile keeps | |
| # each .so small. --split-per-abi emits one ~40 MB APK per architecture | |
| # instead of a single ~150 MB universal APK bundling all four ABIs. | |
| # Release APKs are unsigned by default; the next step signs them. | |
| working-directory: apps/geolibre-desktop | |
| run: npx tauri android build --apk --split-per-abi | |
| env: | |
| VITE_GEE_OAUTH_CLIENT_ID: ${{ secrets.VITE_GEE_OAUTH_CLIENT_ID }} | |
| - name: Sign APKs | |
| # With release-keystore secrets set, the APKs are signed for distribution. | |
| # Without them, they're signed with a throwaway debug keystore so the CI | |
| # artifacts are still installable for testing (do NOT publish those). | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| build_tools="$(ls -d "$ANDROID_HOME"/build-tools/* | sort -V | tail -1)" | |
| if [ -n "${KEYSTORE_BASE64:-}" ]; then | |
| echo "Signing with the release keystore from secrets." | |
| echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.jks" | |
| keystore="$RUNNER_TEMP/release.jks" | |
| store_pass="$KEYSTORE_PASSWORD" | |
| key_pass="${KEY_PASSWORD:-$KEYSTORE_PASSWORD}" | |
| alias="$KEY_ALIAS" | |
| else | |
| echo "::warning::No ANDROID_KEYSTORE_BASE64 secret set — signing with a throwaway debug keystore. Installable for testing only, NOT for distribution." | |
| keystore="$RUNNER_TEMP/debug.jks" | |
| "$JAVA_HOME/bin/keytool" -genkeypair -v -keystore "$keystore" \ | |
| -storepass android -keypass android -alias androiddebugkey \ | |
| -keyalg RSA -keysize 2048 -validity 10000 \ | |
| -dname "CN=Android Debug,O=Android,C=US" | |
| store_pass=android; key_pass=android; alias=androiddebugkey | |
| fi | |
| out="$RUNNER_TEMP/apks"; mkdir -p "$out" | |
| found=0 | |
| while IFS= read -r unsigned; do | |
| found=1 | |
| # e.g. app-arm64-v8a-release-unsigned.apk -> geolibre-arm64-v8a.apk | |
| abi="$(basename "$unsigned" | sed -E 's/^app-(.*)-release-unsigned\.apk$/\1/')" | |
| aligned="$RUNNER_TEMP/aligned-$abi.apk" | |
| signed="$out/geolibre-android-$abi.apk" | |
| "$build_tools/zipalign" -p -f 4 "$unsigned" "$aligned" | |
| "$build_tools/apksigner" sign --ks "$keystore" \ | |
| --ks-pass "pass:$store_pass" --key-pass "pass:$key_pass" \ | |
| --ks-key-alias "$alias" --out "$signed" "$aligned" | |
| "$build_tools/apksigner" verify "$signed" | |
| echo "signed $signed ($(du -h "$signed" | cut -f1))" | |
| done < <(find apps/geolibre-desktop/src-tauri/gen/android \ | |
| -name '*release-unsigned.apk') | |
| if [ "$found" -eq 0 ]; then | |
| echo "::error::No release-unsigned APKs found"; exit 1 | |
| fi | |
| - name: Upload signed APKs | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: geolibre-android-release-apks | |
| path: ${{ runner.temp }}/apks/*.apk | |
| if-no-files-found: error | |
| retention-days: 14 |