Skip to content

ci: add quick iOS build+test pass and gate WASM deploy on every test - #24

Merged
Azyyyyyy merged 3 commits into
mainfrom
ci-quick-ios-check
Jul 30, 2026
Merged

ci: add quick iOS build+test pass and gate WASM deploy on every test#24
Azyyyyyy merged 3 commits into
mainfrom
ci-quick-ios-check

Conversation

@Azyyyyyy

Copy link
Copy Markdown
Owner

Adds a quick iOS build + test pass to the PR CI workflow so we get signal that a mobile release will still compile before we cut a tag, without paying the 30-40 min xcodebuild archive -> IPA pack -> attest tail that mobile-release.yml does.

Also gates the WASM deploy on every test job, not just Android.

New job: ios-test

  • runs-on: macos-26 for iOS 26 SDK parity with mobile-release (same runner class we picked to unblock the Compose UIViewLayoutRegion link).
  • timeout-minutes: 40 hard ceiling so a stuck simulator boot doesn't burn macOS minutes.
  • ~/.konan + CocoaPods caches keyed to match mobile-release.yml verbatim so PR runs pre-warm the release workflow (and vice versa).
  • Prep: ./gradlew :shared:writeIosVersionXcconfig :shared:generateDummyFramework (no -PappVersion.* flags -- CI doesn't need release-shaped version stamps).
  • pod install in iosApp/ with the same cache-hit --repo-update guard as release.
  • Fast smoke ./gradlew :shared:compileTestKotlinIosSimulatorArm64 -- analog to the Android job's Compile instrumented tests fast pass at line 42; fails fast on source-set / cinterop / pod-drift issues before we pay simulator boot.
  • xcodebuild build against Simulator arch with signing disabled -- same Swift + KMP + linker surface as the release archive, just without the archive/IPA/signing wrapping.
  • ./gradlew :shared:iosSimulatorArm64Test -- direct KMP analog of :shared:connectedAndroidDeviceTest; iterates the same commonTest corpus across both mobile targets, which is the "same tests that run on Android if possible" ask.
  • Upload iosSimulatorArm64 test reports on always().

Every test job now gates build-wasm-image

Changed:

+++
build-wasm-image:
needs: [test, desktop-test, integration-test, ios-test]
if: github.event_name == 'push'
+++

The if: github.event_name == 'push' guard is unchanged, so this only affects merged-to-main deploys -- PRs still surface each job's failure as check-run signal without blocking anything.

Trade-off: the old design kept the WASM deploy resilient to flakes in desktop-test (Skia/AWT), integration-test (Testcontainers pulling ghcr.io), and now ios-test (simulator boot). Any of those flaking on main now holds up the WASM deploy until re-run. Standard mitigation is the "Re-run failed jobs" button; if any prove flaky in practice we can carve exceptions back out job-by-job. The two rationale comments above desktop-test and integration-test have been updated to reflect this so future readers don't work off stale intent.

Expected cost

  • Cold cache (first PR after merge): ~20 min.
  • Warm cache (subsequent PRs): ~10-15 min.
  • Compare mobile-release iOS at ~30-40 min.

Known risks

  • iOS 26 simulator runtime on macos-26: expected preinstalled but not verified in the earlier mobile-release runs (they only used the SDK for archive linking, never booted a simulator). If missing, the iosSimulatorArm64Test step fails with "Unable to boot device". Fallback is adding xcodes runtimes install "iOS 26" -- will add that only if the first cold run needs it.
  • ~/.konan cache growth: adding iosSimulatorArm64 slice grows the shared cache entry by ~1-2 GB. Still well under the 10 GB repo quota.

Azyyyyyy and others added 3 commits July 30, 2026 14:09
Adds a fourth CI job `ios-test` on `macos-26` that mirrors the iOS
half of `mobile-release.yml` but stops at `xcodebuild build` (no
archive, no IPA, no attest). Runs the same `commonTest` corpus on
iosSimulatorArm64 that `test` already runs on Android, so common-code
regressions surface on both mobile targets before a mobile release.

  * writeIosVersionXcconfig + generateDummyFramework, no appVersion flags
  * pod install with the same cache-hit --repo-update guard as release
  * compileTestKotlinIosSimulatorArm64 fast smoke
  * xcodebuild build against Simulator arch, signing disabled
  * :shared:iosSimulatorArm64Test
  * Upload iosSimulatorArm64 test reports on always()

Shares the ~/.konan and CocoaPods cache keys with mobile-release.yml so
PR runs pre-warm the release workflow (and vice versa).

Also expands `build-wasm-image.needs` from `test` to
`[test, desktop-test, integration-test, ios-test]` and updates the
two rationale comments (above desktop-test and integration-test) so the
design intent tracks the new gate. The `if: github.event_name ==
'push'` guard is unchanged, so this only affects merged-to-main
deploys — PRs still surface job failures as check-run signal without
blocking anything.

Expected cost:
  * cold cache first PR: ~20 min
  * warm cache: ~10-15 min
Compare mobile-release iOS at ~30-40 min.

Co-authored-by: Cursor <cursoragent@cursor.com>
Without ARCHS=arm64 ONLY_ACTIVE_ARCH=YES, xcodebuild's generic Simulator destination on Apple Silicon macos-26 tries to build a fat arm64+x86_64 slice. Compose Multiplatform's :shared:syncPodComposeResourcesForIos then aborts with 'Unknown iOS simulator arch: x86_64' because shared/build.gradle.kts only declares iosArm64 and iosSimulatorArm64 targets (no iosX64). Pinning to arm64 keeps the slice single-arch and matches the iosSimulatorArm64 KMP target the tests use.

Co-authored-by: Cursor <cursoragent@cursor.com>
linkDebugTestIosSimulatorArm64 hit a Kotlin compiler assertion when consuming the incrementally-cached multiplatform-markdown-renderer klib: 'Lowering ReturnsInsertion: phases [Enums] are required, but not satisfied'. The compiler itself surfaces the workaround (kotlin.incremental.native=false) in the same message. Applied to both the smoke compile and the actual test step so the K/N linker uses a fresh build.

This does NOT invalidate the ~/.konan cache (~1-2 GB, saves 5-10 min of Kotlin/Native compiler + platform-lib downloads per run) — that cache holds the compiler and platform bindings, which are project-independent. kotlin.incremental.native only controls per-module IR snapshots under each project's build/, which our CI does not cache across runs anyway.

Mobile-release iOS is unaffected (uses linkReleaseFramework, a different phase pipeline that doesn't hit the [Enums] assertion). Kept as a per-invocation -P flag rather than editing gradle.properties so local dev + mobile-release both keep incremental.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Azyyyyyy
Azyyyyyy merged commit 65bc9da into main Jul 30, 2026
5 checks passed
@Azyyyyyy
Azyyyyyy deleted the ci-quick-ios-check branch July 30, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant