Skip to content

Commit fd287ee

Browse files
Saadnajmiclaude
andcommitted
ci: add SPM build workflow with parallel Hermes builds and caching
Add microsoft-build-spm.yml with 4-stage pipeline: 1. resolve-hermes: find Hermes commit, check cache 2. build-hermesc + build-hermes-slice (5x parallel): build from source 3. assemble-hermes: create universal xcframework, save cache 4. build-spm (ios/macos/visionos): build SPM packages The assembled Hermes xcframework is cached by commit hash, so ~95% of CI runs skip the entire Hermes build and go straight to SPM builds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bfa7c9b commit fd287ee

File tree

3 files changed

+267
-1
lines changed

3 files changed

+267
-1
lines changed

.github/actions/microsoft-setup-toolchain/action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ inputs:
1919
xcode-developer-dir:
2020
description: Set the path for the active Xcode developer directory
2121
default: "/Applications/Xcode.app"
22+
cmake-version:
23+
description: CMake version to install. Set to 'system' to skip installation and use the runner's pre-installed cmake.
24+
default: "3.31.9"
2225
runs:
2326
using: composite
2427
steps:
2528
- name: Install cmake
29+
if: ${{ inputs.cmake-version != 'system' }}
2630
uses: jwlawson/actions-setup-cmake@v2
2731
with:
28-
cmake-version: '3.31.9'
32+
cmake-version: ${{ inputs.cmake-version }}
2933
- name: Set up Ccache
3034
id: setup-ccache
3135
if: ${{ inputs.platform == 'ios' || inputs.platform == 'macos' || inputs.platform == 'visionos' }}
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
name: Build SPM
2+
3+
on:
4+
workflow_call:
5+
6+
jobs:
7+
resolve-hermes:
8+
name: "Resolve Hermes"
9+
runs-on: macos-15
10+
timeout-minutes: 10
11+
outputs:
12+
hermes-commit: ${{ steps.resolve.outputs.hermes-commit }}
13+
cache-hit: ${{ steps.cache.outputs.cache-hit }}
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
filter: blob:none
18+
fetch-depth: 0
19+
20+
- name: Setup Xcode
21+
run: sudo xcode-select --switch /Applications/Xcode_16.2.app
22+
23+
- name: Set up Node.js
24+
uses: actions/setup-node@v4.4.0
25+
with:
26+
node-version: '22'
27+
cache: yarn
28+
registry-url: https://registry.npmjs.org
29+
30+
- name: Install npm dependencies
31+
run: yarn install
32+
33+
- name: Resolve Hermes commit at merge base
34+
id: resolve
35+
working-directory: packages/react-native
36+
run: |
37+
COMMIT=$(node -e "const {hermesCommitAtMergeBase} = require('./scripts/ios-prebuild/hermes'); console.log(hermesCommitAtMergeBase().commit);" 2>&1 | grep -E '^[0-9a-f]{40}$')
38+
echo "hermes-commit=$COMMIT" >> "$GITHUB_OUTPUT"
39+
echo "Resolved Hermes commit: $COMMIT"
40+
41+
- name: Restore Hermes cache
42+
id: cache
43+
uses: actions/cache/restore@v4
44+
with:
45+
key: hermes-v1-${{ steps.resolve.outputs.hermes-commit }}-Debug
46+
path: /tmp/hermes-destroot
47+
48+
- name: Upload cached Hermes artifacts
49+
if: steps.cache.outputs.cache-hit == 'true'
50+
uses: actions/upload-artifact@v4
51+
with:
52+
name: hermes-artifacts
53+
path: /tmp/hermes-destroot
54+
retention-days: 1
55+
56+
build-hermesc:
57+
name: "Build hermesc"
58+
if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }}
59+
needs: resolve-hermes
60+
runs-on: macos-15
61+
timeout-minutes: 30
62+
steps:
63+
- uses: actions/checkout@v4
64+
with:
65+
filter: blob:none
66+
67+
- name: Setup Xcode
68+
run: sudo xcode-select --switch /Applications/Xcode_16.2.app
69+
70+
- name: Clone Hermes
71+
run: |
72+
git clone --depth 1 https://github.com/facebook/hermes.git /tmp/hermes
73+
cd /tmp/hermes
74+
git fetch --depth 1 origin ${{ needs.resolve-hermes.outputs.hermes-commit }}
75+
git checkout ${{ needs.resolve-hermes.outputs.hermes-commit }}
76+
77+
- name: Build hermesc
78+
working-directory: /tmp/hermes
79+
env:
80+
HERMES_PATH: /tmp/hermes
81+
JSI_PATH: /tmp/hermes/API/jsi
82+
MAC_DEPLOYMENT_TARGET: '14.0'
83+
run: |
84+
source $GITHUB_WORKSPACE/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh
85+
build_host_hermesc
86+
87+
- name: Upload hermesc artifact
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: hermesc
91+
path: /tmp/hermes/build_host_hermesc
92+
retention-days: 1
93+
94+
build-hermes-slice:
95+
name: "Hermes ${{ matrix.slice }}"
96+
if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }}
97+
needs: [resolve-hermes, build-hermesc]
98+
runs-on: macos-15
99+
timeout-minutes: 45
100+
strategy:
101+
fail-fast: false
102+
matrix:
103+
slice: [iphoneos, iphonesimulator, macosx, xros, xrsimulator]
104+
steps:
105+
- uses: actions/checkout@v4
106+
with:
107+
filter: blob:none
108+
109+
- name: Setup Xcode
110+
run: sudo xcode-select --switch /Applications/Xcode_16.2.app
111+
112+
- name: Download visionOS SDK
113+
if: ${{ matrix.slice == 'xros' || matrix.slice == 'xrsimulator' }}
114+
run: |
115+
sudo xcodebuild -runFirstLaunch
116+
sudo xcrun simctl list
117+
sudo xcodebuild -downloadPlatform visionOS
118+
sudo xcodebuild -runFirstLaunch
119+
120+
- name: Clone Hermes
121+
run: |
122+
git clone --depth 1 https://github.com/facebook/hermes.git /tmp/hermes
123+
cd /tmp/hermes
124+
git fetch --depth 1 origin ${{ needs.resolve-hermes.outputs.hermes-commit }}
125+
git checkout ${{ needs.resolve-hermes.outputs.hermes-commit }}
126+
127+
- name: Download hermesc
128+
uses: actions/download-artifact@v4
129+
with:
130+
name: hermesc
131+
path: /tmp/hermes/build_host_hermesc
132+
133+
- name: Restore hermesc permissions
134+
run: chmod +x /tmp/hermes/build_host_hermesc/bin/hermesc
135+
136+
- name: Build Hermes slice (${{ matrix.slice }})
137+
working-directory: /tmp/hermes
138+
env:
139+
BUILD_TYPE: Debug
140+
HERMES_PATH: /tmp/hermes
141+
JSI_PATH: /tmp/hermes/API/jsi
142+
IOS_DEPLOYMENT_TARGET: '15.1'
143+
MAC_DEPLOYMENT_TARGET: '14.0'
144+
XROS_DEPLOYMENT_TARGET: '1.0'
145+
RELEASE_VERSION: '1000.0.0'
146+
run: |
147+
bash $GITHUB_WORKSPACE/packages/react-native/sdks/hermes-engine/utils/build-ios-framework.sh "${{ matrix.slice }}"
148+
149+
- name: Upload slice artifact
150+
uses: actions/upload-artifact@v4
151+
with:
152+
name: hermes-slice-${{ matrix.slice }}
153+
path: /tmp/hermes/destroot
154+
retention-days: 1
155+
156+
assemble-hermes:
157+
name: "Assemble Hermes xcframework"
158+
if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }}
159+
needs: [resolve-hermes, build-hermes-slice]
160+
runs-on: macos-15
161+
timeout-minutes: 15
162+
steps:
163+
- uses: actions/checkout@v4
164+
with:
165+
filter: blob:none
166+
167+
- name: Download all slice artifacts
168+
uses: actions/download-artifact@v4
169+
with:
170+
pattern: hermes-slice-*
171+
path: /tmp/slices
172+
173+
- name: Assemble destroot from slices
174+
run: |
175+
mkdir -p /tmp/hermes/destroot/Library/Frameworks
176+
for slice_dir in /tmp/slices/hermes-slice-*; do
177+
slice_name=$(basename "$slice_dir" | sed 's/hermes-slice-//')
178+
echo "Copying slice: $slice_name"
179+
cp -R "$slice_dir/Library/Frameworks/$slice_name" /tmp/hermes/destroot/Library/Frameworks/
180+
# Copy include and bin directories (identical across slices, only need one copy)
181+
if [ -d "$slice_dir/include" ] && [ ! -d /tmp/hermes/destroot/include ]; then
182+
cp -R "$slice_dir/include" /tmp/hermes/destroot/
183+
fi
184+
if [ -d "$slice_dir/bin" ]; then
185+
cp -R "$slice_dir/bin" /tmp/hermes/destroot/
186+
fi
187+
done
188+
echo "Assembled destroot contents:"
189+
ls -la /tmp/hermes/destroot/Library/Frameworks/
190+
191+
- name: Create universal xcframework
192+
working-directory: /tmp/hermes
193+
env:
194+
HERMES_PATH: /tmp/hermes
195+
run: |
196+
source $GITHUB_WORKSPACE/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh
197+
create_universal_framework "iphoneos" "iphonesimulator" "macosx" "xros" "xrsimulator"
198+
199+
- name: Save Hermes cache
200+
uses: actions/cache/save@v4
201+
with:
202+
key: hermes-v1-${{ needs.resolve-hermes.outputs.hermes-commit }}-Debug
203+
path: /tmp/hermes/destroot
204+
205+
- name: Upload Hermes artifacts
206+
uses: actions/upload-artifact@v4
207+
with:
208+
name: hermes-artifacts
209+
path: /tmp/hermes/destroot
210+
retention-days: 1
211+
212+
build-spm:
213+
name: "SPM ${{ matrix.platform }}"
214+
needs: [resolve-hermes, assemble-hermes]
215+
# Run when upstream jobs succeeded or were skipped (cache hit)
216+
if: ${{ always() && !cancelled() && !failure() }}
217+
runs-on: macos-26
218+
timeout-minutes: 60
219+
strategy:
220+
fail-fast: false
221+
matrix:
222+
platform: [ios, macos, visionos]
223+
steps:
224+
- uses: actions/checkout@v4
225+
with:
226+
filter: blob:none
227+
fetch-depth: 0
228+
229+
- name: Setup toolchain
230+
uses: ./.github/actions/microsoft-setup-toolchain
231+
with:
232+
node-version: '22'
233+
platform: ${{ matrix.platform }}
234+
235+
- name: Install npm dependencies
236+
run: yarn install
237+
238+
- name: Download Hermes artifacts
239+
uses: actions/download-artifact@v4
240+
with:
241+
name: hermes-artifacts
242+
path: packages/react-native/.build/artifacts/hermes/destroot
243+
244+
- name: Create Hermes version marker
245+
working-directory: packages/react-native
246+
run: |
247+
VERSION=$(node -p "require('./package.json').version")
248+
echo "${VERSION}-Debug" > .build/artifacts/hermes/version.txt
249+
250+
- name: Setup SPM workspace (using prebuilt Hermes)
251+
working-directory: packages/react-native
252+
run: node scripts/ios-prebuild.js -s -f Debug
253+
254+
- name: Build SPM (${{ matrix.platform }})
255+
working-directory: packages/react-native
256+
run: node scripts/ios-prebuild.js -b -f Debug -p ${{ matrix.platform }}

.github/workflows/microsoft-pr.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ jobs:
132132
permissions: {}
133133
uses: ./.github/workflows/microsoft-build-rntester.yml
134134

135+
build-spm:
136+
name: "Build SPM"
137+
permissions: {}
138+
uses: ./.github/workflows/microsoft-build-spm.yml
139+
135140
test-react-native-macos-init:
136141
name: "Test react-native-macos init"
137142
permissions: {}
@@ -156,6 +161,7 @@ jobs:
156161
- yarn-constraints
157162
- javascript-tests
158163
- build-rntester
164+
- build-spm
159165
- test-react-native-macos-init
160166
# - react-native-test-app-integration
161167
steps:

0 commit comments

Comments
 (0)