feat: build React Native macOS with Swift Package Manager#2815
feat: build React Native macOS with Swift Package Manager#2815Saadnajmi wants to merge 4 commits intomicrosoft:mainfrom
Conversation
a22ab6e to
30d348a
Compare
|
fa8de90 to
21908c3
Compare
## Summary Fixes compilation errors that block macOS SPM builds, helping unblock #2815. - **RCTLinkingManager**: combined iOS and macOS implementations into a single file using `#if TARGET_OS_OSX` guards. Added missing `NativeLinkingManagerSpec` conformance (`openSettings`, `sendIntent`, `getTurboModule`), removed unused import, deleted the `macos/` overlay directory, and cleaned up the podspec - **RCTCursor.m**: replaced `Foundation.h` + conditional `AppKit.h` with `RCTUIKit` umbrella header - **RCTViewComponentView.mm**: removed duplicate `cursor` property check introduced during merge ## Test plan - [x] macOS SPM build passes (verified on `feature/spm-macos-support` — zero errors from these files) - [ ] Verify iOS build is not regressed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
d1b4bcd to
01e53ee
Compare
01e53ee to
c1cd032
Compare
c1cd032 to
878f3eb
Compare
5fb99f5 to
16e4eb8
Compare
242d8d5 to
a0e768b
Compare
39c472a to
49cbeba
Compare
| // [macOS] Map macOS version to upstream RN version for artifact lookup. | ||
| // For stable branches, peerDependencies maps to the upstream version. | ||
| // For the main branch (1000.0.0), fall back to the latest stable RN release. | ||
| if (!process.env.RN_DEP_VERSION) { | ||
| const packageJsonPath = path.resolve(__dirname, '..', '..', 'package.json'); | ||
| const mappedVersion = findMatchingHermesVersion(packageJsonPath); | ||
| if (mappedVersion != null) { | ||
| dependencyLog( | ||
| `Using mapped upstream version for ReactNativeDependencies lookup: ${mappedVersion}`, | ||
| ); | ||
| resolvedVersion = mappedVersion; | ||
| } else if (resolvedVersion === '1000.0.0') { | ||
| const latestStable = await getLatestStableVersionFromNPM(); | ||
| dependencyLog( | ||
| `Main branch detected. Using latest stable RN version for ReactNativeDependencies: ${latestStable}`, | ||
| ); | ||
| resolvedVersion = latestStable; | ||
| } | ||
| } | ||
| // macOS] |
There was a problem hiding this comment.
Shouldn't we fall back to the version from the merge base?
| @@ -60,7 +60,12 @@ function get_mac_deployment_target { | |||
| function build_host_hermesc { | |||
| echo "Building hermesc" | |||
| pushd "$HERMES_PATH" > /dev/null || exit 1 | |||
| cmake -S . -B build_host_hermesc -DJSI_DIR="$JSI_PATH" | |||
| # Set a deployment target to prevent -Werror=unguarded-availability-new | |||
| # failures in LLVM's check_symbol_exists tests (macOS SDK headers contain | |||
| # availability annotations that error without a deployment target). | |||
| cmake -S . -B build_host_hermesc \ | |||
| -DJSI_DIR="$JSI_PATH" \ | |||
| -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING="${MAC_DEPLOYMENT_TARGET:-10.15}" | |||
There was a problem hiding this comment.
Why are these changes necessary? Can we remove them now?
| name: .reactGraphicsApple, | ||
| path: "ReactCommon/react/renderer/graphics/platform/ios", | ||
| linkedFrameworks: ["UIKit", "CoreGraphics"], | ||
| linkedFrameworks: ["CoreGraphics"], |
There was a problem hiding this comment.
Can we do an #if os() check here, and then if we do, do we still need conditional linkers settings below?
| "components/view/platform/macos", | ||
| "components/textinput/platform/android", | ||
| "components/text/platform/android", | ||
| "components/textinput/platform/macos", | ||
| "components/text/tests", | ||
| "textlayoutmanager/tests", | ||
| "textlayoutmanager/platform/android", | ||
| "textlayoutmanager/platform/cxx", | ||
| "textlayoutmanager/platform/windows", | ||
| "textlayoutmanager/platform/macos", |
There was a problem hiding this comment.
similar comment here
packages/react-native/Package.swift
Outdated
| let package = Package( | ||
| name: react, | ||
| platforms: [.iOS(.v15), .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)], | ||
| platforms: [.iOS(.v15), .macOS(.v14), .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)], |
packages/react-native/Package.swift
Outdated
| // [macOS] Platform-specific framework linking for targets that need UIKit (iOS/visionOS) vs AppKit (macOS) | ||
| var conditionalLinkerSettings: [LinkerSetting] = linkerSettings | ||
| if name == "React-graphics-Apple" || name == "React-RCTUIKit" { | ||
| conditionalLinkerSettings.append(.linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS]))) | ||
| conditionalLinkerSettings.append(.linkedFramework("AppKit", .when(platforms: [.macOS]))) | ||
| } | ||
| // macOS] |
There was a problem hiding this comment.
Can we remove if we handle higher up?
49cbeba to
79286e3
Compare
| git clone --depth 1 https://github.com/facebook/hermes.git /tmp/hermes | ||
| cd /tmp/hermes | ||
| git fetch --depth 1 origin ${{ needs.resolve-hermes.outputs.hermes-commit }} | ||
| git checkout ${{ needs.resolve-hermes.outputs.hermes-commit }} |
There was a problem hiding this comment.
Doing a shallow clone and then fetching another commit is really expensive. You should use actions/checkout instead as it handles checking out a specific commit very efficiently.
| @@ -0,0 +1,256 @@ | |||
| name: Build SPM | |||
There was a problem hiding this comment.
Let's avoid using acronyms when we have space:
| name: Build SPM | |
| name: Build SwiftPM |
| const {computeNightlyTarballURL, createLogger} = require('./utils'); | ||
| const {execSync} = require('child_process'); | ||
| const fs = require('fs'); | ||
| const os = require('os'); |
There was a problem hiding this comment.
| const os = require('os'); | |
| const os = require('os'); // [macos] |
| execSync(`git clone --depth 1 ${HERMES_GITHUB_URL} "${hermesDir}"`, { | ||
| stdio: 'inherit', | ||
| timeout: 300000, | ||
| }); | ||
| execSync(`git -C "${hermesDir}" fetch --depth 1 origin ${commit}`, { | ||
| stdio: 'inherit', | ||
| timeout: 120000, | ||
| }); | ||
| execSync(`git -C "${hermesDir}" checkout ${commit}`, { | ||
| stdio: 'inherit', | ||
| }); |
There was a problem hiding this comment.
Like I said above, you should not do a shallow clone followed by another fetch. This is the most optimal way to checkout a specific commit:
git init "${hermesDir}"
cd "${hermesDir}"
git remote add origin ${HERMES_GITHUB_URL}
git fetch --no-tags --depth 1 origin +${commit}:refs/remotes/origin/main
git checkout main| const tarballPath = path.join(artifactsPath, tarballName); | ||
| hermesLog('Creating Hermes tarball from build output...'); | ||
| execSync(`tar -czf "${tarballPath}" -C "${hermesDir}" destroot`, { | ||
| stdio: 'inherit', |
There was a problem hiding this comment.
Consider reusing this option object elsewhere.
packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js
Outdated
Show resolved
Hide resolved
- Add macOS and visionOS platforms to ios-prebuild CLI and type definitions - Build Hermes from source at the merge base with facebook/react-native when no prebuilt artifacts are available (main branch / 1000.0.0) - Fix host hermesc cmake build by setting CMAKE_OSX_DEPLOYMENT_TARGET to prevent -Werror=unguarded-availability-new failures in LLVM config checks - Map ReactNativeDependencies version to upstream RN via peerDependencies, with fallback to merge base version or latest stable release for main branch - Conditionally include macOS-specific platform view sources in Package.swift using #if os(macOS) to avoid compiling macOS C++ on iOS/visionOS - Platform-conditional UIKit/AppKit framework linking via platformLinkerSettings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make HERMES_PATH overridable via environment variable so CI can point to a separately cloned Hermes repo - Add CMAKE_OSX_DEPLOYMENT_TARGET to build_host_hermesc to fix -Werror=unguarded-availability-new failures in LLVM cmake checks - Add macOS deployment target support via get_mac_deployment_target Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
89b00ec to
f6714d5
Compare
- Rename workflow to "Build SwiftPM" (tido64) - Replace shallow clone + fetch with actions/checkout for Hermes (tido64) - Use efficient git init + fetch pattern in buildFromHermesCommit (tido64) - Extract reusable build env object in hermes.js (tido64) - Move macOS version resolution to fork-only macosVersionResolver.js (tido64) - Replace platformLinkerSettings with #if os(macOS) in Package.swift (Saad) - Revert CMAKE_OSX_DEPLOYMENT_TARGET from build-apple-framework.sh (Saad) - Add // [macOS] tag to os require in hermes.js (tido64) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
f6714d5 to
21f812c
Compare
packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js
Dismissed
Show dismissed
Hide dismissed
| BUILD_TYPE=${BUILD_TYPE:-Debug} | ||
|
|
||
| HERMES_PATH="$CURR_SCRIPT_DIR/.." | ||
| HERMES_PATH=${HERMES_PATH:-"$CURR_SCRIPT_DIR/.."} |
There was a problem hiding this comment.
Add a comment why we need this change
Summary
Extends the Hermes build scripts to include macOS slices in the universal xcframework, and adds macOS support to the Swift Package Manager build system.
Currently, Hermes builds a standalone macOS
.frameworkviabuild-mac-framework.shbut does not include it in the universal.xcframeworkused by SPM. This means SPM consumers cannot target macOS. This PR fixes that by addingmacosxas a platform inbuild-ios-framework.sh, so the macOS slice is built alongside iOS, visionOS, tvOS, and catalyst — and included in the universal xcframework.This mirrors the upstream Hermes changes:
static_h— full consolidation)Commit 1: SPM macOS support
.macOS(.v14)platform toPackage.swiftReact-RCTUIKitas its own SPM module with conditional UIKit/AppKit linkingfindMatchingHermesVersionandhermesCommitAtMergeBasefrom Ruby to JS for Hermes version resolutionCommit 2: Include macOS slice in Hermes xcframework
"macosx"tocreate_universal_frameworkandcreate_frameworkinbuild-ios-framework.shmacosxcases toget_architecture(x86_64;arm64) andget_deployment_targetHERMES_PATHoverridable via env var inbuild-apple-framework.shCommit 3: CI jobs
microsoft-build-spm.ymlreusable workflow with two stages:macos-15(includes the macOS slice)macos-26microsoft-pr.ymlcmake-versioninput tomicrosoft-setup-toolchainto allow skipping CMake installationTest plan
🤖 Generated with Claude Code