Skip to content

CI: add SwiftPM iOS e2e workflows (RNTester, HelloWorld, new app)#57659

Open
chrfalch wants to merge 5 commits into
mainfrom
chrfalch/add-e2e-tests-for-swiftpm
Open

CI: add SwiftPM iOS e2e workflows (RNTester, HelloWorld, new app)#57659
chrfalch wants to merge 5 commits into
mainfrom
chrfalch/add-e2e-tests-for-swiftpm

Conversation

@chrfalch

Copy link
Copy Markdown
Collaborator

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.ymlpackages/rn-tester
  • test-ios-spm-helloworld.ymlprivate/helloworld
  • test-ios-spm-newapp.yml — a fresh copy of private/helloworld, wired to a Verdaccio-published build of this monorepo

None 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 slices input to prebuild-ios-dependencies.yml and prebuild-ios-core.yml. It defaults to the full platform set, so existing callers (test-all.yml, releases) are unchanged; the e2e workflows request only ios-simulator, since that is all they compile against.

Changelog:

[INTERNAL] [ADDED] - CI workflows validating the SwiftPM iOS build (RNTester, HelloWorld, new app)

Test Plan:

  • Trialed the HelloWorld workflow on this branch (temporary push trigger, since removed): full run green for both Debug and Release, incl. the real spm add + xcodebuild. The slices input correctly collapsed the prebuild matrices to the single ios-simulator slice per flavor.
  • node --check, prettier --check, and eslint clean on both scripts; all workflow YAML validated.

chrfalch and others added 2 commits July 24, 2026 08:53
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>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 24, 2026
@chrfalch
chrfalch requested a review from cipolleschi July 24, 2026 07:41
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Jul 24, 2026
chrfalch and others added 2 commits July 24, 2026 10:11
- 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 cipolleschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +22 to +36
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +80 to +89
- 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't have to download both. You can have a matrix and run Debug and Release in parallel.

Comment on lines +22 to +36
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Comment on lines +40 to +57
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'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment on lines +111 to +120
- 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, Debug and release should run in parallel.

@@ -0,0 +1,115 @@
name: Test iOS SwiftPM - RN Tester

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Comment thread scripts/e2e/spm-ios-e2e.js Outdated
@@ -0,0 +1,525 @@
/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this? can't we run the commands you show during the AppJS talk?

Comment thread scripts/e2e/spm-prime-artifacts.js Outdated
@@ -0,0 +1,185 @@
/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@chrfalch

Copy link
Copy Markdown
Collaborator Author

Thanks for the review — all five points addressed.

1. Don't re-run the prebuilds. Done. The workflows are workflow_call-only and download ReactCore{Debug,Release} + ReactNativeDependencies{Debug,Release} from the run, following e2e-ios-templateapp. The slices input I'd added to the two prebuild workflows is reverted (zero diff vs main).

2. Call it from test-all, no cron. Done — three reusable calls modeled on test_e2e_ios_templateapp, gated on should_test_ios. One flag: test-all.yml has no schedule trigger (only dispatch / PR / push to main), so these now run per-PR and on main pushes rather than nightly — more coverage than the cron I removed, matching test_ios_helloworld / test_ios_rntester. Since this lane has never run before, I've left it continue-on-error: true so it can't hard-block unrelated iOS PRs; happy to flip that off once it's proven green, or to move it somewhere cheaper if 6 macOS jobs per iOS PR is too much.

3. Don't rebuild the npm package. Done. newapp needs: build_npm_package and downloads react-native-package, then uses the existing scripts/e2e/init-project-e2e.js --useHelloWorld — the same invocation test-all already uses for Android.

4. The two scripts. Both deleted. Priming needed no new code: download-spm-artifacts.js already accepts tarball overrides via RN_CORE_TARBALL_PATH / RN_DEPS_TARBALL_PATH, so it's an inline npx react-native spm download --artifacts <dir>. Net −650 lines vs the previous revision.

5. Debug/Release in parallel. They are parallel matrix cells. Each cell does still stage both flavors: ensureBothArtifactFlavors (setup-apple-spm.js:555-576, called from the add pipeline at ~:1102) validates both the debug/ and release/ slots unconditionally, and --download skip throws on an incomplete one — by design, since spm add stages both flavor framework trees and a per-configuration build setting selects one at build time. A true single-flavor cell would need a flavor-scoping flag in setup-apple-spm.js; glad to do that as a follow-up if you want the downloads halved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants