From 4645561dc18105fd20f884125c3d2c0c1feab168 Mon Sep 17 00:00:00 2001 From: Mohamed Ibrahim Date: Sun, 14 Jun 2026 13:20:23 +0100 Subject: [PATCH 1/2] =?UTF-8?q?ci:=20add=20PR=20build+test=20gate=20?= =?UTF-8?q?=E2=80=94=20JVM=20on=20ubuntu,=20build-all=20+=20iOS=20on=20mac?= =?UTF-8?q?OS=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After this commit every pull request (and push to main) builds all KMP targets and runs the unit tests before it can merge. Until now PRs ran no build or tests — only api-check and the tag-triggered publish workflow — so regressions could merge unnoticed and the launch-hardening PRs had to be verified by hand. Two jobs: - jvm (ubuntu): runs :sharingan/:sharingan-noop testDebugUnitTest, which compiles the Android + commonMain/commonTest sources and runs them on the JVM. Cheap, fast feedback. No iOS/Native tasks — those can't link on Linux. - ios (macOS): runs `assemble` (the cross-target build gate that compiles every target incl. the iOS frameworks) then iosSimulatorArm64Test for both modules. iOS runs on every PR, not deferred. concurrency cancels superseded runs to cap macOS minutes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/build.yml | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6569792 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,84 @@ +name: Build & Test + +# PR-time build/test gate (issue #22). Every pull request and every push to main +# must build all KMP targets and run the JVM + iOS unit tests before it can merge. +# This complements the other workflows, which guard different things: +# - api-check.yml -> public-API surface stability (apiCheck) +# - publish.yml -> tag-triggered Maven Central release +# Neither builds the code or runs tests on a PR, so this workflow is the actual +# correctness gate. A PR that introduces a failing unit test is blocked here: +# - a broken common/JVM test fails the `jvm` job's testDebugUnitTest step; +# - a broken iOS test fails the `ios` job's iosSimulatorArm64Test step. +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +# Cancel superseded runs on the same ref (e.g. a force-push to a PR branch) so we +# don't burn runner minutes — especially the macOS minutes the iOS job consumes. +concurrency: + group: build-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # JVM/common unit tests. Runs on ubuntu (cheap, fast feedback): the + # Android-hosted debug unit-test task compiles the Android + commonMain/ + # commonTest sources and runs them on the JVM, so a shared-logic regression + # fails here within minutes without waiting on a macOS runner. iOS/Kotlin- + # Native tasks are deliberately NOT invoked here — Kotlin/Native iOS targets + # only link on macOS, so `assemble` (which pulls in the iOS framework link + # tasks) is run in the macOS job below, not here. + jvm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + # commonTest is executed by the Android debug unit-test task. A failing + # test here blocks the PR. + - name: Run JVM/common unit tests + run: ./gradlew :sharingan:testDebugUnitTest :sharingan-noop:testDebugUnitTest + + # Full multiplatform build + iOS simulator unit tests on macOS — the only + # host that can link the Kotlin/Native iOS frameworks. This job (a) runs + # `assemble`, the cross-target build gate that compiles EVERY target (Android + # AARs and the iOS arm64/simulator frameworks), and (b) runs the Kotlin/Native + # unit tests on the iOS simulator. It runs on EVERY PR (not deferred): the + # library ships an iOS target, so an iOS-only regression must not reach a + # release unnoticed. The `concurrency` block above caps wasted macOS minutes. + ios: + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + # Build every target on the canonical host (the only one that links the + # iOS frameworks). A target that no longer compiles fails the PR here. + - name: Assemble all targets + run: ./gradlew assemble + + # Executes Kotlin/Native unit tests on the iOS simulator. The :sharingan + # simulator deviceId is pinned in sharingan/build.gradle.kts; if a runner's + # Xcode lacks that device the step fails fast (a deviceId follow-up, not a + # silent skip). + - name: Run iOS simulator unit tests + run: ./gradlew :sharingan:iosSimulatorArm64Test :sharingan-noop:iosSimulatorArm64Test From 37cdfb51cee6acdf2f5b46cdb3717fff62718493 Mon Sep 17 00:00:00 2001 From: Mohamed Ibrahim Date: Sun, 14 Jun 2026 13:35:48 +0100 Subject: [PATCH 2/2] ci: run the iOS job on macos-26 (Xcode 26 / iOS 26 SDK) (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first CI run proved the structure works (jvm + api-check green) but the iOS job failed: on the default macos-latest image (Xcode 16.4, iOS 18 SDK) the Compose-UIKit runtime references iOS 26 symbols (UIViewLayoutRegion / UIUtilities), so linkDebugFrameworkIosArm64 fails with "Undefined symbols for architecture arm64". The project's CMP version requires the iOS 26 SDK, which only ships with Xcode 26 — present on macos-26. (api-check stays on macos-latest because it only compiles klibs and never links a framework.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/build.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6569792..0117cff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,7 +58,14 @@ jobs: # library ships an iOS target, so an iOS-only regression must not reach a # release unnoticed. The `concurrency` block above caps wasted macOS minutes. ios: - runs-on: macos-latest + # macos-26 (Tahoe) ships Xcode 26 / the iOS 26 SDK. The default macos-latest + # image (Xcode 16.x, iOS 18 SDK) CANNOT link this project's frameworks: the + # Compose-UIKit runtime references iOS 26 symbols (e.g. UIViewLayoutRegion in + # the UIUtilities framework), so `linkDebugFrameworkIos*` fails with + # "Undefined symbols for architecture arm64" on an older SDK. api-check.yml + # only *compiles* klibs (never links a framework), which is why it still + # passes on macos-latest. + runs-on: macos-26 steps: - uses: actions/checkout@v6 @@ -76,9 +83,10 @@ jobs: - name: Assemble all targets run: ./gradlew assemble - # Executes Kotlin/Native unit tests on the iOS simulator. The :sharingan - # simulator deviceId is pinned in sharingan/build.gradle.kts; if a runner's - # Xcode lacks that device the step fails fast (a deviceId follow-up, not a - # silent skip). + # Executes Kotlin/Native unit tests on the iOS simulator. :sharingan pins + # deviceId "iPhone 17 Pro" (sharingan/build.gradle.kts), which exists in + # the macos-26 iOS 26 runtime; :sharingan-noop pins none and uses the KGP + # default. If a runtime lacks the device the step fails fast (no silent + # skip) — a deviceId follow-up, not a hidden gap. - name: Run iOS simulator unit tests run: ./gradlew :sharingan:iosSimulatorArm64Test :sharingan-noop:iosSimulatorArm64Test