Skip to content

Commit 1208178

Browse files
chrfalchmeta-codesync[bot]
authored andcommitted
CI: add SwiftPM iOS e2e workflows (RNTester, HelloWorld, new app) (#57659)
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-tester` - `test-ios-spm-helloworld.yml` — `private/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) Pull Request resolved: #57659 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. Reviewed By: huntie Differential Revision: D114044420 Pulled By: cipolleschi fbshipit-source-id: cb8723ae6d78bb765f3dc930d8010d4eb82ea4c0
1 parent 14fe96a commit 1208178

4 files changed

Lines changed: 456 additions & 0 deletions

File tree

.github/workflows/test-all.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,40 @@ jobs:
203203
fail-on-error: true
204204
secrets: inherit
205205

206+
test_ios_spm_rntester:
207+
needs:
208+
[
209+
prebuild_apple_dependencies,
210+
prebuild_react_native_core,
211+
check_code_changes,
212+
]
213+
if: ${{ needs.prebuild_react_native_core.result == 'success' && needs.check_code_changes.outputs.should_test_ios == 'true' }}
214+
uses: ./.github/workflows/test-ios-spm-rntester.yml
215+
secrets: inherit
216+
217+
test_ios_spm_helloworld:
218+
needs:
219+
[
220+
prebuild_apple_dependencies,
221+
prebuild_react_native_core,
222+
check_code_changes,
223+
]
224+
if: ${{ needs.prebuild_react_native_core.result == 'success' && needs.check_code_changes.outputs.should_test_ios == 'true' }}
225+
uses: ./.github/workflows/test-ios-spm-helloworld.yml
226+
secrets: inherit
227+
228+
test_ios_spm_newapp:
229+
needs:
230+
[
231+
build_npm_package,
232+
prebuild_apple_dependencies,
233+
prebuild_react_native_core,
234+
check_code_changes,
235+
]
236+
if: ${{ needs.prebuild_react_native_core.result == 'success' && needs.build_npm_package.result == 'success' && needs.check_code_changes.outputs.should_test_ios == 'true' }}
237+
uses: ./.github/workflows/test-ios-spm-newapp.yml
238+
secrets: inherit
239+
206240
test_e2e_android_templateapp:
207241
needs: [build_npm_package, build_android]
208242
if: ${{ always() && needs.build_android.result == 'success' && needs.build_npm_package.result == 'success' }}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
name: Test iOS SwiftPM - Hello World
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_call:
8+
9+
jobs:
10+
test:
11+
runs-on: macos-15-large
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
flavor: [Debug, Release]
16+
env:
17+
APP_IOS_DIR: private/helloworld/ios
18+
XCODE_PROJECT: HelloWorld.xcodeproj
19+
XCODE_SCHEME: HelloWorld
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v6
23+
- name: Setup xcode
24+
uses: ./.github/actions/setup-xcode
25+
- name: Setup node.js
26+
uses: ./.github/actions/setup-node
27+
- name: Run yarn install
28+
uses: ./.github/actions/yarn-install
29+
- name: Set Hermes prebuilt version
30+
shell: bash
31+
run: node ./scripts/releases/use-hermes-prebuilt.js
32+
- name: Run yarn install again, with the correct hermes version
33+
uses: ./.github/actions/yarn-install
34+
- name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`)
35+
shell: bash
36+
run: pod --version || sudo gem install cocoapods --no-document
37+
# Both flavors are needed by every matrix cell, not just the one it builds:
38+
# `spm add` stages both flavor framework trees and lets a per-configuration
39+
# build setting pick one at build time, so it validates the debug/ and
40+
# release/ artifact slots together (and `--download skip` refuses an
41+
# incomplete slot). Please don't "optimise" this down to one flavor.
42+
- name: Download ReactCore (Debug)
43+
uses: actions/download-artifact@v7
44+
with:
45+
name: ReactCoreDebug.xcframework.tar.gz
46+
path: /tmp/rc-debug
47+
- name: Download ReactCore (Release)
48+
uses: actions/download-artifact@v7
49+
with:
50+
name: ReactCoreRelease.xcframework.tar.gz
51+
path: /tmp/rc-release
52+
- name: Download ReactNativeDependencies (Debug)
53+
uses: actions/download-artifact@v7
54+
with:
55+
name: ReactNativeDependenciesDebug.xcframework.tar.gz
56+
path: /tmp/deps-debug
57+
- name: Download ReactNativeDependencies (Release)
58+
uses: actions/download-artifact@v7
59+
with:
60+
name: ReactNativeDependenciesRelease.xcframework.tar.gz
61+
path: /tmp/deps-release
62+
# The ordinary `spm download`, pointed at the XCFrameworks this run just
63+
# built rather than at the published nightly (RN_CORE_TARBALL_PATH /
64+
# RN_DEPS_TARBALL_PATH are overrides download-spm-artifacts.js already
65+
# supports). It fills both flavor slots per call and skips any slot that
66+
# already validates, so it runs twice: the first pass fills both slots
67+
# from the Release tarballs, then debug/ is dropped and refilled from the
68+
# Debug tarballs while release/ is left alone. hermes-engine is fetched
69+
# from Maven by the command itself.
70+
- name: Download XCFrameworks built in this run (Debug + Release)
71+
shell: bash
72+
working-directory: ${{ env.APP_IOS_DIR }}
73+
run: |
74+
RN_CORE_TARBALL_PATH=/tmp/rc-release/ReactCoreRelease.xcframework.tar.gz \
75+
RN_DEPS_TARBALL_PATH=/tmp/deps-release/ReactNativeDependenciesRelease.xcframework.tar.gz \
76+
npx react-native spm download --artifacts /tmp/spm-artifacts
77+
rm -rf /tmp/spm-artifacts/debug
78+
RN_CORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz \
79+
RN_DEPS_TARBALL_PATH=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz \
80+
npx react-native spm download --artifacts /tmp/spm-artifacts
81+
- name: Scaffold Package.swift manifests for community dependencies
82+
shell: bash
83+
working-directory: ${{ env.APP_IOS_DIR }}
84+
run: npx react-native spm scaffold || true
85+
- name: Convert the app to SwiftPM
86+
shell: bash
87+
working-directory: ${{ env.APP_IOS_DIR }}
88+
run: npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip
89+
- name: Assert the app is on SwiftPM
90+
shell: bash
91+
working-directory: ${{ env.APP_IOS_DIR }}
92+
run: |
93+
if [[ ! -f "$XCODE_PROJECT/.spm-injected.json" ]]; then
94+
echo "::error::spm add did not inject SwiftPM: $XCODE_PROJECT/.spm-injected.json is missing"
95+
exit 1
96+
fi
97+
if [[ -f Podfile ]] && grep -q 'use_react_native!' Podfile; then
98+
echo "::error::spm add --deintegrate left use_react_native! in the Podfile"
99+
exit 1
100+
fi
101+
echo "SwiftPM injected in place; Podfile de-integrated."
102+
- name: Build ${{ env.XCODE_SCHEME }} (${{ matrix.flavor }})
103+
shell: bash
104+
working-directory: ${{ env.APP_IOS_DIR }}
105+
run: |
106+
xcodebuild \
107+
-project "$XCODE_PROJECT" \
108+
-scheme "$XCODE_SCHEME" \
109+
-configuration "${{ matrix.flavor }}" \
110+
-sdk iphonesimulator \
111+
-destination 'generic/platform=iOS Simulator' \
112+
-derivedDataPath build/spm-e2e-dd \
113+
build
114+
- name: Check the embedded React.framework flavor
115+
shell: bash
116+
working-directory: ${{ env.APP_IOS_DIR }}
117+
run: |
118+
PRODUCTS="build/spm-e2e-dd/Build/Products/${{ matrix.flavor }}-iphonesimulator"
119+
if [[ ! -d "$PRODUCTS" ]]; then
120+
echo "Skipping flavor check: no build products directory."
121+
exit 0
122+
fi
123+
BINARY=$(find "$PRODUCTS" -maxdepth 4 -path '*.app/Frameworks/React.framework/React' -type f 2>/dev/null | head -1 || true)
124+
if [[ -z "$BINARY" ]] || ! command -v nm >/dev/null; then
125+
echo "Skipping flavor check: no embedded React.framework binary or no nm."
126+
exit 0
127+
fi
128+
COUNT=$(nm "$BINARY" | grep -c getDebugProps || true)
129+
echo "getDebugProps symbols in $BINARY: $COUNT"
130+
if [[ "${{ matrix.flavor }}" == 'Debug' && "$COUNT" -eq 0 ]]; then
131+
echo "::error::Debug build embeds a Release React.framework (expected getDebugProps symbols, found none)"
132+
exit 1
133+
fi
134+
if [[ "${{ matrix.flavor }}" == 'Release' && "$COUNT" -ne 0 ]]; then
135+
echo "::error::Release build embeds a Debug React.framework ($COUNT getDebugProps symbols, expected none)"
136+
exit 1
137+
fi
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
name: Test iOS SwiftPM - New App
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_call:
8+
9+
jobs:
10+
test:
11+
runs-on: macos-15-large
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
flavor: [Debug, Release]
16+
env:
17+
APP_IOS_DIR: private/helloworld/ios
18+
XCODE_PROJECT: HelloWorld.xcodeproj
19+
XCODE_SCHEME: HelloWorld
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v6
23+
- name: Setup xcode
24+
uses: ./.github/actions/setup-xcode
25+
- name: Setup node.js
26+
uses: ./.github/actions/setup-node
27+
- name: Run yarn install
28+
uses: ./.github/actions/yarn-install
29+
- name: Set Hermes prebuilt version
30+
shell: bash
31+
run: node ./scripts/releases/use-hermes-prebuilt.js
32+
- name: Run yarn install again, with the correct hermes version
33+
uses: ./.github/actions/yarn-install
34+
- name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`)
35+
shell: bash
36+
run: pod --version || sudo gem install cocoapods --no-document
37+
# Both flavors are needed by every matrix cell, not just the one it builds:
38+
# `spm add` stages both flavor framework trees and lets a per-configuration
39+
# build setting pick one at build time, so it validates the debug/ and
40+
# release/ artifact slots together (and `--download skip` refuses an
41+
# incomplete slot). Please don't "optimise" this down to one flavor.
42+
- name: Download ReactCore (Debug)
43+
uses: actions/download-artifact@v7
44+
with:
45+
name: ReactCoreDebug.xcframework.tar.gz
46+
path: /tmp/rc-debug
47+
- name: Download ReactCore (Release)
48+
uses: actions/download-artifact@v7
49+
with:
50+
name: ReactCoreRelease.xcframework.tar.gz
51+
path: /tmp/rc-release
52+
- name: Download ReactNativeDependencies (Debug)
53+
uses: actions/download-artifact@v7
54+
with:
55+
name: ReactNativeDependenciesDebug.xcframework.tar.gz
56+
path: /tmp/deps-debug
57+
- name: Download ReactNativeDependencies (Release)
58+
uses: actions/download-artifact@v7
59+
with:
60+
name: ReactNativeDependenciesRelease.xcframework.tar.gz
61+
path: /tmp/deps-release
62+
- name: Download React Native package
63+
uses: actions/download-artifact@v7
64+
with:
65+
name: react-native-package
66+
path: /tmp/react-native-tmp
67+
# Reinstalls private/helloworld in place against the packaged react-native
68+
# (published through the local proxy), so the app under test is what a user
69+
# would get from npm rather than the workspace source.
70+
- name: Prepare the HelloWorld application
71+
shell: bash
72+
run: node ./scripts/e2e/init-project-e2e.js --useHelloWorld --pathToLocalReactNative "/tmp/react-native-tmp/$(cat /tmp/react-native-tmp/react-native-package-version)"
73+
# The ordinary `spm download`, pointed at the XCFrameworks this run just
74+
# built rather than at the published nightly (RN_CORE_TARBALL_PATH /
75+
# RN_DEPS_TARBALL_PATH are overrides download-spm-artifacts.js already
76+
# supports). It fills both flavor slots per call and skips any slot that
77+
# already validates, so it runs twice: the first pass fills both slots
78+
# from the Release tarballs, then debug/ is dropped and refilled from the
79+
# Debug tarballs while release/ is left alone. hermes-engine is fetched
80+
# from Maven by the command itself.
81+
- name: Download XCFrameworks built in this run (Debug + Release)
82+
shell: bash
83+
working-directory: ${{ env.APP_IOS_DIR }}
84+
run: |
85+
RN_CORE_TARBALL_PATH=/tmp/rc-release/ReactCoreRelease.xcframework.tar.gz \
86+
RN_DEPS_TARBALL_PATH=/tmp/deps-release/ReactNativeDependenciesRelease.xcframework.tar.gz \
87+
npx react-native spm download --artifacts /tmp/spm-artifacts
88+
rm -rf /tmp/spm-artifacts/debug
89+
RN_CORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz \
90+
RN_DEPS_TARBALL_PATH=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz \
91+
npx react-native spm download --artifacts /tmp/spm-artifacts
92+
- name: Scaffold Package.swift manifests for community dependencies
93+
shell: bash
94+
working-directory: ${{ env.APP_IOS_DIR }}
95+
run: npx react-native spm scaffold || true
96+
- name: Convert the app to SwiftPM
97+
shell: bash
98+
working-directory: ${{ env.APP_IOS_DIR }}
99+
run: npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip
100+
- name: Assert the app is on SwiftPM
101+
shell: bash
102+
working-directory: ${{ env.APP_IOS_DIR }}
103+
run: |
104+
if [[ ! -f "$XCODE_PROJECT/.spm-injected.json" ]]; then
105+
echo "::error::spm add did not inject SwiftPM: $XCODE_PROJECT/.spm-injected.json is missing"
106+
exit 1
107+
fi
108+
if [[ -f Podfile ]] && grep -q 'use_react_native!' Podfile; then
109+
echo "::error::spm add --deintegrate left use_react_native! in the Podfile"
110+
exit 1
111+
fi
112+
echo "SwiftPM injected in place; Podfile de-integrated."
113+
- name: Build ${{ env.XCODE_SCHEME }} (${{ matrix.flavor }})
114+
shell: bash
115+
working-directory: ${{ env.APP_IOS_DIR }}
116+
run: |
117+
xcodebuild \
118+
-project "$XCODE_PROJECT" \
119+
-scheme "$XCODE_SCHEME" \
120+
-configuration "${{ matrix.flavor }}" \
121+
-sdk iphonesimulator \
122+
-destination 'generic/platform=iOS Simulator' \
123+
-derivedDataPath build/spm-e2e-dd \
124+
build
125+
- name: Check the embedded React.framework flavor
126+
shell: bash
127+
working-directory: ${{ env.APP_IOS_DIR }}
128+
run: |
129+
PRODUCTS="build/spm-e2e-dd/Build/Products/${{ matrix.flavor }}-iphonesimulator"
130+
if [[ ! -d "$PRODUCTS" ]]; then
131+
echo "Skipping flavor check: no build products directory."
132+
exit 0
133+
fi
134+
BINARY=$(find "$PRODUCTS" -maxdepth 4 -path '*.app/Frameworks/React.framework/React' -type f 2>/dev/null | head -1 || true)
135+
if [[ -z "$BINARY" ]] || ! command -v nm >/dev/null; then
136+
echo "Skipping flavor check: no embedded React.framework binary or no nm."
137+
exit 0
138+
fi
139+
COUNT=$(nm "$BINARY" | grep -c getDebugProps || true)
140+
echo "getDebugProps symbols in $BINARY: $COUNT"
141+
if [[ "${{ matrix.flavor }}" == 'Debug' && "$COUNT" -eq 0 ]]; then
142+
echo "::error::Debug build embeds a Release React.framework (expected getDebugProps symbols, found none)"
143+
exit 1
144+
fi
145+
if [[ "${{ matrix.flavor }}" == 'Release' && "$COUNT" -ne 0 ]]; then
146+
echo "::error::Release build embeds a Debug React.framework ($COUNT getDebugProps symbols, expected none)"
147+
exit 1
148+
fi

0 commit comments

Comments
 (0)