Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 256 additions & 0 deletions .github/workflows/microsoft-build-spm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
name: Build SwiftPM

on:
workflow_call:

jobs:
resolve-hermes:
name: "Resolve Hermes"
runs-on: macos-15
timeout-minutes: 10
outputs:
hermes-commit: ${{ steps.resolve.outputs.hermes-commit }}
cache-hit: ${{ steps.cache.outputs.cache-hit }}
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0

- name: Setup Xcode
run: sudo xcode-select --switch /Applications/Xcode_16.2.app

- name: Set up Node.js
uses: actions/setup-node@v4.4.0
with:
node-version: '22'
cache: yarn
registry-url: https://registry.npmjs.org

- name: Install npm dependencies
run: yarn install

- name: Resolve Hermes commit at merge base
id: resolve
working-directory: packages/react-native
run: |
COMMIT=$(node -e "const {hermesCommitAtMergeBase} = require('./scripts/ios-prebuild/macosVersionResolver'); console.log(hermesCommitAtMergeBase().commit);" 2>&1 | grep -E '^[0-9a-f]{40}$')
echo "hermes-commit=$COMMIT" >> "$GITHUB_OUTPUT"
echo "Resolved Hermes commit: $COMMIT"

- name: Restore Hermes cache
id: cache
uses: actions/cache/restore@v4
with:
key: hermes-v1-${{ steps.resolve.outputs.hermes-commit }}-Debug
path: hermes-destroot

- name: Upload cached Hermes artifacts
if: steps.cache.outputs.cache-hit == 'true'
uses: actions/upload-artifact@v4
with:
name: hermes-artifacts
path: hermes-destroot
retention-days: 30

build-hermesc:
name: "Build hermesc"
if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }}
needs: resolve-hermes
runs-on: macos-15
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
filter: blob:none

- name: Setup Xcode
run: sudo xcode-select --switch /Applications/Xcode_16.2.app

- name: Clone Hermes
uses: actions/checkout@v4
with:
repository: facebook/hermes
ref: ${{ needs.resolve-hermes.outputs.hermes-commit }}
path: hermes

- name: Build hermesc
working-directory: hermes
env:
HERMES_PATH: ${{ github.workspace }}/hermes
JSI_PATH: ${{ github.workspace }}/hermes/API/jsi
MAC_DEPLOYMENT_TARGET: '14.0'
run: |
source $GITHUB_WORKSPACE/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh
build_host_hermesc

- name: Upload hermesc artifact
uses: actions/upload-artifact@v4
with:
name: hermesc
path: hermes/build_host_hermesc
retention-days: 30

build-hermes-slice:
name: "Hermes ${{ matrix.slice }}"
if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }}
needs: [resolve-hermes, build-hermesc]
runs-on: macos-15
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
slice: [iphoneos, iphonesimulator, macosx, xros, xrsimulator]
steps:
- uses: actions/checkout@v4
with:
filter: blob:none

- name: Setup Xcode
run: sudo xcode-select --switch /Applications/Xcode_16.2.app

- name: Download visionOS SDK
if: ${{ matrix.slice == 'xros' || matrix.slice == 'xrsimulator' }}
run: |
sudo xcodebuild -runFirstLaunch
sudo xcrun simctl list
sudo xcodebuild -downloadPlatform visionOS
sudo xcodebuild -runFirstLaunch

- name: Clone Hermes
uses: actions/checkout@v4
with:
repository: facebook/hermes
ref: ${{ needs.resolve-hermes.outputs.hermes-commit }}
path: hermes

- name: Download hermesc
uses: actions/download-artifact@v4
with:
name: hermesc
path: hermes/build_host_hermesc

- name: Restore hermesc permissions
run: chmod +x ${{ github.workspace }}/hermes/build_host_hermesc/bin/hermesc

- name: Build Hermes slice (${{ matrix.slice }})
working-directory: hermes
env:
BUILD_TYPE: Debug
HERMES_PATH: ${{ github.workspace }}/hermes
JSI_PATH: ${{ github.workspace }}/hermes/API/jsi
IOS_DEPLOYMENT_TARGET: '15.1'
MAC_DEPLOYMENT_TARGET: '14.0'
XROS_DEPLOYMENT_TARGET: '1.0'
RELEASE_VERSION: '1000.0.0'
run: |
bash $GITHUB_WORKSPACE/packages/react-native/sdks/hermes-engine/utils/build-ios-framework.sh "${{ matrix.slice }}"

- name: Upload slice artifact
uses: actions/upload-artifact@v4
with:
name: hermes-slice-${{ matrix.slice }}
path: hermes/destroot
retention-days: 30

assemble-hermes:
name: "Assemble Hermes xcframework"
if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }}
needs: [resolve-hermes, build-hermes-slice]
runs-on: macos-15
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
filter: blob:none

- name: Download all slice artifacts
uses: actions/download-artifact@v4
with:
pattern: hermes-slice-*
path: /tmp/slices

- name: Assemble destroot from slices
run: |
mkdir -p ${{ github.workspace }}/hermes/destroot/Library/Frameworks
for slice_dir in /tmp/slices/hermes-slice-*; do
slice_name=$(basename "$slice_dir" | sed 's/hermes-slice-//')
echo "Copying slice: $slice_name"
cp -R "$slice_dir/Library/Frameworks/$slice_name" ${{ github.workspace }}/hermes/destroot/Library/Frameworks/
# Copy include and bin directories (identical across slices, only need one copy)
if [ -d "$slice_dir/include" ] && [ ! -d ${{ github.workspace }}/hermes/destroot/include ]; then
cp -R "$slice_dir/include" ${{ github.workspace }}/hermes/destroot/
fi
if [ -d "$slice_dir/bin" ]; then
cp -R "$slice_dir/bin" ${{ github.workspace }}/hermes/destroot/
fi
done
echo "Assembled destroot contents:"
ls -la ${{ github.workspace }}/hermes/destroot/Library/Frameworks/

- name: Create universal xcframework
working-directory: hermes
env:
HERMES_PATH: ${{ github.workspace }}/hermes
run: |
source $GITHUB_WORKSPACE/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh
create_universal_framework "iphoneos" "iphonesimulator" "macosx" "xros" "xrsimulator"

- name: Save Hermes cache
uses: actions/cache/save@v4
with:
key: hermes-v1-${{ needs.resolve-hermes.outputs.hermes-commit }}-Debug
path: hermes/destroot

- name: Upload Hermes artifacts
uses: actions/upload-artifact@v4
with:
name: hermes-artifacts
path: hermes/destroot
retention-days: 30

build-spm:
name: "SPM ${{ matrix.platform }}"
needs: [resolve-hermes, assemble-hermes]
# Run when upstream jobs succeeded or were skipped (cache hit)
if: ${{ always() && !cancelled() && !failure() }}
runs-on: macos-26
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
platform: [ios, macos, visionos]
steps:
- uses: actions/checkout@v4
with:
filter: blob:none
fetch-depth: 0

- name: Setup toolchain
uses: ./.github/actions/microsoft-setup-toolchain
with:
node-version: '22'
platform: ${{ matrix.platform }}

- name: Install npm dependencies
run: yarn install

- name: Download Hermes artifacts
uses: actions/download-artifact@v4
with:
name: hermes-artifacts
path: packages/react-native/.build/artifacts/hermes/destroot

- name: Create Hermes version marker
working-directory: packages/react-native
run: |
VERSION=$(node -p "require('./package.json').version")
echo "${VERSION}-Debug" > .build/artifacts/hermes/version.txt

- name: Setup SPM workspace (using prebuilt Hermes)
working-directory: packages/react-native
run: node scripts/ios-prebuild.js -s -f Debug

- name: Build SPM (${{ matrix.platform }})
working-directory: packages/react-native
run: node scripts/ios-prebuild.js -b -f Debug -p ${{ matrix.platform }}
6 changes: 6 additions & 0 deletions .github/workflows/microsoft-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ jobs:
permissions: {}
uses: ./.github/workflows/microsoft-build-rntester.yml

build-spm:
name: "Build SPM"
permissions: {}
uses: ./.github/workflows/microsoft-build-spm.yml

test-react-native-macos-init:
name: "Test react-native-macos init"
permissions: {}
Expand All @@ -156,6 +161,7 @@ jobs:
- yarn-constraints
- javascript-tests
- build-rntester
- build-spm
- test-react-native-macos-init
# - react-native-test-app-integration
steps:
Expand Down
37 changes: 29 additions & 8 deletions packages/react-native/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ let reactJsErrorHandler = RNTarget(
let reactGraphicsApple = RNTarget(
name: .reactGraphicsApple,
path: "ReactCommon/react/renderer/graphics/platform/ios",
linkedFrameworks: ["UIKit", "CoreGraphics"],
linkedFrameworks: ["CoreGraphics"],
// [macOS] UIKit/AppKit linked conditionally for cross-compilation
platformLinkerSettings: [
.linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])),
.linkedFramework("AppKit", .when(platforms: [.macOS])),
],
dependencies: [.reactDebug, .jsi, .reactUtils, .reactNativeDependencies]
)

Expand Down Expand Up @@ -363,12 +368,26 @@ let reactCore = RNTarget(
"ReactCommon/react/runtime/platform/ios", // explicit header search path to break circular dependency. RCTHost imports `RCTDefines.h` in ReactCore, ReacCore needs to import RCTHost
],
linkedFrameworks: ["CoreServices"],
// [macOS]
platformLinkerSettings: [
.linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])),
.linkedFramework("AppKit", .when(platforms: [.macOS])),
],
excludedPaths: ["Fabric", "Tests", "Resources", "Runtime/RCTJscInstanceFactory.mm", "I18n/strings", "CxxBridge/JSCExecutorFactory.mm", "CoreModules"],
dependencies: [.reactNativeDependencies, .reactCxxReact, .reactPerfLogger, .jsi, .reactJsiExecutor, .reactUtils, .reactFeatureFlags, .reactRuntimeScheduler, .yoga, .reactJsInspector, .reactJsiTooling, .rctDeprecation, .reactCoreRCTWebsocket, .reactRCTImage, .reactTurboModuleCore, .reactRCTText, .reactRCTBlob, .reactRCTAnimation, .reactRCTNetwork, .reactFabric, .hermesPrebuilt],
sources: [".", "Runtime/RCTHermesInstanceFactory.mm"]
)

/// React-Fabric.podspec
// [macOS
#if os(macOS)
let reactFabricViewPlatformSources = ["components/view/platform/macos"]
let reactFabricViewPlatformExcludes = ["components/view/platform/cxx"]
#else
let reactFabricViewPlatformExcludes = ["components/view/platform/macos"]
let reactFabricViewPlatformSources = ["components/view/platform/cxx"]
#endif
// macOS]
let reactFabric = RNTarget(
name: .reactFabric,
path: "ReactCommon/react/renderer",
Expand All @@ -379,7 +398,7 @@ let reactFabric = RNTarget(
"components/view/tests",
"components/view/platform/android",
"components/view/platform/windows",
"components/view/platform/macos",
// "components/view/platform/macos", // [macOS]
"components/scrollview/tests",
"components/scrollview/platform/android",
"mounting/tests",
Expand All @@ -402,9 +421,9 @@ let reactFabric = RNTarget(
"components/unimplementedview",
"components/virtualview",
"components/root/tests",
],
] + reactFabricViewPlatformExcludes, // [macOS]
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga],
sources: ["animations", "attributedstring", "core", "componentregistry", "componentregistry/native", "components/root", "components/view", "components/view/platform/cxx", "components/scrollview", "components/scrollview/platform/cxx", "components/legacyviewmanagerinterop", "dom", "scheduler", "mounting", "observers/events", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency"]
sources: ["animations", "attributedstring", "core", "componentregistry", "componentregistry/native", "components/root", "components/view", "components/scrollview", "components/scrollview/platform/cxx", "components/legacyviewmanagerinterop", "dom", "scheduler", "mounting", "observers/events", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency"] + reactFabricViewPlatformSources // [macOS]
)

/// React-RCTFabric.podspec
Expand All @@ -424,7 +443,7 @@ let reactFabricComponents = RNTarget(
"components/view/platform/android",
"components/view/platform/windows",
"components/view/platform/macos",
"components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm",
// [macOS] Both switch files included; TARGET_OS_OSX guards select the correct one.
"components/textinput/platform/android",
"components/text/platform/android",
"components/textinput/platform/macos",
Expand Down Expand Up @@ -591,7 +610,7 @@ let targets = [

let package = Package(
name: react,
platforms: [.iOS(.v15), .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)],
platforms: [.iOS(.v15), .macOS(.v14) /* [macOS] */, .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)],
products: [
.library(
name: react,
Expand Down Expand Up @@ -632,14 +651,16 @@ class BinaryTarget: BaseTarget {

class RNTarget: BaseTarget {
let linkedFrameworks: [String]
let platformLinkerSettings: [LinkerSetting] // [macOS]
let excludedPaths: [String]
let dependencies: [String]
let sources: [String]?
let publicHeadersPath: String?
let defines: [CXXSetting]

init(name: String, path: String, searchPaths: [String] = [], linkedFrameworks: [String] = [], excludedPaths: [String] = [], dependencies: [String] = [], sources: [String]? = nil, publicHeadersPath: String? = ".", defines: [CXXSetting] = []) {
init(name: String, path: String, searchPaths: [String] = [], linkedFrameworks: [String] = [], platformLinkerSettings: [LinkerSetting] = [], excludedPaths: [String] = [], dependencies: [String] = [], sources: [String]? = nil, publicHeadersPath: String? = ".", defines: [CXXSetting] = []) {
self.linkedFrameworks = linkedFrameworks
self.platformLinkerSettings = platformLinkerSettings
self.excludedPaths = excludedPaths
self.dependencies = dependencies
self.sources = sources
Expand Down Expand Up @@ -675,7 +696,7 @@ class RNTarget: BaseTarget {
override func target(targets: [BaseTarget]) -> Target {
let searchPaths: [String] = self.headerSearchPaths(targets: targets)

let linkerSettings = self.linkedFrameworks.reduce([]) { $0 + [LinkerSetting.linkedFramework($1)] }
let linkerSettings = self.linkedFrameworks.reduce([]) { $0 + [LinkerSetting.linkedFramework($1)] } + self.platformLinkerSettings // [macOS]

return Target.reactNativeTarget(
name: self.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/

#include <TargetConditionals.h> // [macOS]
#if !TARGET_OS_OSX // [macOS]

#import <React/RCTUtils.h>
#import <UIKit/UIKit.h>
#include "AppleSwitchShadowNode.h"
Expand All @@ -26,3 +29,5 @@
}

} // namespace facebook::react

#endif // !TARGET_OS_OSX [macOS]
Loading
Loading