ci(tauri): add CI guardrails for the mobile discovery FFI contract - #337
Merged
Conversation
The mobile mDNS/DNS-SD commands cross a Rust ↔ Swift/Kotlin boundary that
is matched by string at runtime: run_mobile_plugin("name") dispatches to
`@objc public func name` (Swift) and `@Command fun name` (Kotlin). Each
side compiles independently, so a rename or dropped handler is a silent
contract drift that only surfaces as an `unknown method` error during
manual on-device testing — the exact risk PR #330 introduced.
mobile_mdns.rs is #[cfg(mobile)] and not compiled on the host, so the
test scans the three source files as text and asserts the command-string
sets are identical in every direction (fails on a Rust string with no
native handler AND on an orphaned native handler). It runs in the
existing host-side `cargo test` for the tauri crate — no new toolchain —
in milliseconds, and would have caught the #330 rename with certainty.
Refs #333
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Kotlin discovery plugin was never compiled in CI (only the Rust half of
the FFI boundary was checked), so two latent build breaks shipped:
- `drainResolveQueue()` destructured a nullable `Pair` whose null-check and
assignment happened inside a `synchronized {}` lambda. Kotlin 1.9 (the
version `tauri android init` generates) rejects the smart-cast at the
destructuring site. Restructure so the `synchronized` block yields a
non-null value, preserving the exact locking semantics.
- `build.gradle.kts` referenced `consumer-rules.pro` via
`consumerProguardFiles(...)`, but the file was missing, breaking any Gradle
build of the module. Add it with keep rules for the reflectively-invoked
`@Command` handlers.
Both are prerequisites for the new Android compile job (#333) to pass.
Refs #333
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add two build-only jobs that compile the Swift (iOS) and Kotlin (Android) plugin sources against the Tauri mobile API this crate's Cargo.lock resolves, so a renamed/removed `@objc`/`@Command` handler or a signature drift on the native side of the string-matched FFI boundary fails CI instead of only surfacing during manual on-device testing. Neither job needs a device, an emulator/simulator, the NDK, or a Rust mobile cross-compile: the Tauri mobile API ships inside the `tauri` crate (`mobile/ios-api`, `mobile/android`), so the scripts `cargo fetch` the crate, read its location from `cargo metadata`, and compile the native sources against it — iOS via `swift build` (iphonesimulator SDK), Android via a standalone Gradle harness running `:tauri-plugin-iroh-http:assembleDebug`. The iOS step is folded into the existing `rust-check-macos` job to avoid a second billed macOS runner; Android runs on ubuntu. Both are gated to `pull_request` / tag builds like the other platform jobs, so a native contract break blocks merge. Refs #333 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a coverage matrix to build-and-test.md distinguishing what the new FFI-contract checks verify (string-parity of command names across Rust/Swift/Kotlin, plus iOS Swift and Android Kotlin compilation — all deterministic, no device) from what still requires manual on-device verification (real multicast mDNS advertise/browse/self-echo/resolution over a LAN, tracked in #334). Document how to run each check locally. Refs #333 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Jul 10, 2026
Closed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the CI gap on the mobile discovery contract (Refs #333). The Tauri
plugin's discovery commands cross a Rust ↔ Swift/Kotlin FFI boundary matched
by string at runtime (
run_mobile_plugin("name")↔@objc func name↔@Command fun name). Until now CI onlycargo checked the Rust half, so arename or signature drift (as risked in #330) was a silent, device-only runtime
failure. This PR makes that drift fail CI.
What's added
String-parity contract test (
tests::ffi_contractinpackages/iroh-http-tauri/src/tests.rs). Becausemobile_mdns.rsis#[cfg(mobile)]and not host-compiled, it source-scans the three filesand asserts the command-name sets are identical in every direction (fails on
a Rust string with no native handler and on an orphaned native handler).
Runs in the existing host-side
cargo testfor the crate — no new toolchain,milliseconds. Verified it fails on an injected rename and on an orphaned
handler, in both directions. Would have caught feat(discovery): standardize desktop mDNS on DNS-SD for iOS/Android interop #330 with certainty.
Native compile jobs (build-only; no device, emulator/simulator, NDK, or
Rust mobile cross-compile). The Tauri mobile API ships inside the
tauricrate (
mobile/ios-api,mobile/android), so the scriptscargo fetchit,locate it via
cargo metadata, and compile the native sources against theversion this crate's
Cargo.lockalready resolves:swift build(iphonesimulator SDK), folded into the existingrust-check-macosjob to avoid a second billed macOS runner.android-ci/) runs:tauri-plugin-iroh-http:assembleDebugon ubuntu (no macOS cost).Both are gated to
pull_request/ tag builds like the other platform jobs,so a native contract break blocks merge (acceptance criterion 3).
Two latent bugs fixed — surfaced immediately because the native sources
had never been compiled in CI:
drainResolveQueue()destructured a nullablePairacross asynchronized {}lambda — rejected by Kotlin 1.9 (whattauri android initgenerates). Restructured to preserve locking semantics.
build.gradle.ktsreferenced a missingconsumer-rules.pro.Docs —
docs/build-and-test.mdgains a coverage matrix stating what CIverifies (parity + iOS/Android compile, all deterministic) vs. what still
needs manual on-device verification (real multicast mDNS behavior, tracked in
Verify DNS-SD discovery on real iOS and Android devices #334), plus local run instructions.
Validation
All jobs were validated locally on macOS (Xcode 26, JDK 21, Android SDK):
swift buildandgradle assembleDebugboth compile the plugin cleanly; theparity test passes and fails correctly on injected drift;
cargo fmtand thefull tauri crate test suite (29 tests) pass.
Explicitly out of scope
Real multicast mDNS behavior (advertise/browse/self-echo/endpoint resolution)
still requires a device on a LAN — see #334. This PR closes the gap on the
contract, not the behavior.
Refs #333, #334. (Leaving #333 open for the requester to close.)