diff --git a/.github/scripts/resolve-hermes.mts b/.github/scripts/resolve-hermes.mts new file mode 100644 index 00000000000..8d628957359 --- /dev/null +++ b/.github/scripts/resolve-hermes.mts @@ -0,0 +1,250 @@ +#!/usr/bin/env node +/** + * CI entry point for resolving Hermes artifacts. + * + * Commands: + * node resolve-hermes.mts download-hermes [Debug|Release] + * node resolve-hermes.mts recompose-xcframework + * node resolve-hermes.mts resolve-commit + * + * Each command writes results to $GITHUB_OUTPUT for use in GitHub Actions. + */ +import { createRequire } from 'node:module'; +import os from 'node:os'; +import { parseArgs } from 'node:util'; +import { $, echo, fs, path } from 'zx'; + +// Use createRequire to import CommonJS modules from ESM context +const require = createRequire(import.meta.url); +const { + findMatchingHermesVersion, + findVersionAtMergeBase, + getLatestStableVersionFromNPM, + hermesCommitAtMergeBase, +} = require('../../packages/react-native/scripts/ios-prebuild/microsoft-hermes.js'); +const { + computeNightlyTarballURL, +} = require('../../packages/react-native/scripts/ios-prebuild/utils.js'); + +function setActionOutput(key: string, value: string) { + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + fs.appendFileSync(outputFile, `${key}=${value}\n`); + } +} + +/** + * Downloads the upstream Hermes tarball from Maven or Sonatype. + * + * Tries multiple version resolution strategies in order: + * 1. Mapped version from peerDependencies (stable branches) + * 2. Version at merge base with facebook/react-native (main branch) + * 3. Latest stable version from npm (last resort) + * + * Returns {tarballPath, version} on success, or null if no tarball is available. + */ +async function downloadUpstreamHermesTarball( + buildType: string = 'Debug', +): Promise<{ tarballPath: string; version: string } | null> { + const packageJsonPath = path.resolve( + import.meta.dirname!, '..', '..', 'packages', 'react-native', 'package.json', + ); + + // Build a list of candidate versions to try (in priority order) + const candidates: string[] = []; + + const mapped = findMatchingHermesVersion(packageJsonPath); + if (mapped != null) { + candidates.push(mapped); + } + + const mergeBaseVersion = findVersionAtMergeBase(); + if (mergeBaseVersion != null && !candidates.includes(mergeBaseVersion)) { + candidates.push(mergeBaseVersion); + } + + try { + const latestStable = await getLatestStableVersionFromNPM(); + if (!candidates.includes(latestStable)) { + candidates.push(latestStable); + } + } catch { + // npm lookup failed, continue with what we have + } + + if (candidates.length === 0) { + echo('Could not determine any upstream version to download Hermes tarball'); + return null; + } + + const mavenRepoUrl = 'https://repo1.maven.org/maven2'; + const namespace = 'com/facebook/react'; + + for (const version of candidates) { + const releaseUrl = `${mavenRepoUrl}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-hermes-ios-${buildType.toLowerCase()}.tar.gz`; + const nightlyUrl = await computeNightlyTarballURL( + version, + buildType, + 'react-native-artifacts', + `hermes-ios-${buildType.toLowerCase()}.tar.gz`, + ); + const urlsToTry = [releaseUrl]; + if (nightlyUrl) { + urlsToTry.push(nightlyUrl); + } + + for (const tarballUrl of urlsToTry) { + echo(`Trying upstream Hermes tarball (version: ${version}, ${buildType}) at ${tarballUrl}...`); + + try { + const response = await fetch(tarballUrl); + if (!response.ok) { + echo(`Tarball not available: ${response.status} ${response.statusText}`); + continue; + } + + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-')); + const tarballPath = path.join(tmpDir, 'hermes-ios.tar.gz'); + const buffer = await response.arrayBuffer(); + fs.writeFileSync(tarballPath, Buffer.from(buffer)); + + echo(`Downloaded upstream Hermes tarball (${version}) to ${tarballPath}`); + return { tarballPath, version }; + } catch (e: any) { + echo(`Error downloading tarball for ${version}: ${e.message}`); + continue; + } + } + } + + echo('No upstream Hermes tarball found for any candidate version — will build from source.'); + return null; +} + +/** + * Extracts an upstream Hermes tarball and recomposes the xcframework to include + * the macOS slice, if needed. + * + * Upstream tarballs ship a universal xcframework (iOS, simulator, catalyst, + * tvOS, visionOS) plus a standalone macosx/hermes.framework. This function + * merges the standalone macOS framework into the universal xcframework using + * `xcodebuild -create-xcframework`. + * + * NOTE: Once upstream Hermes includes macOS in the universal xcframework + * natively, this function will detect the existing macOS slice and skip + * the recompose. At that point, this step can be removed entirely. + * Tracking PRs: + * - https://github.com/facebook/hermes/pull/1958 + * - https://github.com/facebook/hermes/pull/1970 + * - https://github.com/facebook/hermes/pull/1971 + */ +async function recomposeHermesXcframework( + tarballPath: string, + destroot: string, +): Promise { + // Extract tarball + fs.mkdirSync(destroot, { recursive: true }); + await $`tar -xzf ${tarballPath} -C ${destroot} --strip-components=2`; + + const frameworksDir = path.join(destroot, 'Library', 'Frameworks'); + const xcfwPath = path.join(frameworksDir, 'universal', 'hermes.xcframework'); + + echo('Upstream tarball contents:'); + await $`ls -la ${frameworksDir}`; + + // Check if macOS is already in the universal xcframework — if so, no recompose needed + const xcfwContents = fs.readdirSync(xcfwPath); + const hasMacSlice = xcfwContents.some( + (entry: string) => entry.startsWith('macos') && entry.includes('arm64'), + ); + if (hasMacSlice) { + echo('macOS slice already present in universal xcframework, skipping recompose'); + const standaloneMacDir = path.join(frameworksDir, 'macosx'); + if (fs.existsSync(standaloneMacDir)) { + fs.removeSync(standaloneMacDir); + } + return true; + } + + // Check for standalone macOS framework + const standaloneMacFw = path.join(frameworksDir, 'macosx', 'hermes.framework'); + if (!fs.existsSync(standaloneMacFw)) { + echo('ERROR: Upstream tarball missing macosx/hermes.framework'); + return false; + } + + // Collect existing frameworks from inside the universal xcframework + const frameworkArgs: string[] = []; + for (const entry of xcfwContents) { + const fwPath = path.join(xcfwPath, entry, 'hermes.framework'); + if (fs.existsSync(fwPath) && fs.statSync(fwPath).isDirectory()) { + echo(`Found slice: ${fwPath}`); + frameworkArgs.push('-framework', fwPath); + } + } + + // Add the standalone macOS framework + echo(`Found standalone macOS slice: ${standaloneMacFw}`); + frameworkArgs.push('-framework', standaloneMacFw); + + // Build new xcframework at a temp path (frameworks reference paths inside the old xcfw) + const xcfwNew = path.join(frameworksDir, 'universal', 'hermes-new.xcframework'); + const sliceCount = frameworkArgs.filter(f => f !== '-framework').length; + echo(`Creating new universal xcframework with ${sliceCount} slices...`); + await $`xcodebuild -create-xcframework ${frameworkArgs} -output ${xcfwNew} -allow-internal-distribution`; + + // Swap in the recomposed xcframework + fs.removeSync(xcfwPath); + fs.renameSync(xcfwNew, xcfwPath); + + // Clean up standalone macOS dir (now included in universal) + fs.removeSync(path.join(frameworksDir, 'macosx')); + + echo('Recomposed xcframework:'); + await $`ls -la ${xcfwPath}/`; + + return true; +} + +// --- CLI dispatch --- + +const { positionals } = parseArgs({ + allowPositionals: true, + strict: false, +}); + +const [command, ...args] = positionals; + +switch (command) { + case 'download-hermes': { + const buildType = args[0] || 'Debug'; + const result = await downloadUpstreamHermesTarball(buildType); + if (result != null) { + setActionOutput('tarball', result.tarballPath); + setActionOutput('version', result.version); + echo(`Downloaded upstream Hermes tarball for version ${result.version}`); + } else { + echo('No upstream tarball available'); + } + break; + } + case 'recompose-xcframework': { + const [tarball, destroot] = args; + if (!tarball || !destroot) { + echo('Usage: node resolve-hermes.mts recompose-xcframework '); + process.exit(1); + } + const recomposed = await recomposeHermesXcframework(tarball, destroot); + setActionOutput('recomposed', String(recomposed)); + break; + } + case 'resolve-commit': { + const { commit } = hermesCommitAtMergeBase(); + setActionOutput('hermes-commit', commit); + echo(`Resolved Hermes commit: ${commit}`); + break; + } + default: + echo(`Unknown command: ${command ?? '(none)'}. Available: download-hermes, recompose-xcframework, resolve-commit`); + process.exit(1); +} diff --git a/.github/workflows/microsoft-pr.yml b/.github/workflows/microsoft-pr.yml index c20f2c7edfc..157cd8b05fa 100644 --- a/.github/workflows/microsoft-pr.yml +++ b/.github/workflows/microsoft-pr.yml @@ -132,10 +132,10 @@ jobs: permissions: {} uses: ./.github/workflows/microsoft-build-rntester.yml - build-spm: - name: "Build SPM" + prebuild-macos-core: + name: "Prebuild macOS Core" permissions: {} - uses: ./.github/workflows/microsoft-build-spm.yml + uses: ./.github/workflows/microsoft-prebuild-macos-core.yml test-react-native-macos-init: name: "Test react-native-macos init" @@ -162,7 +162,7 @@ jobs: - yarn-constraints - javascript-tests - build-rntester - - build-spm + - prebuild-macos-core - test-react-native-macos-init # - react-native-test-app-integration steps: diff --git a/.github/workflows/microsoft-prebuild-macos-core.yml b/.github/workflows/microsoft-prebuild-macos-core.yml new file mode 100644 index 00000000000..878678e7efe --- /dev/null +++ b/.github/workflows/microsoft-prebuild-macos-core.yml @@ -0,0 +1,185 @@ +name: Prebuild macOS Core + +on: + workflow_call: + +jobs: + resolve-hermes: + name: "Resolve Hermes" + uses: ./.github/workflows/microsoft-resolve-hermes.yml + + build: + name: "Build ${{ matrix.platform }}" + needs: [resolve-hermes] + runs-on: macos-26 + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + platform: [ios, ios-simulator, macos, visionos, visionos-simulator] + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + fetch-depth: 0 + + - name: Restore slice cache + id: cache-slice + uses: actions/cache/restore@v4 + with: + key: v1-macos-core-${{ matrix.platform }}-Debug-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} + path: | + packages/react-native/.build/output/spm/Debug/Build/Products + packages/react-native/.build/headers + + - name: Setup toolchain + if: steps.cache-slice.outputs.cache-hit != 'true' + uses: ./.github/actions/microsoft-setup-toolchain + with: + node-version: '22' + platform: ${{ matrix.platform }} + + - name: Install npm dependencies + if: steps.cache-slice.outputs.cache-hit != 'true' + run: yarn install + + - name: Download Hermes artifacts + if: steps.cache-slice.outputs.cache-hit != 'true' + uses: actions/download-artifact@v4 + with: + name: hermes-artifacts + path: packages/react-native/.build/artifacts/hermes/destroot + + - name: Create Hermes version marker + if: steps.cache-slice.outputs.cache-hit != 'true' + working-directory: packages/react-native + run: | + echo "prebuilt-Debug" > .build/artifacts/hermes/version.txt + + - name: Setup workspace (using prebuilt Hermes) + if: steps.cache-slice.outputs.cache-hit != 'true' + working-directory: packages/react-native + env: + HERMES_VERSION: prebuilt + run: node scripts/ios-prebuild.js -s -f Debug + + - name: Build (${{ matrix.platform }}) + if: steps.cache-slice.outputs.cache-hit != 'true' + working-directory: packages/react-native + run: node scripts/ios-prebuild.js -b -f Debug -p ${{ matrix.platform }} + + - name: Save slice cache + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/') && endsWith(github.ref, '-stable') }} + uses: actions/cache/save@v4 + with: + key: v1-macos-core-${{ matrix.platform }}-Debug-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} + path: | + packages/react-native/.build/output/spm/Debug/Build/Products + packages/react-native/.build/headers + + - name: Upload headers + uses: actions/upload-artifact@v4 + with: + name: prebuild-macos-core-headers-Debug-${{ matrix.platform }} + path: packages/react-native/.build/headers + + - name: Upload slice artifacts + uses: actions/upload-artifact@v4 + with: + name: prebuild-macos-core-slice-Debug-${{ matrix.platform }} + path: packages/react-native/.build/output/spm/Debug/Build/Products + + compose-xcframework: + name: "Compose XCFramework (Debug)" + needs: [build] + if: ${{ always() && !cancelled() && !failure() }} + runs-on: macos-26 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + filter: blob:none + + - name: Restore compose cache + id: cache-xcframework + uses: actions/cache/restore@v4 + with: + key: v1-macos-core-xcframework-Debug-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} + path: | + packages/react-native/.build/output/xcframeworks/ReactCoreDebug.xcframework.tar.gz + packages/react-native/.build/output/xcframeworks/ReactCoreDebug.framework.dSYM.tar.gz + + - name: Setup toolchain + if: steps.cache-xcframework.outputs.cache-hit != 'true' + uses: ./.github/actions/microsoft-setup-toolchain + with: + node-version: '22' + platform: ios + + - name: Install npm dependencies + if: steps.cache-xcframework.outputs.cache-hit != 'true' + run: yarn install + + - name: Download slice artifacts + if: steps.cache-xcframework.outputs.cache-hit != 'true' + uses: actions/download-artifact@v4 + with: + pattern: prebuild-macos-core-slice-Debug-* + path: packages/react-native/.build/output/spm/Debug/Build/Products + merge-multiple: true + + - name: Download headers + if: steps.cache-xcframework.outputs.cache-hit != 'true' + uses: actions/download-artifact@v4 + with: + pattern: prebuild-macos-core-headers-Debug-* + path: packages/react-native/.build/headers + merge-multiple: true + + - name: Verify downloaded artifacts + if: steps.cache-xcframework.outputs.cache-hit != 'true' + run: | + echo "=== Products directory ===" + ls -R packages/react-native/.build/output/spm/Debug/Build/Products/ | head -40 + echo "=== Headers directory ===" + ls packages/react-native/.build/headers/ | head -20 + + - name: Create XCFramework + if: steps.cache-xcframework.outputs.cache-hit != 'true' + working-directory: packages/react-native + run: node scripts/ios-prebuild -c -f Debug + + - name: Compress XCFramework + if: steps.cache-xcframework.outputs.cache-hit != 'true' + run: | + cd packages/react-native/.build/output/xcframeworks/Debug + tar -cz -f ../ReactCoreDebug.xcframework.tar.gz React.xcframework + + - name: Compress dSYMs + if: steps.cache-xcframework.outputs.cache-hit != 'true' + run: | + cd packages/react-native/.build/output/xcframeworks/Debug/Symbols + tar -cz -f ../../ReactCoreDebug.framework.dSYM.tar.gz . + + - name: Save compose cache + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/') && endsWith(github.ref, '-stable') }} + uses: actions/cache/save@v4 + with: + key: v1-macos-core-xcframework-Debug-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} + path: | + packages/react-native/.build/output/xcframeworks/ReactCoreDebug.xcframework.tar.gz + packages/react-native/.build/output/xcframeworks/ReactCoreDebug.framework.dSYM.tar.gz + + - name: Upload XCFramework + uses: actions/upload-artifact@v4 + with: + name: ReactCoreDebug.xcframework.tar.gz + path: packages/react-native/.build/output/xcframeworks/ReactCoreDebug.xcframework.tar.gz + retention-days: 14 + + - name: Upload dSYMs + uses: actions/upload-artifact@v4 + with: + name: ReactCoreDebug.framework.dSYM.tar.gz + path: packages/react-native/.build/output/xcframeworks/ReactCoreDebug.framework.dSYM.tar.gz + retention-days: 14 diff --git a/.github/workflows/microsoft-build-spm.yml b/.github/workflows/microsoft-resolve-hermes.yml similarity index 60% rename from .github/workflows/microsoft-build-spm.yml rename to .github/workflows/microsoft-resolve-hermes.yml index ae9752c507f..78f590715dd 100644 --- a/.github/workflows/microsoft-build-spm.yml +++ b/.github/workflows/microsoft-resolve-hermes.yml @@ -1,44 +1,81 @@ -name: Build SwiftPM +# Resolve Hermes — reusable workflow called by microsoft-prebuild-macos-core.yml +# +# Strategy (fast path first): +# 1. Download upstream Hermes tarball from Maven/Sonatype +# 2. If found → recompose xcframework (add macOS slice) → upload artifact → done +# 3. If not found → resolve Hermes commit at merge base → check cache → upload if cached +# +# Build-from-source fallback (only when recomposed != true AND cache-hit != true): +# build-hermesc → build 5 platform slices in parallel → assemble universal xcframework +# +name: Resolve Hermes on: workflow_call: jobs: + # --------------------------------------------------------------------------- + # Fast path: download upstream tarball and recompose, or resolve commit + cache + # --------------------------------------------------------------------------- resolve-hermes: name: "Resolve Hermes" runs-on: macos-15 - timeout-minutes: 10 + timeout-minutes: 15 outputs: hermes-commit: ${{ steps.resolve.outputs.hermes-commit }} cache-hit: ${{ steps.cache.outputs.cache-hit }} + recomposed: ${{ steps.recompose.outputs.recomposed }} 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 + - name: Setup toolchain + uses: ./.github/actions/microsoft-setup-toolchain with: node-version: '22' - cache: yarn - registry-url: https://registry.npmjs.org + platform: macos - name: Install npm dependencies run: yarn install + # Step 1: Try to download a prebuilt Hermes tarball from upstream Maven/Sonatype. + # Writes tarball= and version= to $GITHUB_OUTPUT if successful. + - name: Download upstream Hermes tarball + id: download + run: node .github/scripts/resolve-hermes.mts download-hermes Debug + + # Step 2: If tarball found, recompose the xcframework to include the macOS slice + # (or skip if macOS is already present in the universal xcframework). + # Writes recomposed=true/false to $GITHUB_OUTPUT. + - name: Recompose xcframework with macOS slice + id: recompose + if: steps.download.outputs.tarball != '' + run: >- + node .github/scripts/resolve-hermes.mts + recompose-xcframework + "${{ steps.download.outputs.tarball }}" + "${{ github.workspace }}/hermes-destroot" + + # Upload recomposed artifacts — the prebuild-macos-core workflow downloads these + - name: Upload recomposed Hermes artifacts + if: steps.recompose.outputs.recomposed == 'true' + uses: actions/upload-artifact@v4 + with: + name: hermes-artifacts + path: hermes-destroot + retention-days: 30 + + # Step 3 (fallback): No upstream tarball — resolve the Hermes commit hash + # at the merge base with facebook/react-native and check the build cache. - name: Resolve Hermes commit at merge base + if: steps.recompose.outputs.recomposed != 'true' id: resolve - working-directory: packages/react-native - run: | - COMMIT=$(node -e "const {hermesCommitAtMergeBase} = require('./scripts/ios-prebuild/macosVersionResolver'); console.log(hermesCommitAtMergeBase().commit);" 2>&1 | grep -E '^[0-9a-f]{40}$') - echo "hermes-commit=$COMMIT" >> "$GITHUB_OUTPUT" - echo "Resolved Hermes commit: $COMMIT" + run: node .github/scripts/resolve-hermes.mts resolve-commit - name: Restore Hermes cache + if: steps.recompose.outputs.recomposed != 'true' id: cache uses: actions/cache/restore@v4 with: @@ -46,16 +83,20 @@ jobs: path: hermes-destroot - name: Upload cached Hermes artifacts - if: steps.cache.outputs.cache-hit == 'true' + if: steps.recompose.outputs.recomposed != 'true' && steps.cache.outputs.cache-hit == 'true' uses: actions/upload-artifact@v4 with: name: hermes-artifacts path: hermes-destroot retention-days: 30 + # --------------------------------------------------------------------------- + # Build-from-source fallback — only runs when no recomposed or cached artifact + # Pipeline: hermesc (host compiler) → 5 platform slices → assemble xcframework + # --------------------------------------------------------------------------- build-hermesc: name: "Build hermesc" - if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }} + if: ${{ needs.resolve-hermes.outputs.recomposed != 'true' && needs.resolve-hermes.outputs.cache-hit != 'true' }} needs: resolve-hermes runs-on: macos-15 timeout-minutes: 30 @@ -64,8 +105,11 @@ jobs: with: filter: blob:none - - name: Setup Xcode - run: sudo xcode-select --switch /Applications/Xcode_16.2.app + - name: Setup toolchain + uses: ./.github/actions/microsoft-setup-toolchain + with: + platform: macos + cache-npm-dependencies: '' - name: Clone Hermes uses: actions/checkout@v4 @@ -93,7 +137,7 @@ jobs: build-hermes-slice: name: "Hermes ${{ matrix.slice }}" - if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }} + if: ${{ needs.resolve-hermes.outputs.recomposed != 'true' && needs.resolve-hermes.outputs.cache-hit != 'true' }} needs: [resolve-hermes, build-hermesc] runs-on: macos-15 timeout-minutes: 45 @@ -101,21 +145,27 @@ jobs: fail-fast: false matrix: slice: [iphoneos, iphonesimulator, macosx, xros, xrsimulator] + include: + - slice: iphoneos + platform: ios + - slice: iphonesimulator + platform: ios + - slice: macosx + platform: macos + - slice: xros + platform: visionos + - slice: xrsimulator + platform: visionos 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: Setup toolchain + uses: ./.github/actions/microsoft-setup-toolchain + with: + platform: ${{ matrix.platform }} + cache-npm-dependencies: '' - name: Clone Hermes uses: actions/checkout@v4 @@ -155,7 +205,7 @@ jobs: assemble-hermes: name: "Assemble Hermes xcframework" - if: ${{ needs.resolve-hermes.outputs.cache-hit != 'true' }} + if: ${{ needs.resolve-hermes.outputs.recomposed != 'true' && needs.resolve-hermes.outputs.cache-hit != 'true' }} needs: [resolve-hermes, build-hermes-slice] runs-on: macos-15 timeout-minutes: 15 @@ -208,50 +258,3 @@ jobs: name: hermes-artifacts path: hermes/destroot retention-days: 30 - - build-spm: - name: "SPM ${{ matrix.platform }}" - needs: [resolve-hermes, assemble-hermes] - # Run when upstream jobs succeeded or were skipped (cache hit) - if: ${{ always() && !cancelled() && !failure() }} - runs-on: macos-26 - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - platform: [ios, macos, visionos] - steps: - - uses: actions/checkout@v4 - with: - filter: blob:none - fetch-depth: 0 - - - name: Setup toolchain - uses: ./.github/actions/microsoft-setup-toolchain - with: - node-version: '22' - platform: ${{ matrix.platform }} - - - name: Install npm dependencies - run: yarn install - - - name: Download Hermes artifacts - uses: actions/download-artifact@v4 - with: - name: hermes-artifacts - path: packages/react-native/.build/artifacts/hermes/destroot - - - name: Create Hermes version marker - working-directory: packages/react-native - run: | - echo "prebuilt-Debug" > .build/artifacts/hermes/version.txt - - - name: Setup SPM workspace (using prebuilt Hermes) - working-directory: packages/react-native - env: - HERMES_VERSION: prebuilt - 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/package.json b/package.json index d57a41c95ab..f262e9fe2a5 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,8 @@ "temp-dir": "^2.0.0", "tinybench": "^3.1.0", "typescript": "5.8.3", - "ws": "^6.2.3" + "ws": "^6.2.3", + "zx": "^8.2.4" }, "resolutions": { "@grpc/proto-loader": "^0.7.8", diff --git a/packages/react-native/scripts/ios-prebuild/cli.js b/packages/react-native/scripts/ios-prebuild/cli.js index c2f3586f46f..e18e663df0a 100644 --- a/packages/react-native/scripts/ios-prebuild/cli.js +++ b/packages/react-native/scripts/ios-prebuild/cli.js @@ -20,6 +20,7 @@ const platforms /*: $ReadOnlyArray */ = [ 'mac-catalyst', 'macos', // [macOS] 'visionos', // [macOS] + 'visionos-simulator', // [macOS] ]; // CI can't use commas in cache keys, so 'macOS,variant=Mac Catalyst' was creating troubles @@ -30,6 +31,7 @@ const platformToDestination /*: $ReadOnly<{|[Platform]: Destination|}> */ = { 'mac-catalyst': 'macOS,variant=Mac Catalyst', macos: 'macOS', // [macOS] visionos: 'visionOS', // [macOS] + 'visionos-simulator': 'visionOS 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 4b997cb4a90..92cc5b5eda6 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -11,7 +11,7 @@ const { findMatchingHermesVersion, hermesCommitAtMergeBase, -} = require('./macosVersionResolver'); // [macOS] +} = require('./microsoft-hermes'); // [macOS] const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); diff --git a/packages/react-native/scripts/ios-prebuild/macosVersionResolver.js b/packages/react-native/scripts/ios-prebuild/microsoft-hermes.js similarity index 95% rename from packages/react-native/scripts/ios-prebuild/macosVersionResolver.js rename to packages/react-native/scripts/ios-prebuild/microsoft-hermes.js index 70b85d3cebe..9bdeec72d8f 100644 --- a/packages/react-native/scripts/ios-prebuild/macosVersionResolver.js +++ b/packages/react-native/scripts/ios-prebuild/microsoft-hermes.js @@ -4,7 +4,11 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * [macOS] Handles version resolution for macOS fork branches. + * [macOS] Resolves Hermes artifacts for macOS fork branches. + * + * Library functions for version resolution and resolving Hermes commits. + * The CI entry point that orchestrates downloading, recomposing, and + * caching is at .github/scripts/resolve-hermes.mts. * * @flow * @format diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index cc0bc9f8ff1..b4efe4fdea1 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -14,7 +14,7 @@ const { findMatchingHermesVersion, findVersionAtMergeBase, getLatestStableVersionFromNPM, -} = require('./macosVersionResolver'); // [macOS] +} = require('./microsoft-hermes'); // [macOS] const {computeNightlyTarballURL, createLogger} = require('./utils'); const {execSync} = require('child_process'); const fs = require('fs'); diff --git a/packages/react-native/scripts/ios-prebuild/types.js b/packages/react-native/scripts/ios-prebuild/types.js index 87f516c5ba9..cdb554815a6 100644 --- a/packages/react-native/scripts/ios-prebuild/types.js +++ b/packages/react-native/scripts/ios-prebuild/types.js @@ -13,15 +13,17 @@ export type Platform = 'ios' | 'ios-simulator' | 'mac-catalyst' | - 'macos' | // [macOS] - 'visionos'; // [macOS] + 'macos' | // [macOS] + 'visionos' | // [macOS] + 'visionos-simulator'; // [macOS] export type Destination = 'iOS' | 'iOS Simulator' | 'macOS,variant=Mac Catalyst' | - 'macOS' | // [macOS] - 'visionOS'; // [macOS] + 'macOS' | // [macOS] + 'visionOS' | // [macOS] + 'visionOS Simulator'; // [macOS] export type BuildFlavor = 'Debug' | 'Release'; */ diff --git a/yarn.lock b/yarn.lock index 6df6b951b68..0fe1ab5a0f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2963,6 +2963,7 @@ __metadata: tinybench: "npm:^3.1.0" typescript: "npm:5.8.3" ws: "npm:^6.2.3" + zx: "npm:^8.2.4" languageName: unknown linkType: soft @@ -14214,3 +14215,12 @@ __metadata: checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c languageName: node linkType: hard + +"zx@npm:^8.2.4": + version: 8.8.5 + resolution: "zx@npm:8.8.5" + bin: + zx: build/cli.js + checksum: 10c0/1273e4f72cfe35a59041aef5a56fd87318bc4e11947d101810b67e5c486ab30574042938728e8a15e085de985e762b8585fcdaab4cf87fd113153b63a5846611 + languageName: node + linkType: hard