diff --git a/.github/workflows/ci-android.yml b/.github/workflows/ci-android.yml deleted file mode 100644 index c228bd9d1..000000000 --- a/.github/workflows/ci-android.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Compile gate for the Android host module: regenerates the UniFFI Kotlin -# bindings and compiles the shell against them whenever the module or the -# native surface changes. No cross-compile, no publishing. -name: ci-android - -on: - pull_request: - paths: - - "android/**" - - "rust/crates/truapi-server/**" - - "rust/crates/truapi-platform/**" - - "rust/crates/uniffi-bindgen-cli/**" - - ".github/workflows/ci-android.yml" - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - # codegen.sh formats its generated Rust with `cargo +nightly fmt`. - - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly - with: - toolchain: nightly - components: rustfmt - - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable - - uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2.8.1 - - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 - with: - distribution: temurin - java-version: "17" - - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - with: - gradle-version: "8.9" - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "22" - # truapi-server declares its generated modules unconditionally and they are - # gitignored, so the crate does not compile until codegen has run. - - name: Generate the Rust codegen output - run: | - npm ci --ignore-scripts - TRUAPI_SKIP_PACKAGE_BUILD=1 ./scripts/codegen.sh - - name: Generate UniFFI Kotlin bindings - run: make uniffi-kotlin - - name: Compile the shell against the bindings - run: gradle :truapi-host:assembleRelease diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9b38ad02..bd7d6fd86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -192,44 +192,56 @@ jobs: - name: Generate TrUAPIProvider bindings run: make provider-swift - ios-changes: - name: iOS change filter + changes: + name: Change filter runs-on: ubuntu-latest outputs: - ios: ${{ steps.filter.outputs.ios }} + sdk_swift: ${{ steps.filter.outputs.sdk_swift }} + sdk_kotlin: ${{ steps.filter.outputs.sdk_kotlin }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 2 persist-credentials: false - # Actions has no per-job paths filter, so compute it once here and gate - # ios-swift on the output. On pull_request the checkout is the merge - # commit, so HEAD^1 is the base. Other events always run the gate. + # Actions has no per-job paths filter, so compute every gate once here and + # have each gated job read an output. On pull_request the checkout is the + # merge commit, so HEAD^1 is the base. Other events always run every gate. # - # The bindings are generated, not committed, so a UniFFI surface change - # leaves no ios/ diff to key on. The list therefore names every crate the - # bindings are generated from, plus js/container, which is compiled into - # the TrUAPIHost target as a resource. - - name: Detect iOS-relevant changes + # Neither binding set is committed, so a UniFFI surface change leaves no + # ios/ or android/ diff to key on. Each list therefore names every crate + # its bindings are generated from. js/container is in the Swift list + # because it is compiled into the TrUAPIHost target as a resource. Both + # lists name this workflow, so editing a filter exercises what it gates. + - name: Detect which areas changed id: filter run: | set -euo pipefail + if [ "${{ github.event_name }}" != "pull_request" ]; then - echo "ios=true" >> "$GITHUB_OUTPUT" + { + echo "sdk_swift=true" + echo "sdk_kotlin=true" + } >> "$GITHUB_OUTPUT" exit 0 fi - if git diff --name-only HEAD^1 HEAD \ - | grep -qE '^(ios/|Package\.swift$|Makefile$|js/container/|rust/crates/truapi/|rust/crates/truapi-platform/|rust/crates/truapi-server/|rust/crates/truapi-provider/)'; then - echo "ios=true" >> "$GITHUB_OUTPUT" - else - echo "ios=false" >> "$GITHUB_OUTPUT" - fi + + changed="$(git diff --name-only HEAD^1 HEAD)" + gate() { + if grep -qE "$2" <<<"$changed"; then + echo "$1=true" >> "$GITHUB_OUTPUT" + else + echo "$1=false" >> "$GITHUB_OUTPUT" + fi + } + + gate sdk_swift '^(ios/|Package\.swift$|Makefile$|js/container/|rust/crates/truapi/|rust/crates/truapi-platform/|rust/crates/truapi-server/|rust/crates/truapi-provider/|\.github/workflows/ci\.yml$)' + gate sdk_kotlin '^(android/|Makefile$|rust/crates/truapi/|rust/crates/truapi-platform/|rust/crates/truapi-server/|rust/crates/uniffi-bindgen-cli/|\.github/workflows/ci\.yml$)' ios-swift: name: iOS package (swift compile) - needs: [ios-changes, codegen] - if: needs.ios-changes.outputs.ios == 'true' + needs: [changes, codegen] + if: needs.changes.outputs.sdk_swift == 'true' runs-on: macos-15 timeout-minutes: 30 env: @@ -324,6 +336,46 @@ jobs: CODE_SIGNING_ALLOWED=NO swift build --target TrUAPIProvider + # Compile gate for the Android host module: regenerates the UniFFI Kotlin + # bindings and compiles the shell against them. No cross-compile, no + # publishing. + android-bindings: + name: Android host (kotlin compile) + needs: changes + if: needs.changes.outputs.sdk_kotlin == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + # codegen.sh formats its generated Rust with `cargo +nightly fmt`. + - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly + with: + toolchain: nightly + components: rustfmt + - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable + - uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2.8.1 + - uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0 + with: + distribution: temurin + java-version: "17" + - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + with: + gradle-version: "8.9" + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "22" + # truapi-server declares its generated modules unconditionally and they are + # gitignored, so the crate does not compile until codegen has run. + - name: Generate the Rust codegen output + run: | + npm ci --ignore-scripts + TRUAPI_SKIP_PACKAGE_BUILD=1 ./scripts/codegen.sh + - name: Generate UniFFI Kotlin bindings + run: make uniffi-kotlin + - name: Compile the shell against the bindings + run: gradle :truapi-host:assembleRelease + ts-client: name: "@parity/truapi" runs-on: ubuntu-latest @@ -591,8 +643,9 @@ jobs: licenses, codegen, ios-bindings, - ios-changes, + changes, ios-swift, + android-bindings, ts-client, ts-host, ts-debugger, @@ -609,10 +662,11 @@ jobs: "${{ needs.licenses.result }}" "${{ needs.codegen.result }}" "${{ needs.ios-bindings.result }}" - # ios-changes is listed because a failure there skips ios-swift, - # and skipped counts as a pass below. - "${{ needs.ios-changes.result }}" + # changes is listed because a failure there skips every gated + # job, and skipped counts as a pass below. + "${{ needs.changes.result }}" "${{ needs.ios-swift.result }}" + "${{ needs.android-bindings.result }}" "${{ needs.ts-client.result }}" "${{ needs.ts-host.result }}" "${{ needs.ts-debugger.result }}" diff --git a/CLAUDE.md b/CLAUDE.md index b1e193510..876792efe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -104,14 +104,17 @@ scripts/truapi-host-installer.sh still has a binding representation, and the `ios-swift` job generates the package's Swift sources and container resource and then compiles the package and its test target, which is what catches a hand-written conformer that - missed a new protocol requirement. `ios-swift` is path-filtered, and the - filter has to name every crate the bindings are generated from, since a - protocol change no longer leaves an `ios/` diff to key on. On the Kotlin side - the `ci-android` job compiles - `TrUAPIHost.kt` against freshly generated bindings on pull requests touching - `android/` or the native crates, which catches the same class of drift; - `make android-check` does it locally. The embedding apps are compiled by - neither. + missed a new protocol requirement. On the Kotlin side the `android-bindings` + job compiles `TrUAPIHost.kt` against freshly generated bindings, which catches + the same class of drift; `make android-check` does it locally. The embedding + apps are compiled by neither. +- Both compile gates are path-filtered from one place. The `changes` job in + `ci.yml` computes `sdk_swift` and `sdk_kotlin`, and each gated job reads the + output. Because neither binding set is committed, a filter has to name every + crate its bindings are generated from, since a protocol change leaves no + `ios/` or `android/` diff to key on. Every job in `ci.yml` is aggregated by + `ci-status`, which is the check worth requiring: a job skipped by its filter + counts as a pass, so a gate cannot stall a PR it does not apply to. Hosts implement `HostBridge`, whose protocol extension defaults the optional callbacks; `TrUAPIHostRuntime` and each product execution retain one. To publish, include `@parity/ios-host ` in the `release:` PR title.