diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 177bd13..55a56a2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,18 +6,28 @@ on: pull_request: branches: [ master ] +# Both jobs target a self-hosted runner, by capability rather than by hostname. +# See tool/setup_ci_runner.sh for how that runner is provisioned and why it is +# not the Gitea host: Flutter cannot build an Android APK on a Linux arm64 host +# (flutter/flutter#189724), and the Gitea box is aarch64. The runner is +# registered at android-stuff org scope so any repo in the org can use it. +# +# The runner is host-mode, so Flutter, the JDK and the Android SDK/NDK are the +# machine's own — the same toolchain that produced the verified release, and no +# multi-hundred-MB download per run. Nothing here installs a toolchain; the +# Toolchain step fails loudly if one is missing. + jobs: analyze: name: Analyze & Test - runs-on: ubuntu-latest + runs-on: flutter-android steps: - uses: actions/checkout@v4 - - uses: subosito/flutter-action@v2 - with: - flutter-version: '3.47.1' - channel: 'stable' - cache: true + - name: Toolchain + run: | + flutter --version + echo "ANDROID_HOME=${ANDROID_HOME:?not set on this runner}" - name: Get dependencies run: flutter pub get @@ -32,21 +42,10 @@ jobs: release-apk: name: Release APK - runs-on: ubuntu-latest + runs-on: flutter-android steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - uses: subosito/flutter-action@v2 - with: - flutter-version: '3.47.1' - channel: 'stable' - cache: true - - name: Get dependencies run: flutter pub get diff --git a/docs/README.md b/docs/README.md index 230f1a7..b7218e9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -183,6 +183,41 @@ Dart uses `Completer` to await native completion. The MethodChanne | `isTestRunning()` | `isTestRunning` | Check if test is in progress | | `setTempDir(String)` | `setTempDir` | Point `mkstemp()` at the app cache dir | +## CI + +Both jobs run on a **self-hosted runner registered at `android-stuff` org scope**, +targeted by the capability label `flutter-android` rather than by hostname. +`tool/setup_ci_runner.sh` provisions it. + +It does not run on the Gitea host. Gitea lives on an aarch64 box, and Flutter +cannot build an Android APK on a Linux arm64 host at all — the SDK ships no +arm64-host `gen_snapshot` ([flutter#189724](https://github.com/flutter/flutter/issues/189724), +[#75864](https://github.com/flutter/flutter/issues/75864), open as of July 2026). +An x86_64 runner is a requirement, not a preference. + +The runner is **host mode**: jobs use the machine's own Flutter, JDK and Android +SDK/NDK. That means CI builds with the same toolchain that produced the verified +release, and no NDK download per run. The trade is isolation — workflows run as +that user on that machine — so the runner is only for repos whose workflows are +trusted. + +Two jobs: + +| Job | What it protects | +|---|---| +| `analyze` | `flutter analyze`, then `flutter test` — which includes `architecture_test.dart`, failing the build if a layer boundary is crossed | +| `release-apk` | builds the **minified** APK and runs `tool/check_jni_keep_rules.sh` | + +The second exists because of a specific escape. A release APK that aborted on +every test shipped once, while every on-device check passed — because those +checks all used the debug build, which does not minify. Building the release APK +is necessary but not sufficient: a renamed JNI callback still produces a +*successful build* and a broken app. So the guard reads R8's `mapping.txt` for +the callback classes and methods, and the DEX string pool for the native entry +points. It is verified to fail: delete the keep rules from +`android/app/proguard-rules.pro`, rebuild, and it reports `MainThreadCallback`, +`onOutput`, `onError` and `onComplete` renamed. + ## Building Prerequisites: