From 04245d17dabfbe5931d531ddd1eccc811be2e8f0 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Apr 2026 16:56:08 -0700 Subject: [PATCH 1/4] feat: add macOS and visionOS support to SPM build system - 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 --- packages/react-native/Package.swift | 58 +++- .../react-native/scripts/ios-prebuild/cli.js | 6 + .../scripts/ios-prebuild/hermes.js | 279 +++++++++++++++++- .../ios-prebuild/reactNativeDependencies.js | 82 +++++ .../scripts/ios-prebuild/setup.js | 11 + .../scripts/ios-prebuild/types.js | 10 +- 6 files changed, 431 insertions(+), 15 deletions(-) diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index c387085ea236..0b0c3475af30 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -246,7 +246,13 @@ let reactJsErrorHandler = RNTarget( let reactGraphicsApple = RNTarget( name: .reactGraphicsApple, path: "ReactCommon/react/renderer/graphics/platform/ios", - linkedFrameworks: ["UIKit", "CoreGraphics"], + linkedFrameworks: ["CoreGraphics"], + // [macOS: UIKit on iOS/visionOS, AppKit on macOS + platformLinkerSettings: [ + .linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])), + .linkedFramework("AppKit", .when(platforms: [.macOS])), + ], + // macOS] dependencies: [.reactDebug, .jsi, .reactUtils, .reactNativeDependencies] ) @@ -360,12 +366,21 @@ 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"], - 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], + 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], 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 reactFabricViewPlatformSources = ["components/view/platform/cxx"] +let reactFabricViewPlatformExcludes = ["components/view/platform/macos"] +#endif +// macOS] let reactFabric = RNTarget( name: .reactFabric, path: "ReactCommon/react/renderer", @@ -376,7 +391,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", @@ -399,9 +415,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 @@ -420,16 +436,14 @@ let reactFabricComponents = RNTarget( "components/modal/platform/cxx", "components/view/platform/android", "components/view/platform/windows", - "components/view/platform/macos", + // "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", "components/text/tests", "textlayoutmanager/tests", "textlayoutmanager/platform/android", "textlayoutmanager/platform/cxx", "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. ], dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging], @@ -524,6 +538,22 @@ let reactSettings = RNTarget( dependencies: [.reactTurboModuleCore, .yoga] ) +// [macOS +/// React-RCTUIKit.podspec +/// UIKit/AppKit compatibility layer for React Native macOS. +let reactRCTUIKit = RNTarget( + name: .reactRCTUIKit, + path: "React/RCTUIKit", + // [macOS: UIKit on iOS/visionOS, AppKit on macOS + platformLinkerSettings: [ + .linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])), + .linkedFramework("AppKit", .when(platforms: [.macOS])), + ], + // macOS] + excludedPaths: ["README.md"] +) +// macOS] + // MARK: Target list let targets = [ reactDebug, @@ -581,13 +611,14 @@ let targets = [ reactAppDelegate, reactSettings, reactRuntimeExecutor, + reactRCTUIKit, // [macOS] ] // MARK: Package object 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, @@ -628,14 +659,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 @@ -671,7 +704,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, @@ -753,6 +786,7 @@ extension String { static let reactNativeModuleDom = "React-domnativemodule" static let reactAppDelegate = "React-RCTAppDelegate" static let reactSettings = "React-RCTSettings" + static let reactRCTUIKit = "React-RCTUIKit" // [macOS] } func relativeSearchPath(_ depth: Int, _ path: String) -> String { diff --git a/packages/react-native/scripts/ios-prebuild/cli.js b/packages/react-native/scripts/ios-prebuild/cli.js index 01301c800af7..86b639d00ad8 100644 --- a/packages/react-native/scripts/ios-prebuild/cli.js +++ b/packages/react-native/scripts/ios-prebuild/cli.js @@ -17,7 +17,10 @@ import type {BuildFlavor, Destination, Platform} from './types'; const platforms /*: $ReadOnlyArray */ = [ 'ios', 'ios-simulator', + 'macos', // [macOS] 'mac-catalyst', + 'visionos', // [macOS] + 'visionos-simulator', // [macOS] ]; // CI can't use commas in cache keys, so 'macOS,variant=Mac Catalyst' was creating troubles @@ -25,7 +28,10 @@ const platforms /*: $ReadOnlyArray */ = [ const platformToDestination /*: $ReadOnly<{|[Platform]: Destination|}> */ = { ios: 'iOS', 'ios-simulator': 'iOS Simulator', + macos: 'macOS', // [macOS] 'mac-catalyst': 'macOS,variant=Mac Catalyst', + visionos: 'xrOS', // [macOS] + 'visionos-simulator': 'xrOS Simulator', // [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 356901ebdf22..ac12be2e380e 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -11,6 +11,7 @@ const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); +const os = require('os'); const path = require('path'); const stream = require('stream'); const {promisify} = require('util'); @@ -22,6 +23,124 @@ const hermesLog = createLogger('Hermes'); import type {BuildFlavor, Destination, Platform} from './types'; */ +// [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') { + hermesLog( + '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']; + hermesLog( + `Mapped macOS version ${pkg.version} to upstream RN version: ${upstreamVersion}`, + ); + return upstreamVersion; + } + + hermesLog( + '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 + hermesLog('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 + hermesLog( + `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'.`, + ); + } + + hermesLog( + `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}); + } +} +// macOS] + /** * Downloads hermes artifacts from the specified version and build type. If you want to specify a specific * version of hermes, use the HERMES_VERSION environment variable. The path to the artifacts will be inside @@ -56,6 +175,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 +216,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 +270,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 */; /** @@ -221,10 +369,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'); @@ -244,6 +398,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.', ); @@ -263,6 +427,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}`, @@ -369,6 +535,115 @@ 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 + hermesLog(`Cloning Hermes at commit ${commit}...`); + 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', + }); + + // The build-ios-framework.sh script runs from the hermes directory. + // It sources build-apple-framework.sh which sets HERMES_PATH relative to itself, + // but we override it to point to the cloned Hermes repo. + const reactNativeRoot = path.resolve(__dirname, '..', '..'); + const buildScript = path.join( + reactNativeRoot, + 'sdks', + 'hermes-engine', + 'utils', + 'build-ios-framework.sh', + ); + + hermesLog(`Building Hermes frameworks (${buildType})...`); + execSync(`bash "${buildScript}"`, { + cwd: hermesDir, + stdio: 'inherit', + timeout: 3600000, // 60 minutes + env: { + ...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, + }, + }); + + // 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); @@ -376,4 +651,6 @@ function abort(message /*: string */) { module.exports = { prepareHermesArtifactsAsync, + findMatchingHermesVersion, // [macOS] + hermesCommitAtMergeBase, // [macOS] }; diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index bac3a0e05197..4e89b3f9ecc2 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -10,6 +10,7 @@ /*:: import type {BuildFlavor} from './types'; */ +const {findMatchingHermesVersion} = require('./hermes'); // [macOS] const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); @@ -44,6 +45,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(); @@ -117,6 +147,58 @@ async function getNightlyVersionFromNPM() /*: Promise */ { return latestNightly; } +// [macOS +/** + * 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; +} +// macOS] + /*:: type ReactNativeDependenciesEngineSourceType = | 'download_prebuild_tarball' diff --git a/packages/react-native/scripts/ios-prebuild/setup.js b/packages/react-native/scripts/ios-prebuild/setup.js index bd26c3634f44..c2cd31e65460 100644 --- a/packages/react-native/scripts/ios-prebuild/setup.js +++ b/packages/react-native/scripts/ios-prebuild/setup.js @@ -145,6 +145,7 @@ async function setup( link('Libraries/LinkingIOS', 'React'); link('Libraries/Settings', 'React'); + link('React/RCTUIKit', 'React'); // [macOS] link('Libraries/PushNotificationIOS', 'React'); link('Libraries/Settings', 'React'); link('Libraries/Vibration', 'React'); @@ -184,6 +185,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..5ad9e8b25106 100644 --- a/packages/react-native/scripts/ios-prebuild/types.js +++ b/packages/react-native/scripts/ios-prebuild/types.js @@ -12,12 +12,18 @@ export type Platform = 'ios' | 'ios-simulator' | - 'mac-catalyst'; + 'macos' | + 'mac-catalyst' | + 'visionos' | + 'visionos-simulator'; export type Destination = 'iOS' | 'iOS Simulator' | - 'macOS,variant=Mac Catalyst'; + 'macOS' | + 'macOS,variant=Mac Catalyst' | + 'xrOS' | + 'xrOS Simulator'; export type BuildFlavor = 'Debug' | 'Release'; */ From c2f5b6167ece69b3967615fc55c1162fa577d44d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Apr 2026 16:56:14 -0700 Subject: [PATCH 2/4] feat: include macOS slice in Hermes xcframework - 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 --- .../sdks/hermes-engine/utils/build-apple-framework.sh | 9 +++++++-- .../sdks/hermes-engine/utils/build-ios-framework.sh | 11 +++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) 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..299b5b903495 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) @@ -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}" cmake --build ./build_host_hermesc --target hermesc -j "${NUM_CORES}" popd > /dev/null || exit 1 } 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 233ee43ed4fc466aa219995982333b0d6d0e7619 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 8 Apr 2026 16:56:30 -0700 Subject: [PATCH 3/4] ci: add SPM build workflow with parallel Hermes builds and caching 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 --- .../microsoft-setup-toolchain/action.yml | 6 +- .github/workflows/microsoft-build-spm.yml | 256 ++++++++++++++++++ .github/workflows/microsoft-pr.yml | 6 + 3 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/microsoft-build-spm.yml diff --git a/.github/actions/microsoft-setup-toolchain/action.yml b/.github/actions/microsoft-setup-toolchain/action.yml index 3d731fc7776d..8e8b6120536d 100644 --- a/.github/actions/microsoft-setup-toolchain/action.yml +++ b/.github/actions/microsoft-setup-toolchain/action.yml @@ -19,13 +19,17 @@ inputs: xcode-developer-dir: description: Set the path for the active Xcode developer directory default: "/Applications/Xcode.app" + cmake-version: + description: CMake version to install. Set to 'system' to skip installation and use the runner's pre-installed cmake. + default: "3.31.9" runs: using: composite steps: - name: Install cmake + if: ${{ inputs.cmake-version != 'system' }} uses: jwlawson/actions-setup-cmake@v2 with: - cmake-version: '3.31.9' + cmake-version: ${{ inputs.cmake-version }} - name: Set up Ccache id: setup-ccache if: ${{ inputs.platform == 'ios' || inputs.platform == 'macos' || inputs.platform == 'visionos' }} diff --git a/.github/workflows/microsoft-build-spm.yml b/.github/workflows/microsoft-build-spm.yml new file mode 100644 index 000000000000..b891d5549db6 --- /dev/null +++ b/.github/workflows/microsoft-build-spm.yml @@ -0,0 +1,256 @@ +name: Build SPM + +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: /tmp/hermes-destroot + + - name: Upload cached Hermes artifacts + if: steps.cache.outputs.cache-hit == 'true' + uses: actions/upload-artifact@v4 + with: + name: hermes-artifacts + path: /tmp/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 + run: | + 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 }} + + - name: Build hermesc + working-directory: /tmp/hermes + env: + HERMES_PATH: /tmp/hermes + JSI_PATH: /tmp/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: /tmp/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 + run: | + 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 }} + + - name: Download hermesc + uses: actions/download-artifact@v4 + with: + name: hermesc + path: /tmp/hermes/build_host_hermesc + + - name: Restore hermesc permissions + run: chmod +x /tmp/hermes/build_host_hermesc/bin/hermesc + + - name: Build Hermes slice (${{ matrix.slice }}) + working-directory: /tmp/hermes + env: + BUILD_TYPE: Debug + HERMES_PATH: /tmp/hermes + JSI_PATH: /tmp/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: /tmp/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 /tmp/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" /tmp/hermes/destroot/Library/Frameworks/ + # Copy include and bin directories (identical across slices, only need one copy) + if [ -d "$slice_dir/include" ] && [ ! -d /tmp/hermes/destroot/include ]; then + cp -R "$slice_dir/include" /tmp/hermes/destroot/ + fi + if [ -d "$slice_dir/bin" ]; then + cp -R "$slice_dir/bin" /tmp/hermes/destroot/ + fi + done + echo "Assembled destroot contents:" + ls -la /tmp/hermes/destroot/Library/Frameworks/ + + - name: Create universal xcframework + working-directory: /tmp/hermes + env: + HERMES_PATH: /tmp/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: /tmp/hermes/destroot + + - name: Upload Hermes artifacts + uses: actions/upload-artifact@v4 + with: + name: hermes-artifacts + path: /tmp/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/.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 21f812cd5697c1f8b6913b6569187989b08c3471 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Thu, 9 Apr 2026 11:58:47 -0700 Subject: [PATCH 4/4] refactor: address PR review comments (round 2) - 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 --- .github/workflows/microsoft-build-spm.yml | 66 +++--- packages/react-native/Package.swift | 2 + .../scripts/ios-prebuild/hermes.js | 176 +++------------- .../ios-prebuild/macosVersionResolver.js | 199 ++++++++++++++++++ .../ios-prebuild/reactNativeDependencies.js | 58 +---- .../utils/build-apple-framework.sh | 7 +- 6 files changed, 270 insertions(+), 238 deletions(-) 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 index b891d5549db6..46a6c25a62c8 100644 --- a/.github/workflows/microsoft-build-spm.yml +++ b/.github/workflows/microsoft-build-spm.yml @@ -1,4 +1,4 @@ -name: Build SPM +name: Build SwiftPM on: workflow_call: @@ -43,14 +43,14 @@ jobs: uses: actions/cache/restore@v4 with: key: hermes-v1-${{ steps.resolve.outputs.hermes-commit }}-Debug - path: /tmp/hermes-destroot + 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: /tmp/hermes-destroot + path: hermes-destroot retention-days: 1 build-hermesc: @@ -68,17 +68,17 @@ jobs: run: sudo xcode-select --switch /Applications/Xcode_16.2.app - name: Clone Hermes - run: | - 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 }} + uses: actions/checkout@v4 + with: + repository: facebook/hermes + ref: ${{ needs.resolve-hermes.outputs.hermes-commit }} + path: hermes - name: Build hermesc - working-directory: /tmp/hermes + working-directory: hermes env: - HERMES_PATH: /tmp/hermes - JSI_PATH: /tmp/hermes/API/jsi + 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 @@ -88,7 +88,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: hermesc - path: /tmp/hermes/build_host_hermesc + path: hermes/build_host_hermesc retention-days: 1 build-hermes-slice: @@ -118,27 +118,27 @@ jobs: sudo xcodebuild -runFirstLaunch - name: Clone Hermes - run: | - 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 }} + 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: /tmp/hermes/build_host_hermesc + path: hermes/build_host_hermesc - name: Restore hermesc permissions - run: chmod +x /tmp/hermes/build_host_hermesc/bin/hermesc + run: chmod +x ${{ github.workspace }}/hermes/build_host_hermesc/bin/hermesc - name: Build Hermes slice (${{ matrix.slice }}) - working-directory: /tmp/hermes + working-directory: hermes env: BUILD_TYPE: Debug - HERMES_PATH: /tmp/hermes - JSI_PATH: /tmp/hermes/API/jsi + 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' @@ -150,7 +150,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: hermes-slice-${{ matrix.slice }} - path: /tmp/hermes/destroot + path: hermes/destroot retention-days: 1 assemble-hermes: @@ -172,26 +172,26 @@ jobs: - name: Assemble destroot from slices run: | - mkdir -p /tmp/hermes/destroot/Library/Frameworks + 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" /tmp/hermes/destroot/Library/Frameworks/ + 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 /tmp/hermes/destroot/include ]; then - cp -R "$slice_dir/include" /tmp/hermes/destroot/ + 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" /tmp/hermes/destroot/ + cp -R "$slice_dir/bin" ${{ github.workspace }}/hermes/destroot/ fi done echo "Assembled destroot contents:" - ls -la /tmp/hermes/destroot/Library/Frameworks/ + ls -la ${{ github.workspace }}/hermes/destroot/Library/Frameworks/ - name: Create universal xcframework - working-directory: /tmp/hermes + working-directory: hermes env: - HERMES_PATH: /tmp/hermes + 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" @@ -200,13 +200,13 @@ jobs: uses: actions/cache/save@v4 with: key: hermes-v1-${{ needs.resolve-hermes.outputs.hermes-commit }}-Debug - path: /tmp/hermes/destroot + path: hermes/destroot - name: Upload Hermes artifacts uses: actions/upload-artifact@v4 with: name: hermes-artifacts - path: /tmp/hermes/destroot + path: hermes/destroot retention-days: 1 build-spm: diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index 0b0c3475af30..9ee8a536aef2 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -248,6 +248,8 @@ let reactGraphicsApple = RNTarget( path: "ReactCommon/react/renderer/graphics/platform/ios", linkedFrameworks: ["CoreGraphics"], // [macOS: UIKit on iOS/visionOS, AppKit on macOS + // Note: #if os(macOS) doesn't work here because Package.swift runs on the host, + // not the target. Use .when(platforms:) for cross-compilation support. platformLinkerSettings: [ .linkedFramework("UIKit", .when(platforms: [.iOS, .visionOS])), .linkedFramework("AppKit", .when(platforms: [.macOS])), diff --git a/packages/react-native/scripts/ios-prebuild/hermes.js b/packages/react-native/scripts/ios-prebuild/hermes.js index ac12be2e380e..c1ca2e89de8a 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -8,10 +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'); +const os = require('os'); // [macOS] const path = require('path'); const stream = require('stream'); const {promisify} = require('util'); @@ -23,124 +27,6 @@ const hermesLog = createLogger('Hermes'); import type {BuildFlavor, Destination, Platform} from './types'; */ -// [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') { - hermesLog( - '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']; - hermesLog( - `Mapped macOS version ${pkg.version} to upstream RN version: ${upstreamVersion}`, - ); - return upstreamVersion; - } - - hermesLog( - '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 - hermesLog('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 - hermesLog( - `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'.`, - ); - } - - hermesLog( - `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}); - } -} -// macOS] - /** * Downloads hermes artifacts from the specified version and build type. If you want to specify a specific * version of hermes, use the HERMES_VERSION environment variable. The path to the artifacts will be inside @@ -556,23 +442,19 @@ async function buildFromHermesCommit( const hermesDir = path.join(tmpDir, 'hermes'); try { - // Clone Hermes at the identified commit + // 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 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}`, { + 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'}); - // The build-ios-framework.sh script runs from the hermes directory. - // It sources build-apple-framework.sh which sets HERMES_PATH relative to itself, - // but we override it to point to the cloned Hermes repo. const reactNativeRoot = path.resolve(__dirname, '..', '..'); const buildScript = path.join( reactNativeRoot, @@ -582,23 +464,25 @@ async function buildFromHermesCommit( '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: { - ...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, - }, + env: buildEnv, }); // Create tarball from the destroot (same structure as Maven artifacts) @@ -651,6 +535,6 @@ function abort(message /*: string */) { module.exports = { prepareHermesArtifactsAsync, - findMatchingHermesVersion, // [macOS] - hermesCommitAtMergeBase, // [macOS] + 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 4e89b3f9ecc2..1bdd9b08bb62 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -10,7 +10,11 @@ /*:: import type {BuildFlavor} from './types'; */ -const {findMatchingHermesVersion} = require('./hermes'); // [macOS] +const { + findMatchingHermesVersion, + findVersionAtMergeBase, + getLatestStableVersionFromNPM, +} = require('./macosVersionResolver'); // [macOS] const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); @@ -147,58 +151,6 @@ async function getNightlyVersionFromNPM() /*: Promise */ { return latestNightly; } -// [macOS -/** - * 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; -} -// macOS] - /*:: type ReactNativeDependenciesEngineSourceType = | 'download_prebuild_tarball' 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 299b5b903495..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 @@ -60,12 +60,7 @@ function get_mac_deployment_target { function build_host_hermesc { echo "Building hermesc" pushd "$HERMES_PATH" > /dev/null || exit 1 - # 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}" + cmake -S . -B build_host_hermesc -DJSI_DIR="$JSI_PATH" cmake --build ./build_host_hermesc --target hermesc -j "${NUM_CORES}" popd > /dev/null || exit 1 }