diff --git a/.forgejo/workflows/android.yml b/.forgejo/workflows/android.yml index a8eed46..328637a 100644 --- a/.forgejo/workflows/android.yml +++ b/.forgejo/workflows/android.yml @@ -42,7 +42,7 @@ jobs: # # The guard runs here so it covers the skip path too (§6.3). # - # NOTE THE COUPLING WITH ci.yml: when this lane builds, its last step dispatches + # NOTE THE COUPLING WITH ci.yml: when this lane builds, its server-image job dispatches # ci.yml so the image bakes in the APK just published. When it SKIPS, no dispatch # happens — and that is correct, because ci.yml's `gate` stands down only when the # push touched Android's files, which is the same condition that makes this build. @@ -72,9 +72,33 @@ jobs: sh packaging/guard-forward.sh android "$channel" echo "build=$(sh packaging/should-build.sh android "$channel")" >> $GITHUB_OUTPUT + # The core ships inside the APK (through android/ffi), so its checks have to gate + # the APK HERE, in this workflow's graph. desktop.yml runs them too, but a red run + # there cannot stop this lane publishing (rule 177, #5237). + # + # On ci-tauri, not ci-rust-android: these are host-target tests, and on Linux the + # core's native-tls is OpenSSL, whose headers only ci-tauri carries. The Android + # image has OpenSSL vendored for the Android targets alone (run 8663 failed at + # `openssl-sys` trying this there). Same Rust pin, same Cargo.lock. + rust: + name: Core and FFI clippy and tests + needs: [decide] + if: needs.decide.outputs.build == 'true' + runs-on: python-ci + container: + image: git.fabledsword.com/bvandeusen/ci-tauri:1.97 + steps: + - uses: actions/checkout@v6 + + - name: Clippy + run: cargo clippy --locked -p inkwell-core -p inkwell-ffi --all-targets -- -D warnings + + - name: Test + run: cargo test --locked -p inkwell-core -p inkwell-ffi + build: name: Kotlin + Rust (APK) - needs: [decide] + needs: [decide, rust] if: needs.decide.outputs.build == 'true' # runs-on is only a scheduling label (Label Model B). flutter-ci is the # proven-working label that can pull our container images. @@ -86,8 +110,6 @@ jobs: permissions: contents: write - # For the dispatch at the end: this lane starts the server image build. - actions: write defaults: run: @@ -169,16 +191,6 @@ jobs: - name: Build the native library and bindings run: ./gradlew generateUniffiBindings -PINKWELL_CARGO_PROFILE=${{ steps.build.outputs.profile }} - # The core ships inside this APK (through android/ffi), so its checks have to - # gate the APK HERE, in this workflow's graph. desktop.yml runs them too, but a - # red run there cannot stop this lane publishing (rule 177, #5237). Host - # target: the crates' own tests, not the cross-compiled .so. - - name: Rust clippy and tests (core and FFI) - working-directory: . - run: | - cargo clippy --locked -p inkwell-core -p inkwell-ffi --all-targets -- -D warnings - cargo test --locked -p inkwell-core -p inkwell-ffi - # The image's PINNED CLIs, not Gradle plugins. ci-rust-android carries both # (M12 step 3) precisely so this lane needs no second image, and going # through Gradle plugins would mean a second version of each tool resolved @@ -195,8 +207,8 @@ jobs: - name: Unit tests # Host-JVM tests only. Anything touching the core needs an Android # runtime to load the .so, so those are instrumented tests and belong on - # an emulator, not here — the Rust side is covered by the cargo step - # above. + # an emulator, not here — the Rust side is the `rust` job, which this + # one needs. # # DEBUG regardless of what is being packaged: AGP creates unit-test tasks # only for `testBuildType`, which is debug, so `testReleaseUnitTest` does @@ -290,21 +302,32 @@ jobs: path: ${{ steps.build.outputs.apk }} if-no-files-found: error + server-image: + name: Build the server image + needs: [decide, rust, build] + if: always() && needs.decide.outputs.build == 'true' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') + runs-on: flutter-ci + container: + # The image the dispatch has always run in, so its curl is a known quantity. + image: git.fabledsword.com/bvandeusen/ci-rust-android:1.97 + permissions: + actions: write + steps: # The server image bakes in whatever client the dev release holds, so it has # to be built AFTER this lane, not alongside it. `ci.yml` stands down on any # push that touches the Android app (its `gate` job) and waits to be called # from here — that is the other half of this. # - # `always()`: a FAILED Android build must still let the server image through. - # There is no new client in that case, so it bakes in the previous one, which - # is exactly right — the alternative is a broken Android lane silently - # blocking server delivery. + # `always()`: a FAILED Android build — or failed core checks, which skip the + # build job entirely — must still let the server image through. There is no + # new client in that case, so it bakes in the previous one, which is exactly + # right — the alternative is a broken Android lane silently blocking server + # delivery. Its own job for that reason: a step inside `build` never runs + # when `build` is skipped. # # Not `if: success()` and not skipped on tags either: every ref that builds an # image needs the call, or nothing builds one at all. - name: Build the server image now the client is published - if: always() && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') - working-directory: . env: GITHUB_TOKEN: ${{ github.token }} run: |