From 99bf47d9d721cb4a91b35e7cde012425749c0b41 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 10 Apr 2026 18:06:35 -0700 Subject: [PATCH 1/4] Backport SPM macOS support to 0.81-stable Adapts the SPM macOS support from the main branch for the 0.81-stable branch. Key difference from main: RCTUIKit is part of React-Core on 0.81 (not a separate module), so platformLinkerSettings for UIKit/AppKit go directly on the reactCore target. Changes: - Add macOS/visionOS platform support to ios-prebuild CLI and types - Add macOS version resolution via macosVersionResolver.js (fork-only) - Add Hermes build-from-source fallback for main branch builds - Add macOS platform to Package.swift with platform-conditional linking - Add macOS view platform sources/excludes to reactFabric - Add macOS view platform header links in setup.js - Add Hermes CI workflow for building macOS slices - Make HERMES_PATH overridable in build-apple-framework.sh - Add macOS slice to build-ios-framework.sh Co-Authored-By: Claude Opus 4.6 --- .github/workflows/microsoft-build-spm.yml | 256 ++++++++++++++++++ packages/react-native/Package.swift | 38 ++- .../react-native/scripts/ios-prebuild/cli.js | 4 + .../scripts/ios-prebuild/hermes.js | 163 ++++++++++- .../ios-prebuild/macosVersionResolver.js | 199 ++++++++++++++ .../ios-prebuild/reactNativeDependencies.js | 34 +++ .../scripts/ios-prebuild/setup.js | 10 + .../scripts/ios-prebuild/types.js | 8 +- .../utils/build-apple-framework.sh | 2 +- .../utils/build-ios-framework.sh | 11 +- 10 files changed, 710 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/microsoft-build-spm.yml create mode 100644 packages/react-native/scripts/ios-prebuild/macosVersionResolver.js diff --git a/.github/workflows/microsoft-build-spm.yml b/.github/workflows/microsoft-build-spm.yml new file mode 100644 index 000000000000..46a6c25a62c8 --- /dev/null +++ b/.github/workflows/microsoft-build-spm.yml @@ -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/hermes'); 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: 1 + + 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: 1 + + 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: 1 + + 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: 1 + + 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 }} diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index c748b549463b..2742217ae805 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -249,7 +249,13 @@ let reactJsErrorHandler = RNTarget( let reactGraphicsApple = RNTarget( name: .reactGraphicsApple, path: "ReactCommon/react/renderer/graphics/platform/ios", - linkedFrameworks: ["UIKit", "CoreGraphics"], + linkedFrameworks: ["CoreGraphics"], + // [macOS] Package.swift evaluates on the host (macOS), not the target, so #if os(macOS) doesn't work for cross-compilation. + // not the target. Use .when(platforms:) for cross-compilation support. + platformLinkerSettings: [ + .linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])), + .linkedFramework("AppKit", .when(platforms: [.macOS])), + ], dependencies: [.reactDebug, .jsi, .reactUtils, .reactNativeDependencies] ) @@ -363,12 +369,27 @@ 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] RCTUIKit is part of React-Core on 0.81 — add platform-conditional UIKit/AppKit linking + platformLinkerSettings: [ + .linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])), + .linkedFramework("AppKit", .when(platforms: [.macOS])), + ], + // 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: on macOS, use platform/macos view sources instead of platform/cxx +#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", @@ -379,7 +400,8 @@ let reactFabric = RNTarget( "components/view/tests", "components/view/platform/android", "components/view/platform/windows", - "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) "components/scrollview/tests", "components/scrollview/platform/android", "mounting/tests", @@ -402,9 +424,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 @@ -591,7 +613,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, @@ -632,14 +654,16 @@ class BinaryTarget: BaseTarget { class RNTarget: BaseTarget { let linkedFrameworks: [String] + let platformLinkerSettings: [LinkerSetting] // [macOS] Platform-conditional framework linking (e.g. UIKit vs AppKit) 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 @@ -675,7 +699,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, diff --git a/packages/react-native/scripts/ios-prebuild/cli.js b/packages/react-native/scripts/ios-prebuild/cli.js index 01301c800af7..c2f3586f46f5 100644 --- a/packages/react-native/scripts/ios-prebuild/cli.js +++ b/packages/react-native/scripts/ios-prebuild/cli.js @@ -18,6 +18,8 @@ const platforms /*: $ReadOnlyArray */ = [ 'ios', 'ios-simulator', 'mac-catalyst', + 'macos', // [macOS] + 'visionos', // [macOS] ]; // CI can't use commas in cache keys, so 'macOS,variant=Mac Catalyst' was creating troubles @@ -26,6 +28,8 @@ const platformToDestination /*: $ReadOnly<{|[Platform]: Destination|}> */ = { ios: 'iOS', 'ios-simulator': 'iOS Simulator', 'mac-catalyst': 'macOS,variant=Mac Catalyst', + macos: 'macOS', // [macOS] + visionos: 'visionOS', // [macOS] }; const cli = yargs diff --git a/packages/react-native/scripts/ios-prebuild/hermes.js b/packages/react-native/scripts/ios-prebuild/hermes.js index e5e67bfcb0a6..6c3e9859a32a 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -8,9 +8,14 @@ * @format */ +const { + findMatchingHermesVersion, + hermesCommitAtMergeBase, +} = require('./macosVersionResolver'); // [macOS] const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); +const os = require('os'); // [macOS] const path = require('path'); const stream = require('stream'); const {promisify} = require('util'); @@ -56,6 +61,29 @@ async function prepareHermesArtifactsAsync( // Resolve the version from the environment variable or use the default version let resolvedVersion = process.env.HERMES_VERSION ?? version; + // [macOS] Map macOS version to upstream RN version for artifact lookup. + // If no mapped version is found (main branch / 1000.0.0), allowBuildFromSource + // enables the fallback to hermesCommitAtMergeBase() when no prebuilt artifacts exist. + let allowBuildFromSource = false; + if (!process.env.HERMES_VERSION) { + const packageJsonPath = path.resolve( + __dirname, + '..', + '..', + 'package.json', + ); + const mappedVersion = findMatchingHermesVersion(packageJsonPath); + if (mappedVersion != null) { + hermesLog( + `Using mapped upstream version for Hermes lookup: ${mappedVersion}`, + ); + resolvedVersion = mappedVersion; + } else { + allowBuildFromSource = true; + } + } + // macOS] + if (resolvedVersion === 'nightly') { hermesLog('Using latest nightly tarball'); const hermesVersion = await getNightlyVersionFromNPM(); @@ -74,7 +102,11 @@ async function prepareHermesArtifactsAsync( return artifactsPath; } - const sourceType = await hermesSourceType(resolvedVersion, buildType); + const sourceType = await hermesSourceType( + resolvedVersion, + buildType, + allowBuildFromSource, + ); localPath = await resolveSourceFromSourceType( sourceType, resolvedVersion, @@ -124,12 +156,14 @@ type HermesEngineSourceType = | 'local_prebuilt_tarball' | 'download_prebuild_tarball' | 'download_prebuilt_nightly_tarball' + | 'build_from_hermes_commit' */ const HermesEngineSourceTypes = { LOCAL_PREBUILT_TARBALL: 'local_prebuilt_tarball', DOWNLOAD_PREBUILD_TARBALL: 'download_prebuild_tarball', DOWNLOAD_PREBUILT_NIGHTLY_TARBALL: 'download_prebuilt_nightly_tarball', + BUILD_FROM_HERMES_COMMIT: 'build_from_hermes_commit', // [macOS] } /*:: as const */; /** @@ -224,10 +258,16 @@ async function hermesArtifactExists( /** * Determines the source type for Hermes based on availability + * + * @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). */ async function hermesSourceType( version /*: string */, buildType /*: BuildFlavor */, + allowBuildFromSource /*: boolean */ = false, ) /*: Promise */ { if (hermesEngineTarballEnvvarDefined()) { hermesLog('Using local prebuild tarball'); @@ -247,6 +287,16 @@ async function hermesSourceType( return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL; } + // [macOS] When on the macOS main branch (no mapped version, no explicit HERMES_VERSION), + // fall back to resolving the Hermes commit at the merge base with facebook/react-native. + if (allowBuildFromSource) { + hermesLog( + 'No prebuilt Hermes artifact found. Will attempt to resolve from merge base with facebook/react-native.', + ); + return HermesEngineSourceTypes.BUILD_FROM_HERMES_COMMIT; + } + // macOS] + hermesLog( 'Using download prebuild nightly tarball - this is a fallback and might not work.', ); @@ -266,6 +316,8 @@ async function resolveSourceFromSourceType( return downloadPrebuildTarball(version, buildType, artifactsPath); case HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL: return downloadPrebuiltNightlyTarball(version, buildType, artifactsPath); + case HermesEngineSourceTypes.BUILD_FROM_HERMES_COMMIT: // [macOS] + return buildFromHermesCommit(version, buildType, artifactsPath); default: abort( `[Hermes] Unsupported or invalid source type provided: ${sourceType}`, @@ -372,6 +424,113 @@ async function downloadHermesTarball( return destPath; } +// [macOS +/** + * Handles the case where no prebuilt Hermes artifacts are available. + * Determines the Hermes commit at the merge base with facebook/react-native + * and provides actionable guidance for building Hermes. + */ +async function buildFromHermesCommit( + version /*: string */, + buildType /*: BuildFlavor */, + artifactsPath /*: string */, +) /*: Promise */ { + const {commit, timestamp} = hermesCommitAtMergeBase(); + hermesLog( + `Building Hermes from source at commit ${commit} (merge base timestamp: ${timestamp})`, + ); + + const HERMES_GITHUB_URL = 'https://github.com/facebook/hermes.git'; + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-build-')); + const hermesDir = path.join(tmpDir, 'hermes'); + + try { + // Clone Hermes at the identified commit using the most efficient + // single-fetch pattern (see https://github.com/actions/checkout) + hermesLog(`Cloning Hermes at commit ${commit}...`); + execSync(`git init "${hermesDir}"`, {stdio: 'inherit'}); + execSync(`git -C "${hermesDir}" remote add origin ${HERMES_GITHUB_URL}`, { + stdio: 'inherit', + }); + execSync( + `git -C "${hermesDir}" fetch --no-tags --depth 1 origin +${commit}:refs/remotes/origin/main`, + {stdio: 'inherit', timeout: 300000}, + ); + execSync(`git -C "${hermesDir}" checkout main`, {stdio: 'inherit'}); + + const reactNativeRoot = path.resolve(__dirname, '..', '..'); + const buildScript = path.join( + reactNativeRoot, + 'sdks', + 'hermes-engine', + 'utils', + 'build-ios-framework.sh', + ); + + const buildEnv = { + ...process.env, + BUILD_TYPE: buildType, + HERMES_PATH: hermesDir, + JSI_PATH: path.join(hermesDir, 'API', 'jsi'), + REACT_NATIVE_PATH: reactNativeRoot, + // Deployment targets matching react-native-macos minimums + IOS_DEPLOYMENT_TARGET: '15.1', + MAC_DEPLOYMENT_TARGET: '14.0', + XROS_DEPLOYMENT_TARGET: '1.0', + RELEASE_VERSION: version, + }; + + hermesLog(`Building Hermes frameworks (${buildType})...`); + execSync(`bash "${buildScript}"`, { + cwd: hermesDir, + stdio: 'inherit', + timeout: 3600000, // 60 minutes + env: buildEnv, + }); + + // Create tarball from the destroot (same structure as Maven artifacts) + const tarballName = `hermes-ios-${buildType.toLowerCase()}.tar.gz`; + const tarballPath = path.join(artifactsPath, tarballName); + hermesLog('Creating Hermes tarball from build output...'); + execSync(`tar -czf "${tarballPath}" -C "${hermesDir}" destroot`, { + stdio: 'inherit', + }); + + hermesLog(`Hermes built from source and packaged at ${tarballPath}`); + return tarballPath; + } catch (e) { + // Dump CMake error logs before cleanup for debugging + try { + const cmakeErrorLog = path.join( + hermesDir, + 'build_host_hermesc', + 'CMakeFiles', + 'CMakeError.log', + ); + if (fs.existsSync(cmakeErrorLog)) { + hermesLog('=== CMakeError.log ==='); + hermesLog(fs.readFileSync(cmakeErrorLog, 'utf8')); + } + } catch (_) { + // ignore + } + + abort( + `[Hermes] Failed to build Hermes from source at commit ${commit}.\n` + + `Error: ${e.message}\n` + + `To resolve, either:\n` + + ` 1. Set HERMES_ENGINE_TARBALL_PATH to a local Hermes tarball path\n` + + ` 2. Set HERMES_VERSION to an upstream RN version with published artifacts\n` + + ` 3. Build Hermes manually from commit ${commit} and provide the tarball path via HERMES_ENGINE_TARBALL_PATH`, + ); + return ''; // unreachable + } finally { + // Clean up + fs.rmSync(tmpDir, {recursive: true, force: true}); + } +} +// macOS] + function abort(message /*: string */) { hermesLog(message, 'error'); throw new Error(message); @@ -379,4 +538,6 @@ function abort(message /*: string */) { module.exports = { prepareHermesArtifactsAsync, + findMatchingHermesVersion, // [macOS] re-exported from macosVersionResolver.js + hermesCommitAtMergeBase, // [macOS] re-exported from macosVersionResolver.js }; diff --git a/packages/react-native/scripts/ios-prebuild/macosVersionResolver.js b/packages/react-native/scripts/ios-prebuild/macosVersionResolver.js new file mode 100644 index 000000000000..4654117f97a2 --- /dev/null +++ b/packages/react-native/scripts/ios-prebuild/macosVersionResolver.js @@ -0,0 +1,199 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * [macOS] This file is specific to react-native-macos and has no upstream equivalent. + * It handles version resolution for macOS fork branches where the package version + * differs from upstream react-native. + * + * @flow + * @format + */ + +const {createLogger} = require('./utils'); +const {execSync} = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const macosLog = createLogger('macOS'); + +/** + * For react-native-macos stable branches, maps the macOS package version + * to the upstream react-native version using peerDependencies. + * Returns null for version 1000.0.0 (main branch dev version). + * + * This is the JavaScript equivalent of the Ruby `findMatchingHermesVersion` + * in sdks/hermes-engine/hermes-utils.rb. + */ +function findMatchingHermesVersion( + packageJsonPath /*: string */, +) /*: ?string */ { + const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + if (pkg.version === '1000.0.0') { + macosLog( + 'Main branch detected (1000.0.0), no matching upstream Hermes version', + ); + return null; + } + + if (pkg.peerDependencies && pkg.peerDependencies['react-native']) { + const upstreamVersion = pkg.peerDependencies['react-native']; + macosLog( + `Mapped macOS version ${pkg.version} to upstream RN version: ${upstreamVersion}`, + ); + return upstreamVersion; + } + + macosLog( + 'No matching Hermes version found in peerDependencies. Defaulting to package version.', + ); + return null; +} + +/** + * Finds the Hermes commit at the merge base with facebook/react-native. + * Used on the main branch (1000.0.0) where no prebuilt artifacts exist. + * + * Since react-native-macos lags slightly behind facebook/react-native, we can't always use + * the latest Hermes commit because Hermes and JSI don't always guarantee backwards compatibility. + * Instead, we take the commit hash of Hermes at the time of the merge base with facebook/react-native. + * + * This is the JavaScript equivalent of the Ruby `hermes_commit_at_merge_base` + * in sdks/hermes-engine/hermes-utils.rb. + */ +function hermesCommitAtMergeBase() /*: {| commit: string, timestamp: string |} */ { + const HERMES_GITHUB_URL = 'https://github.com/facebook/hermes.git'; + + // Fetch upstream react-native + macosLog('Fetching facebook/react-native to find merge base...'); + try { + execSync('git fetch -q https://github.com/facebook/react-native.git', { + stdio: 'pipe', + }); + } catch (e) { + abort( + '[Hermes] Failed to fetch facebook/react-native into the local repository.', + ); + } + + // Find merge base between our HEAD and upstream's HEAD + const mergeBase = execSync('git merge-base FETCH_HEAD HEAD', { + encoding: 'utf8', + }).trim(); + if (!mergeBase) { + abort( + "[Hermes] Unable to find the merge base between our HEAD and upstream's HEAD.", + ); + } + + // Get timestamp of merge base + const timestamp = execSync(`git show -s --format=%ci ${mergeBase}`, { + encoding: 'utf8', + }).trim(); + if (!timestamp) { + abort( + `[Hermes] Unable to extract the timestamp for the merge base (${mergeBase}).`, + ); + } + + // Clone Hermes bare (minimal) into a temp directory and find the commit + macosLog( + `Merge base timestamp: ${timestamp}. Cloning Hermes to find matching commit...`, + ); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-')); + const hermesGitDir = path.join(tmpDir, 'hermes.git'); + + try { + // Explicitly use Hermes 'main' branch since the default branch changed to 'static_h' (Hermes V1) + execSync( + `git clone -q --bare --filter=blob:none --single-branch --branch main ${HERMES_GITHUB_URL} "${hermesGitDir}"`, + {stdio: 'pipe', timeout: 120000}, + ); + + // Find the Hermes commit at the time of the merge base on branch 'main' + const commit = execSync( + `git --git-dir="${hermesGitDir}" rev-list -1 --before="${timestamp}" refs/heads/main`, + {encoding: 'utf8'}, + ).trim(); + + if (!commit) { + abort( + `[Hermes] Unable to find the Hermes commit hash at time ${timestamp} on branch 'main'.`, + ); + } + + macosLog( + `Using Hermes commit from the merge base with facebook/react-native: ${commit} (timestamp: ${timestamp})`, + ); + return {commit, timestamp}; + } finally { + // Clean up temp directory + fs.rmSync(tmpDir, {recursive: true, force: true}); + } +} + +/** + * Finds the upstream react-native version at the merge base with facebook/react-native. + * Falls back to null if the version at merge base is also 1000.0.0 (i.e. merge base is + * on upstream main, not a release branch). + */ +function findVersionAtMergeBase() /*: ?string */ { + try { + // hermesCommitAtMergeBase() already fetches facebook/react-native, but we + // might not have FETCH_HEAD if this runs standalone. Fetch it. + execSync('git fetch -q https://github.com/facebook/react-native.git', { + stdio: 'pipe', + timeout: 60000, + }); + const mergeBase = execSync('git merge-base FETCH_HEAD HEAD', { + encoding: 'utf8', + }).trim(); + if (!mergeBase) { + return null; + } + // Read the package.json version at the merge base commit + const pkgJson = execSync( + `git show ${mergeBase}:packages/react-native/package.json`, + {encoding: 'utf8'}, + ); + const version = JSON.parse(pkgJson).version; + // If the merge base is also on main (1000.0.0), this doesn't help + if (version === '1000.0.0') { + return null; + } + return version; + } catch (_) { + return null; + } +} + +async function getLatestStableVersionFromNPM() /*: Promise */ { + const npmResponse /*: Response */ = await fetch( + 'https://registry.npmjs.org/react-native/latest', + ); + + if (!npmResponse.ok) { + throw new Error( + `Couldn't get latest stable version from NPM: ${npmResponse.status} ${npmResponse.statusText}`, + ); + } + + const json = await npmResponse.json(); + return json.version; +} + +function abort(message /*: string */) { + macosLog(message, 'error'); + throw new Error(message); +} + +module.exports = { + findMatchingHermesVersion, + hermesCommitAtMergeBase, + findVersionAtMergeBase, + getLatestStableVersionFromNPM, +}; diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index 6b5880e98c11..56b8f5fe7142 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -10,6 +10,11 @@ /*:: import type {BuildFlavor} from './types'; */ +const { + findMatchingHermesVersion, + findVersionAtMergeBase, + getLatestStableVersionFromNPM, +} = require('./macosVersionResolver'); // [macOS] const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); @@ -44,6 +49,35 @@ async function prepareReactNativeDependenciesArtifactsAsync( // Resolve the version from the environment variable or use the default version let resolvedVersion = process.env.RN_DEP_VERSION ?? version; + // [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 versionAtMergeBase = findVersionAtMergeBase(); + if (versionAtMergeBase != null) { + dependencyLog( + `Main branch detected. Using upstream version at merge base for ReactNativeDependencies: ${versionAtMergeBase}`, + ); + resolvedVersion = versionAtMergeBase; + } else { + const latestStable = await getLatestStableVersionFromNPM(); + dependencyLog( + `Main branch detected. Using latest stable RN version for ReactNativeDependencies: ${latestStable}`, + ); + resolvedVersion = latestStable; + } + } + } + // macOS] + if (resolvedVersion === 'nightly') { dependencyLog('Using latest nightly tarball'); const rnVersion = await getNightlyVersionFromNPM(); diff --git a/packages/react-native/scripts/ios-prebuild/setup.js b/packages/react-native/scripts/ios-prebuild/setup.js index 65e34bb6e482..73fe69993f02 100644 --- a/packages/react-native/scripts/ios-prebuild/setup.js +++ b/packages/react-native/scripts/ios-prebuild/setup.js @@ -190,6 +190,16 @@ async function setup( 'ReactCommon/react/renderer/components/view/platform/cxx', 'ReactCommon/react/renderer/components/view', ); + // [macOS - link macOS-specific view platform headers + link( + 'ReactCommon/react/renderer/components/view/platform/macos', + 'ReactCommon/react/renderer/components/view', + ); + link( + 'ReactCommon/react/renderer/components/view/platform/macos', + 'react/renderer/components/view', + ); + // macOS] link('ReactCommon/react/renderer/mounting'); link('ReactCommon/react/renderer/attributedstring'); link('ReactCommon/runtimeexecutor/ReactCommon', 'ReactCommon'); diff --git a/packages/react-native/scripts/ios-prebuild/types.js b/packages/react-native/scripts/ios-prebuild/types.js index c1ac1489c804..87f516c5ba99 100644 --- a/packages/react-native/scripts/ios-prebuild/types.js +++ b/packages/react-native/scripts/ios-prebuild/types.js @@ -12,12 +12,16 @@ export type Platform = 'ios' | 'ios-simulator' | - 'mac-catalyst'; + 'mac-catalyst' | + 'macos' | // [macOS] + 'visionos'; // [macOS] export type Destination = 'iOS' | 'iOS Simulator' | - 'macOS,variant=Mac Catalyst'; + 'macOS,variant=Mac Catalyst' | + 'macOS' | // [macOS] + 'visionOS'; // [macOS] export type BuildFlavor = 'Debug' | 'Release'; */ diff --git a/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh b/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh index 76b23e18970c..44ce6d663582 100755 --- a/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh +++ b/packages/react-native/sdks/hermes-engine/utils/build-apple-framework.sh @@ -12,7 +12,7 @@ CURR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" IMPORT_HERMESC_PATH=${HERMES_OVERRIDE_HERMESC_PATH:-$PWD/build_host_hermesc/ImportHermesc.cmake} BUILD_TYPE=${BUILD_TYPE:-Debug} -HERMES_PATH="$CURR_SCRIPT_DIR/.." +HERMES_PATH=${HERMES_PATH:-"$CURR_SCRIPT_DIR/.."} REACT_NATIVE_PATH=${REACT_NATIVE_PATH:-$CURR_SCRIPT_DIR/../../..} NUM_CORES=$(sysctl -n hw.ncpu) diff --git a/packages/react-native/sdks/hermes-engine/utils/build-ios-framework.sh b/packages/react-native/sdks/hermes-engine/utils/build-ios-framework.sh index 08382b7d4deb..c6cd66001aed 100755 --- a/packages/react-native/sdks/hermes-engine/utils/build-ios-framework.sh +++ b/packages/react-native/sdks/hermes-engine/utils/build-ios-framework.sh @@ -10,7 +10,7 @@ fi set -e # Given a specific target, retrieve the right architecture for it -# $1 the target you want to build. Allowed values: iphoneos, iphonesimulator, catalyst, xros, xrsimulator +# $1 the target you want to build. Allowed values: iphoneos, iphonesimulator, catalyst, macosx, xros, xrsimulator function get_architecture { if [[ $1 == "iphoneos" || $1 == "xros" ]]; then echo "arm64" @@ -20,7 +20,7 @@ function get_architecture { echo "arm64" elif [[ $1 == "appletvsimulator" ]]; then echo "x86_64;arm64" - elif [[ $1 == "catalyst" ]]; then + elif [[ $1 == "catalyst" || $1 == "macosx" ]]; then echo "x86_64;arm64" else echo "Error: unknown architecture passed $1" @@ -29,7 +29,9 @@ function get_architecture { } function get_deployment_target { - if [[ $1 == "xros" || $1 == "xrsimulator" ]]; then + if [[ $1 == "macosx" ]]; then + echo "$(get_mac_deployment_target)" + elif [[ $1 == "xros" || $1 == "xrsimulator" ]]; then echo "$(get_visionos_deployment_target)" else # tvOS and iOS use the same deployment target echo "$(get_ios_deployment_target)" @@ -53,7 +55,7 @@ function build_framework { # group the frameworks together to create a universal framework 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" else echo "Skipping; Clean \"destroot\" to rebuild". fi @@ -63,6 +65,7 @@ function build_universal_framework { # this is used to preserve backward compatibility function create_framework { if [ ! -d destroot/Library/Frameworks/universal/hermes.xcframework ]; then + build_framework "macosx" build_framework "iphoneos" build_framework "iphonesimulator" build_framework "appletvos" From f20a4a6962e54683b5c25bf6387dc587ae85fbae Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 10 Apr 2026 19:44:23 -0700 Subject: [PATCH 2/4] Add build-spm job to microsoft-pr.yml for 0.81-stable The SPM workflow is triggered via workflow_call from microsoft-pr.yml, which was missing on 0.81-stable. Add the build-spm job so CI runs the SPM pipeline on PRs targeting this branch. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/microsoft-pr.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/microsoft-pr.yml b/.github/workflows/microsoft-pr.yml index 968a5d5d9bf3..a1d3d2d4c274 100644 --- a/.github/workflows/microsoft-pr.yml +++ b/.github/workflows/microsoft-pr.yml @@ -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: {} @@ -156,6 +161,7 @@ jobs: - yarn-constraints - javascript-tests - build-rntester + - build-spm - test-react-native-macos-init # - react-native-test-app-integration steps: From bc5b4d3789dfc59622999282b45e318a2e382251 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 10 Apr 2026 20:33:02 -0700 Subject: [PATCH 3/4] Fix macOS SPM build: conditionally exclude iOS/macOS switch files IOSSwitchShadowNode.mm imports UIKit which doesn't exist on macOS. Use #if os(macOS) to exclude the iOS variant and include the macOS variant (and vice versa on iOS), matching the existing pattern for view platform sources. Co-Authored-By: Claude Opus 4.6 --- packages/react-native/Package.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index 2742217ae805..d753392f1941 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -385,9 +385,11 @@ let reactCore = RNTarget( #if os(macOS) let reactFabricViewPlatformSources = ["components/view/platform/macos"] let reactFabricViewPlatformExcludes = ["components/view/platform/cxx"] +let reactFabricComponentsSwitchExcludes = ["components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm"] #else let reactFabricViewPlatformExcludes = ["components/view/platform/macos"] let reactFabricViewPlatformSources = ["components/view/platform/cxx"] +let reactFabricComponentsSwitchExcludes = ["components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm"] #endif // macOS] let reactFabric = RNTarget( @@ -446,7 +448,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", + // "components/switch/iosswitch/.../MacOSSwitchShadowNode.mm" or "IOSSwitchShadowNode.mm" — [macOS] see reactFabricComponentsSwitchExcludes "components/textinput/platform/android", "components/text/platform/android", "components/textinput/platform/macos", @@ -457,7 +459,7 @@ let reactFabricComponents = RNTarget( "textlayoutmanager/platform/windows", "textlayoutmanager/platform/macos", "conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail. - ], + ] + reactFabricComponentsSwitchExcludes, // [macOS] dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging], sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios", "components/switch/iosswitch"] ) From ad4aa3082ed2c48db80e7f3f507b8ec1f28b9dea Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 10 Apr 2026 22:07:43 -0700 Subject: [PATCH 4/4] Fix switch shadow node compilation for cross-platform SPM builds Package.swift's #if os(macOS) runs on the host (always macOS), not the target platform. This meant iOS builds got macOS switch excludes, causing AppKit import errors. Instead of conditionally excluding files in Package.swift, add #if TARGET_OS_OSX guards directly in the .mm files. Both files are now included in all builds but compile to empty on the wrong platform. Co-Authored-By: Claude Opus 4.6 --- packages/react-native/Package.swift | 7 +++---- .../renderer/components/switch/IOSSwitchShadowNode.mm | 5 +++++ .../renderer/components/switch/MacOSSwitchShadowNode.mm | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index d753392f1941..8c9210c0bdb0 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -385,11 +385,9 @@ let reactCore = RNTarget( #if os(macOS) let reactFabricViewPlatformSources = ["components/view/platform/macos"] let reactFabricViewPlatformExcludes = ["components/view/platform/cxx"] -let reactFabricComponentsSwitchExcludes = ["components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm"] #else let reactFabricViewPlatformExcludes = ["components/view/platform/macos"] let reactFabricViewPlatformSources = ["components/view/platform/cxx"] -let reactFabricComponentsSwitchExcludes = ["components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm"] #endif // macOS] let reactFabric = RNTarget( @@ -448,7 +446,8 @@ let reactFabricComponents = RNTarget( "components/view/platform/android", "components/view/platform/windows", "components/view/platform/macos", - // "components/switch/iosswitch/.../MacOSSwitchShadowNode.mm" or "IOSSwitchShadowNode.mm" — [macOS] see reactFabricComponentsSwitchExcludes + // [macOS] Both IOSSwitchShadowNode.mm and MacOSSwitchShadowNode.mm are included; + // they use #if TARGET_OS_OSX guards internally so only the correct one compiles. "components/textinput/platform/android", "components/text/platform/android", "components/textinput/platform/macos", @@ -459,7 +458,7 @@ let reactFabricComponents = RNTarget( "textlayoutmanager/platform/windows", "textlayoutmanager/platform/macos", "conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail. - ] + reactFabricComponentsSwitchExcludes, // [macOS] + ], dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging], sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios", "components/switch/iosswitch"] ) diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm index d1981f17c062..7b8bc2e7d380 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +#include // [macOS] +#if !TARGET_OS_OSX // [macOS] + #import #import #include "AppleSwitchShadowNode.h" @@ -26,3 +29,5 @@ } } // namespace facebook::react + +#endif // !TARGET_OS_OSX [macOS] diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm index cd62f626ba68..ec76095bc8e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +#include // [macOS] +#if TARGET_OS_OSX // [macOS] + #import #include "AppleSwitchShadowNode.h" @@ -30,3 +33,5 @@ } } // namespace facebook::react + +#endif // TARGET_OS_OSX [macOS]