CI: add SwiftPM iOS e2e workflows (RNTester, HelloWorld, new app)#57659
CI: add SwiftPM iOS e2e workflows (RNTester, HelloWorld, new app)#57659chrfalch wants to merge 5 commits into
Conversation
Adds three standalone workflows that scaffold + convert an iOS app to
SwiftPM using the prebuilt XCFrameworks and compile it:
- test-ios-spm-rntester.yml (packages/rn-tester)
- test-ios-spm-helloworld.yml (private/helloworld)
- test-ios-spm-newapp.yml (fresh copy of helloworld, wired to a
Verdaccio-published local monorepo build)
None of these use @react-native-community/cli. Each workflow builds the
prebuilt dependencies + core XCFrameworks via the existing reusable
prebuild-ios-* workflows, primes both flavor slots, then runs the shared
logic in two committed, locally-runnable scripts:
- scripts/e2e/spm-prime-artifacts.js (lay CI tarballs + hermes into the
--artifacts <dir>/<flavor>/ layout)
- scripts/e2e/spm-ios-e2e.js (scaffold -> spm add --deintegrate
-> xcodebuild, per app)
Triggered manually (workflow_dispatch) and nightly (schedule).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an optional `slices` input to prebuild-ios-dependencies.yml and prebuild-ios-core.yml, driving their build matrix from it. It defaults to the full platform set, so existing callers (test-all.yml, releases) are unchanged. The three SwiftPM iOS e2e workflows only ever compile for the iOS simulator, so they now request `["ios-simulator"]` from both prebuild workflows — cutting the dependencies matrix from 8 slices to 1 and the core matrix from 3 to 1 (per flavor), which is the dominant cost of those e2e runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- spm-ios-e2e.js: build the execFileSync `env` from process.env's string entries only (process.env is Flow-typed to allow numbers, which the execFileSync `env` option rejects), and omit `env` entirely when there is no override so the child inherits process.env as-is. - Both scripts: suppress the parseArgs [incompatible-type] error with the standard "Natural Inference rollout" $FlowFixMe, matching set-version.js and init-project-e2e.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
execFileSync's `env` option is invariantly typed {[string]: string |
number | void}, so neither a plain process.env spread ({[string]: string
| void}) nor a string-only object satisfies it. Build the env object
into a correctly-typed indexer instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cipolleschi
left a comment
There was a problem hiding this comment.
Thanks for hardening our CI. However, I think that we are not quite there. This approach does not follow the patterns we are currently using in CI. I left a few comments to improve this approach.
| prebuild_apple_dependencies: | ||
| uses: ./.github/workflows/prebuild-ios-dependencies.yml | ||
| with: | ||
| # e2e only compiles for the iOS simulator — no need for device/catalyst/tv/xr slices. | ||
| slices: '["ios-simulator"]' | ||
| secrets: inherit | ||
|
|
||
| prebuild_react_native_core: | ||
| needs: [prebuild_apple_dependencies] | ||
| if: ${{ needs.prebuild_apple_dependencies.result == 'success' }} | ||
| uses: ./.github/workflows/prebuild-ios-core.yml | ||
| with: | ||
| use-hermes-prebuilt: ${{ !endsWith(github.ref_name, '-stable') }} | ||
| slices: '["ios-simulator"]' | ||
| secrets: inherit |
There was a problem hiding this comment.
I think you have to depend on the job that creates the XCFramework and download the XCFramework directly and use that one. We don't want to run the prebuild for these jobs. We want to consume the prebuilds we are creating.
| - name: Download ReactCore (Debug) | ||
| uses: actions/download-artifact@v7 | ||
| with: | ||
| name: ReactCoreDebug.xcframework.tar.gz | ||
| path: /tmp/rc-debug | ||
| - name: Download ReactCore (Release) | ||
| uses: actions/download-artifact@v7 | ||
| with: | ||
| name: ReactCoreRelease.xcframework.tar.gz | ||
| path: /tmp/rc-release |
There was a problem hiding this comment.
you don't have to download both. You can have a matrix and run Debug and Release in parallel.
| prebuild_apple_dependencies: | ||
| uses: ./.github/workflows/prebuild-ios-dependencies.yml | ||
| with: | ||
| # e2e only compiles for the iOS simulator — no need for device/catalyst/tv/xr slices. | ||
| slices: '["ios-simulator"]' | ||
| secrets: inherit | ||
|
|
||
| prebuild_react_native_core: | ||
| needs: [prebuild_apple_dependencies] | ||
| if: ${{ needs.prebuild_apple_dependencies.result == 'success' }} | ||
| uses: ./.github/workflows/prebuild-ios-core.yml | ||
| with: | ||
| use-hermes-prebuilt: ${{ !endsWith(github.ref_name, '-stable') }} | ||
| slices: '["ios-simulator"]' | ||
| secrets: inherit |
| build_npm_package: | ||
| runs-on: 8-core-ubuntu | ||
| container: | ||
| image: reactnativecommunity/react-native-android:latest | ||
| env: | ||
| TERM: 'dumb' | ||
| LC_ALL: C.UTF8 | ||
| GRADLE_OPTS: '-Dorg.gradle.daemon=false' | ||
| REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v6 | ||
| - name: Build NPM Package | ||
| uses: ./.github/actions/build-npm-package | ||
| with: | ||
| release-type: dry-run | ||
| gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} | ||
| skip-apple-prebuilts: 'true' |
There was a problem hiding this comment.
I don't think we rerun the build npm package when running the template app e2e tests. we should not do it here either.
| @@ -0,0 +1,115 @@ | |||
| name: Test iOS SwiftPM - Hello World | |||
There was a problem hiding this comment.
i would just create the workflow and call it from test-all. I would not add a cron job here. We should rely on the nightly trigger.
| @@ -0,0 +1,155 @@ | |||
| name: Test iOS SwiftPM - New App | |||
| - name: Download ReactCore (Debug) | ||
| uses: actions/download-artifact@v7 | ||
| with: | ||
| name: ReactCoreDebug.xcframework.tar.gz | ||
| path: /tmp/rc-debug | ||
| - name: Download ReactCore (Release) | ||
| uses: actions/download-artifact@v7 | ||
| with: | ||
| name: ReactCoreRelease.xcframework.tar.gz | ||
| path: /tmp/rc-release |
There was a problem hiding this comment.
same as above, Debug and release should run in parallel.
| @@ -0,0 +1,115 @@ | |||
| name: Test iOS SwiftPM - RN Tester | |||
| @@ -0,0 +1,525 @@ | |||
| /** | |||
There was a problem hiding this comment.
do we need this? can't we run the commands you show during the AppJS talk?
| @@ -0,0 +1,185 @@ | |||
| /** | |||
There was a problem hiding this comment.
I don't think we need this for the testing. We know in advance what we are running and we can have two parallel jobs running one debug and one release.
Addresses review feedback that the original approach did not follow the patterns already used in CI: - The three workflows no longer trigger prebuild-ios-dependencies / prebuild-ios-core themselves. They are workflow_call-only and consume the ReactCore / ReactNativeDependencies artifacts produced elsewhere in the run, the way e2e-ios-templateapp does. The `slices` input added to the two prebuild workflows is reverted. - They are invoked from test-all.yml instead of carrying their own cron. Note this means they run per-PR (gated on should_test_ios) and on pushes to main, not nightly -- test-all.yml has no schedule trigger. They start as continue-on-error so a brand-new lane cannot hard-block unrelated iOS PRs; remove that once it has proven green on main. - The new-app workflow reuses the react-native-package artifact instead of rebuilding the npm package. - Both helper scripts are deleted. Priming the artifact slots needs no new code: download-spm-artifacts.js already accepts tarball overrides via RN_CORE_TARBALL_PATH / RN_DEPS_TARBALL_PATH, so the workflows call `npx react-native spm download` directly, and the new-app case reuses scripts/e2e/init-project-e2e.js --useHelloWorld. Debug and Release run as parallel matrix cells. Each cell stages both flavor slots because `spm add` validates both (it stages both framework trees and selects one via a per-configuration build setting). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for the review — all five points addressed. 1. Don't re-run the prebuilds. Done. The workflows are 2. Call it from test-all, no cron. Done — three reusable calls modeled on 3. Don't rebuild the npm package. Done. newapp 4. The two scripts. Both deleted. Priming needed no new code: 5. Debug/Release in parallel. They are parallel matrix cells. Each cell does still stage both flavors: |
Summary:
Adds CI coverage for the SwiftPM (SPM) iOS path, which currently has none. Three standalone workflows scaffold + convert an app to SPM using the prebuilt XCFrameworks and compile it for the iOS simulator:
test-ios-spm-rntester.yml—packages/rn-testertest-ios-spm-helloworld.yml—private/helloworldtest-ios-spm-newapp.yml— a fresh copy ofprivate/helloworld, wired to a Verdaccio-published build of this monorepoNone of them use
@react-native-community/cli. The shared logic lives in two committed, locally-runnable scripts (scripts/e2e/spm-prime-artifacts.js,scripts/e2e/spm-ios-e2e.js); the workflows are thin wrappers. They run manually (workflow_dispatch) and nightly (schedule).This also adds an optional
slicesinput toprebuild-ios-dependencies.ymlandprebuild-ios-core.yml. It defaults to the full platform set, so existing callers (test-all.yml, releases) are unchanged; the e2e workflows request onlyios-simulator, since that is all they compile against.Changelog:
[INTERNAL] [ADDED] - CI workflows validating the SwiftPM iOS build (RNTester, HelloWorld, new app)
Test Plan:
spm add+xcodebuild. Theslicesinput correctly collapsed the prebuild matrices to the singleios-simulatorslice per flavor.node --check,prettier --check, andeslintclean on both scripts; all workflow YAML validated.