feat: build React Native macOS with Swift Package Manager#2815
feat: build React Native macOS with Swift Package Manager#2815Saadnajmi wants to merge 6 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
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
| 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 creating { stdio: "inherit" } once and reuse it (I see it being created at least 4 times in this function alone).
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
tido64
left a comment
There was a problem hiding this comment.
I've only reviewed the code as this can't really be tested atm.
| with: | ||
| name: hermes-artifacts | ||
| path: hermes/destroot | ||
| retention-days: 1 |
There was a problem hiding this comment.
Do we want more...?
| const sourceType = await hermesSourceType( | ||
| resolvedVersion, | ||
| buildType, | ||
| allowBuildFromSource, |
There was a problem hiding this comment.
// [macOS] tag here
| | 'local_prebuilt_tarball' | ||
| | 'download_prebuild_tarball' | ||
| | 'download_prebuilt_nightly_tarball' | ||
| | 'build_from_hermes_commit' |
There was a problem hiding this comment.
// [macOS] tag here
| * | ||
| * @param version - The resolved version string | ||
| * @param buildType - Debug or Release | ||
| * @param allowBuildFromSource - If true (macOS main branch), fall back to BUILD_FROM_HERMES_COMMIT | ||
| * when no prebuilt artifacts exist. If false, fall back to nightly download (original behavior). |
There was a problem hiding this comment.
Remove the comment since it's not upstream
| async function hermesSourceType( | ||
| version /*: string */, | ||
| buildType /*: BuildFlavor */, | ||
| allowBuildFromSource /*: boolean */ = false, |
There was a problem hiding this comment.
Add a // [macOS] tag here
| case HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL: | ||
| return downloadPrebuiltNightlyTarball(version, buildType, artifactsPath); | ||
| case HermesEngineSourceTypes.BUILD_FROM_HERMES_COMMIT: // [macOS] | ||
| return buildFromHermesCommit(version, buildType, artifactsPath); |
There was a problem hiding this comment.
// [macOS] tag here
| module.exports = { | ||
| prepareHermesArtifactsAsync, | ||
| findMatchingHermesVersion, // [macOS] re-exported from macosVersionResolver.js | ||
| hermesCommitAtMergeBase, // [macOS] re-exported from macosVersionResolver.js |
There was a problem hiding this comment.
Why do we need the re-exports? should we just import from `macosVesionResolver elsewhere?
| elif [[ $1 == "appletvsimulator" ]]; then | ||
| echo "x86_64;arm64" | ||
| elif [[ $1 == "catalyst" ]]; then | ||
| elif [[ $1 == "catalyst" || $1 == "macosx" ]]; then |
There was a problem hiding this comment.
Can we add comments to this file? # [macOS]
| if [[ $1 == "macosx" ]]; then | ||
| echo "$(get_mac_deployment_target)" |
There was a problem hiding this comment.
Move to bottom of elsif so we don't need to edit if [[ $1 == "xros" || $1 == "xrsimulator" ]]; then, and then add macOS comments
| function build_universal_framework { | ||
| if [ ! -d destroot/Library/Frameworks/universal/hermes.xcframework ]; then | ||
| create_universal_framework "iphoneos" "iphonesimulator" "catalyst" "xros" "xrsimulator" "appletvos" "appletvsimulator" | ||
| create_universal_framework "macosx" "iphoneos" "iphonesimulator" "catalyst" "xros" "xrsimulator" "appletvos" "appletvsimulator" |
There was a problem hiding this comment.
comment # [macOS]
| # this is used to preserve backward compatibility | ||
| function create_framework { | ||
| if [ ! -d destroot/Library/Frameworks/universal/hermes.xcframework ]; then | ||
| build_framework "macosx" |
There was a problem hiding this comment.
Another # [macOS] tag
packages/react-native/Package.swift
Outdated
| excludedPaths: ["Fabric", "Tests", "Resources", "Runtime/RCTJscInstanceFactory.mm", "I18n/strings", "CxxBridge/JSCExecutorFactory.mm", "CoreModules", "RCTUIKit"], | ||
| dependencies: [.reactNativeDependencies, .reactCxxReact, .reactPerfLogger, .jsi, .reactJsiExecutor, .reactUtils, .reactFeatureFlags, .reactRuntimeScheduler, .yoga, .reactJsInspector, .reactJsiTooling, .rctDeprecation, .reactCoreRCTWebsocket, .reactRCTImage, .reactTurboModuleCore, .reactRCTText, .reactRCTBlob, .reactRCTAnimation, .reactRCTNetwork, .reactFabric, .hermesPrebuilt, .reactRCTUIKit], |
There was a problem hiding this comment.
// [macOS] commetns to say we added RCTUIKit
packages/react-native/Package.swift
Outdated
| "components/view/platform/macos", | ||
| // "components/view/platform/cxx", // [macOS] excluded on macOS, included on iOS/visionOS (see reactFabricViewPlatformExcludes) | ||
| // "components/view/platform/macos", // [macOS] excluded on iOS/visionOS, included on macOS (see reactFabricViewPlatformExcludes) |
There was a problem hiding this comment.
We only excluded macOS, why did we add a commented out line as if we are excluding cxx too?
| // "components/view/platform/macos", // [macOS] not needed here — sources don't include components/view | ||
| "components/textinput/platform/android", | ||
| "components/text/platform/android", | ||
| "components/textinput/platform/macos", |
- Add missing [macOS] diff tags across Package.swift, hermes.js, and
shell build scripts per the diff tag guide
- Move macosx case to bottom of get_deployment_target elif chain to
minimize upstream diff in build-ios-framework.sh
- Remove hermes.js re-exports; import directly from macosVersionResolver
- Extract {stdio: 'inherit'} to const in buildFromHermesCommit
- Add HERMES_PATH override comment in build-apple-framework.sh
- Increase CI artifact retention-days from 1 to 30
- Add commented-out lines for removed upstream excludes in Package.swift
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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