Skip to content

feat: build React Native macOS with Swift Package Manager#2815

Open
Saadnajmi wants to merge 6 commits intomicrosoft:mainfrom
Saadnajmi:feature/spm-macos-support
Open

feat: build React Native macOS with Swift Package Manager#2815
Saadnajmi wants to merge 6 commits intomicrosoft:mainfrom
Saadnajmi:feature/spm-macos-support

Conversation

@Saadnajmi
Copy link
Copy Markdown
Collaborator

@Saadnajmi Saadnajmi commented Jan 17, 2026

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 .framework via build-mac-framework.sh but does not include it in the universal .xcframework used by SPM. This means SPM consumers cannot target macOS. This PR fixes that by adding macosx as a platform in build-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:

Commit 1: SPM macOS support

  • Add .macOS(.v14) platform to Package.swift
  • Create React-RCTUIKit as its own SPM module with conditional UIKit/AppKit linking
  • Port findMatchingHermesVersion and hermesCommitAtMergeBase from Ruby to JS for Hermes version resolution
  • Add macOS platform and destination to prebuild CLI
  • Link RCTUIKit and macOS view platform headers in setup

Commit 2: Include macOS slice in Hermes xcframework

  • Add "macosx" to create_universal_framework and create_framework in build-ios-framework.sh
  • Add macosx cases to get_architecture (x86_64;arm64) and get_deployment_target
  • Make HERMES_PATH overridable via env var in build-apple-framework.sh

Commit 3: CI jobs

  • Add microsoft-build-spm.yml reusable workflow with two stages:
    • Build Hermes: Builds Hermes from source on macos-15 (includes the macOS slice)
    • Build SPM: Uses the Hermes artifact to build SPM for ios, macos, and visionos on macos-26
  • Wire into PR gate in microsoft-pr.yml
  • Add cmake-version input to microsoft-setup-toolchain to allow skipping CMake installation
  • Add visionos/visionos-simulator as supported platforms in the ios-prebuild CLI

Test plan

  • CI: Build Hermes from source with macOS slice
  • CI: SPM builds pass for iOS, macOS, visionOS
  • CocoaPods builds verified locally for iOS, macOS, visionOS

🤖 Generated with Claude Code

@Saadnajmi Saadnajmi requested a review from a team as a code owner January 17, 2026 03:48
@Saadnajmi Saadnajmi changed the title feat: Add macOS support to Swift Package Manager build system (claude code generated) feat: Add macOS support to Swift Package Manager build system Jan 17, 2026
@Saadnajmi Saadnajmi marked this pull request as draft January 17, 2026 03:56
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch from a22ab6e to 30d348a Compare March 23, 2026 03:19
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 23, 2026

⚠️ No Changeset found

Latest commit: 21f812c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Saadnajmi Saadnajmi changed the title (claude code generated) feat: Add macOS support to Swift Package Manager build system feat: Add macOS support to Swift Package Manager build system Mar 23, 2026
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch 5 times, most recently from fa8de90 to 21908c3 Compare March 23, 2026 15:53
Saadnajmi added a commit that referenced this pull request Mar 23, 2026
## 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>
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch 5 times, most recently from d1b4bcd to 01e53ee Compare March 26, 2026 18:49
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch from 01e53ee to c1cd032 Compare April 7, 2026 17:49
@Saadnajmi Saadnajmi changed the title feat: Add macOS support to Swift Package Manager build system feat: include macOS slice in Hermes xcframework Apr 7, 2026
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch from c1cd032 to 878f3eb Compare April 7, 2026 17:59
@Saadnajmi Saadnajmi changed the title feat: include macOS slice in Hermes xcframework feat: Add macOS support to Swift Package Manager build system Apr 7, 2026
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch 4 times, most recently from 5fb99f5 to 16e4eb8 Compare April 7, 2026 19:00
Comment on lines +812 to +818
// [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]
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Can we remove if we handle higher up?

@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch from 49cbeba to 79286e3 Compare April 9, 2026 00:22
const tarballPath = path.join(artifactsPath, tarballName);
hermesLog('Creating Hermes tarball from build output...');
execSync(`tar -czf "${tarballPath}" -C "${hermesDir}" destroot`, {
stdio: 'inherit',
Copy link
Copy Markdown
Member

@tido64 tido64 Apr 9, 2026

Choose a reason for hiding this comment

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

Consider creating { stdio: "inherit" } once and reuse it (I see it being created at least 4 times in this function alone).

Saadnajmi and others added 3 commits April 9, 2026 12:06
- 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>
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch 2 times, most recently from 89b00ec to f6714d5 Compare April 9, 2026 20:38
- 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>
@Saadnajmi Saadnajmi force-pushed the feature/spm-macos-support branch from f6714d5 to 21f812c Compare April 9, 2026 23:38
BUILD_TYPE=${BUILD_TYPE:-Debug}

HERMES_PATH="$CURR_SCRIPT_DIR/.."
HERMES_PATH=${HERMES_PATH:-"$CURR_SCRIPT_DIR/.."}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Add a comment why we need this change

Copy link
Copy Markdown
Member

@tido64 tido64 left a comment

Choose a reason for hiding this comment

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

I've only reviewed the code as this can't really be tested atm.

with:
name: hermes-artifacts
path: hermes/destroot
retention-days: 1
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Do we want more...?

const sourceType = await hermesSourceType(
resolvedVersion,
buildType,
allowBuildFromSource,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

// [macOS] tag here

| 'local_prebuilt_tarball'
| 'download_prebuild_tarball'
| 'download_prebuilt_nightly_tarball'
| 'build_from_hermes_commit'
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

// [macOS] tag here

Comment on lines +258 to +262
*
* @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).
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Remove the comment since it's not upstream

async function hermesSourceType(
version /*: string */,
buildType /*: BuildFlavor */,
allowBuildFromSource /*: boolean */ = false,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

// [macOS] tag here

module.exports = {
prepareHermesArtifactsAsync,
findMatchingHermesVersion, // [macOS] re-exported from macosVersionResolver.js
hermesCommitAtMergeBase, // [macOS] re-exported from macosVersionResolver.js
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Can we add comments to this file? # [macOS]

Comment on lines +32 to +33
if [[ $1 == "macosx" ]]; then
echo "$(get_mac_deployment_target)"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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"
Copy link
Copy Markdown
Collaborator Author

@Saadnajmi Saadnajmi Apr 14, 2026

Choose a reason for hiding this comment

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

comment # [macOS]

# this is used to preserve backward compatibility
function create_framework {
if [ ! -d destroot/Library/Frameworks/universal/hermes.xcframework ]; then
build_framework "macosx"
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Another # [macOS] tag

Comment on lines +371 to +372
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],
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

// [macOS] commetns to say we added RCTUIKit

Comment on lines +379 to +397
"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)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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",
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment here

Saadnajmi and others added 2 commits April 14, 2026 13:47
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants