Repository navigation
ci(native): build and test the native Swift packages on macOS - #7984
Conversation
Adds a Native Swift workflow that builds and tests every Swift package under packages/native on a macOS runner: `swift test` for packages that support macOS, and an iOS Simulator build (or test, for an iOS-only package). Until now the generated Swift binding was only typechecked by hand.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
PR Analysis Report📚 Storybook Preview🧪 Sandbox PreviewNo new or modified components detected. Bundle Size SummaryNo component packages changed. Accessibility AuditStatus: No accessibility violations detected. Visual Regression46 of 496 shot(s) changed. A change here is a question, not a failure: check whether the after is the
and 26 more. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
There was a problem hiding this comment.
Requesting changes: compiling and testing the native Swift in CI closes a real gap, but this workflow routes its own pull-request lane with a paths filter. docs/specs/AST-030/spec.md FR11 instead requires new coverage for paths the CI classifier sends to shared-or-unknown, as it does for packages/native/**, to join that broad owner. Please either run the Swift job from the broad lane in ci.yml and add it to the test join, or first land an owner-approved FR10 amendment to AST-030 that admits a native surface and route the job through it. While there, run swift test only when a package has a test target (.github/workflows/native-swift.yml:66-67), since SwiftPM fails swift test on a package without one.
[Automated review]
| pull_request: | ||
| branches: ['main'] | ||
| paths: | ||
| - 'packages/native/**/*.swift' | ||
| - 'packages/native/**/Package.swift' | ||
| - '.github/workflows/native-swift.yml' |
There was a problem hiding this comment.
This pull_request path filter makes the Swift build a separately routed lane. The CI classifier already routes packages/native/** to shared-or-unknown, and AST-030 FR11 says new coverage inside an existing surface must join that owner instead of creating a separately routed lane. Consider running this job from the broad lane in ci.yml, added to the test join, or admitting a native surface through an FR10 amendment first. The push: main trigger below is outside AST-030's scope.
| xcodebuild -list -json > "$RUNNER_TEMP/schemes.json" | ||
| scheme=$(python3 -c 'import json,sys; s=json.load(open(sys.argv[1]))["workspace"]["schemes"]; print(next((x for x in s if x.endswith("-Package")), s[0]))' "$RUNNER_TEMP/schemes.json") | ||
| if [ "$macos" = True ]; then | ||
| swift test |
There was a problem hiding this comment.
swift test runs here even when $tests is False. For a package without a test target, SwiftPM stops with error: no tests found, and set -euo pipefail then skips the iOS Simulator build and every later package. Guarding it with [ "$tests" = True ], and running swift build otherwise, avoids that once such a package exists.
Moves the Swift build out of its own path-filtered workflow into ci.yml as test-native-swift, joined by test (AST-030 FR11). The job does its macOS work only when a pull request changes Swift under packages/native or ci.yml, and builds a package without a test target instead of running swift test.
There was a problem hiding this comment.
Requesting changes: joining the Swift build to test in ci.yml is the owner AST-030 FR11 asks for, and the job compiled and tested the native Swift on this head, but it now holds every non-spec pull request's required test check for a macos-15 runner, usually followed by a full-history clone, even when nothing Swift changed (this pull request's runs queued 12 and 31 minutes for that runner). Please decide applicability on a Linux runner first and request macos-15 only when Swift work applies, for example with a conditional runs-on or an expected-skip rule in ci-test-join.mjs like the one test-build has, and skip the job's work on tooling-only changes as test-ui does. While there, replace the inline git diff ... || true check with .github/scripts/pr-scope-touches.sh loaded from the base SHA, which fails closed when it cannot compute the diff, counts renames, and does not need fetch-depth: 0.
[Automated review]
| test-native-swift: | ||
| needs: [check-scope] | ||
| if: ${{ github.event_name != 'workflow_dispatch' && always() && !cancelled() && needs.check-scope.outputs.spec_only != 'true' }} | ||
| runs-on: macos-15 |
There was a problem hiding this comment.
This puts a macos-15 runner in front of test on every non-spec pull request, including docsite-only and tooling-only ones that never build Swift, and outside docsite-only changes the full-history checkout below as well; this pull request's runs queued 12 and 31 minutes for that runner. Deciding changed on a Linux runner first and using something like runs-on: ${{ needs.<scope>.outputs.changed == 'true' && 'macos-15' || 'ubuntu-slim' }} (or an expected-skip rule in ci-test-join.mjs like test-build's) keeps the coverage without the wait.
| exit 0 | ||
| fi | ||
| changed=$(git diff --name-only "$(git merge-base "$BASE_SHA" HEAD)" HEAD \ | ||
| | grep -E '^packages/native/.*(\.swift|Package\.swift)$|^\.github/workflows/ci\.yml$' || true) |
There was a problem hiding this comment.
|| true also hides a failed git merge-base or git diff, so a missing base SHA reads as No native Swift change and the build is skipped with success; a .swift file renamed out of the pattern is missed too. .github/scripts/pr-scope-touches.sh, loaded from the base SHA as at line 1642, exits 2 when it cannot tell and uses --no-renames; treating any exit other than 1 as changed=true fails closed and lets the checkout drop fetch-depth: 0.
…anged A native-swift-scope job on ubuntu-slim decides, with pr-scope-touches.sh loaded from the base, whether the change touches Swift under packages/native. test-native-swift asks for macos-15 only then, and otherwise passes on Linux, so other pull requests no longer wait for a macOS runner. Docsite-only and tooling-only changes skip the Swift work; an undecidable scope runs it.
There was a problem hiding this comment.
Recommending approval: the Swift build now runs inside test from ci.yml, a Linux job decides whether native Swift changed and requests macos-15 only then, and a scope that cannot be decided still runs the build, which resolves the earlier automated change requests. As an optional follow-up, ci-native-swift.test.mjs stays green if the default scope=2 (ci.yml:769) becomes scope=1 or the pattern at ci.yml:775 stops matching Swift, so pinning both, or running the step against a few fixture paths, would keep the lane from breaking silently. Separately, with ci.yml out of the pattern an edit to the Swift step itself, like this pull request, never runs it; moving the script into its own file under .github/scripts/ and adding that path to the pattern would cover such edits without putting macOS on every workflow change. The description's Evidence line still says this pull request's own run exercises the Swift job, which no longer holds at this head.
[Automated review]
| 'git show "$BASE_SHA:.github/scripts/pr-scope-touches.sh"', | ||
| ); | ||
| // Only "out of scope" (exit 1) skips; unknown (2) runs the Swift work. | ||
| expect(decide).toContain('if [ "$scope" = 1 ]; then'); |
There was a problem hiding this comment.
This pins the comparison, but not the default scope=2 (ci.yml:769) or the pattern (ci.yml:775): changing the default to scope=1, or the pattern to one that cannot match a Swift path, leaves the whole .github/scripts suite green while native Swift changes would skip the build. Asserting both, or running the step against a few fixture paths (Swift source, Package.swift, a new swift-ios-<n>/ package, a moved-out .swift), would make the fail-closed promise mutation-sensitive.
| && git fetch --quiet --no-tags --depth=1 origin "$BASE_SHA" \ | ||
| && git show "$BASE_SHA:.github/scripts/pr-scope-touches.sh" > "$helper"; then | ||
| scope=0 | ||
| bash "$helper" "$BASE_SHA" '^packages/native/.*(\.swift|/Package\.swift)$' || scope=$? |
There was a problem hiding this comment.
With ci.yml out of this pattern (the earlier head had it), an edit to the build step below never runs it, so a break first fails the next native Swift pull request. Moving the script into its own file under .github/scripts/ and adding that path here would cover lane edits without putting macos-15 in front of every workflow change.
Maintainer impact
The Swift package in
@astryxdesign/native(generated contracts underapple/) and the versionedswift-ios-<n>/packages have no CI. Nothing compiles their Swift or runs their Swift tests, so a generator change can ship Swift that does not build, and an iOS-only branch (#if os(iOS)) is never compiled at all.Intended invariant
AST-082 FR5 and FR16: the Swift binding is generated from the same contracts as the TypeScript types, and a core contract change reaches every native platform on its next build. The Node drift test already proves the committed Swift matches the generator; this lane proves that Swift builds and its tests pass.
Change
Two jobs in
ci.yml, withtestjoining the second (AST-030 FR11: new coverage inside the shared surface joins its owner, no separately routed lane):native-swift-scope(ubuntu-slim) decides withpr-scope-touches.sh, loaded from the base, whether the change touches Swift or aPackage.swiftunderpackages/native. Docsite-only and tooling-only changes skip; an undecidable scope runs the work.test-native-swiftasks formacos-15only when that scope applies and otherwise passes onubuntu-slimwithout work, so no other pull request waits for a macOS runner. On macOS, for every directory holding aPackage.swift:swift testwhen the package supports macOS and has a test target (elseswift build), an iOS Simulator build, and the iOS Simulator tests of an iOS-only package. The scheme is read fromxcodebuild -list.ci-test-join.mjsrequirestest-native-swiftto succeed;ci-native-swift.test.mjspins the routing (Linux scope, conditional runner, fail-closed scope, specialized-lane skip).Evidence
packages/native/**/*.swift.ci.yml, the test-join script and its tests).Scope
Testing
pnpm vitest run --project node .github/scripts: 66 files, 1182 tests pass (join matrix, historical-join contract, new routing test).macos-15at two earlier heads (55 s as a separate workflow; 2 min 10 s astest-native-swift). This head changes only CI files, so its own run skips the Swift work; the stacked PRs above exercise it.test-node (4/4)(official package list, MobilePowerSearch) andtest-ui (2/4)(PowerSearch filter flow) fail at the base commit too; they are a main breakage, not this change.