|
4 | 4 | * This source code is licensed under the MIT license found in the |
5 | 5 | * LICENSE file in the root directory of this source tree. |
6 | 6 | * |
7 | | - * [macOS] Handles version resolution for macOS fork branches. |
| 7 | + * [macOS] Resolves Hermes artifacts for macOS fork branches. |
| 8 | + * |
| 9 | + * Handles downloading upstream Hermes tarballs, recomposing xcframeworks |
| 10 | + * to include the macOS slice, and resolving Hermes commits for |
| 11 | + * build-from-source fallback. Used as both a library and CLI: |
| 12 | + * |
| 13 | + * node microsoft-resolveHermes.js download-hermes [Debug|Release] |
| 14 | + * node microsoft-resolveHermes.js recompose-xcframework <tarball> <destroot> |
| 15 | + * node microsoft-resolveHermes.js resolve-commit |
8 | 16 | * |
9 | 17 | * @flow |
10 | 18 | * @format |
@@ -285,15 +293,161 @@ async function downloadUpstreamHermesTarball( |
285 | 293 | return null; |
286 | 294 | } |
287 | 295 |
|
| 296 | +/** |
| 297 | + * Extracts an upstream Hermes tarball and recomposes the xcframework to include |
| 298 | + * the macOS slice, if needed. |
| 299 | + * |
| 300 | + * Upstream tarballs ship a universal xcframework (iOS, simulator, catalyst, |
| 301 | + * tvOS, visionOS) plus a standalone macosx/hermes.framework. This function |
| 302 | + * merges the standalone macOS framework into the universal xcframework using |
| 303 | + * `xcodebuild -create-xcframework`. |
| 304 | + * |
| 305 | + * NOTE: Once upstream Hermes includes macOS in the universal xcframework |
| 306 | + * natively, this function will detect the existing macOS slice and skip |
| 307 | + * the recompose. At that point, this step can be removed entirely. |
| 308 | + * |
| 309 | + * Returns true if the xcframework was recomposed (or already had macOS), |
| 310 | + * false if the tarball is missing the macOS framework entirely. |
| 311 | + */ |
| 312 | +function recomposeHermesXcframework( |
| 313 | + tarballPath /*: string */, |
| 314 | + destroot /*: string */, |
| 315 | +) /*: boolean */ { |
| 316 | + // Extract tarball |
| 317 | + fs.mkdirSync(destroot, {recursive: true}); |
| 318 | + execSync(`tar -xzf "${tarballPath}" -C "${destroot}" --strip-components=2`, { |
| 319 | + stdio: 'inherit', |
| 320 | + }); |
| 321 | + |
| 322 | + const frameworksDir = path.join(destroot, 'Library', 'Frameworks'); |
| 323 | + const xcfwPath = path.join(frameworksDir, 'universal', 'hermes.xcframework'); |
| 324 | + |
| 325 | + macosLog('Upstream tarball contents:'); |
| 326 | + execSync(`ls -la "${frameworksDir}"`, {stdio: 'inherit'}); |
| 327 | + |
| 328 | + // Check if macOS is already in the universal xcframework — if so, no recompose needed |
| 329 | + const xcfwContents = fs.readdirSync(xcfwPath); |
| 330 | + const hasMacSlice = xcfwContents.some( |
| 331 | + entry => entry.startsWith('macos') && entry.includes('arm64'), |
| 332 | + ); |
| 333 | + if (hasMacSlice) { |
| 334 | + macosLog('macOS slice already present in universal xcframework, skipping recompose'); |
| 335 | + // Clean up standalone macOS dir if it exists |
| 336 | + const standaloneMacDir = path.join(frameworksDir, 'macosx'); |
| 337 | + if (fs.existsSync(standaloneMacDir)) { |
| 338 | + fs.rmSync(standaloneMacDir, {recursive: true, force: true}); |
| 339 | + } |
| 340 | + return true; |
| 341 | + } |
| 342 | + |
| 343 | + // Check for standalone macOS framework |
| 344 | + const standaloneMacFw = path.join(frameworksDir, 'macosx', 'hermes.framework'); |
| 345 | + if (!fs.existsSync(standaloneMacFw)) { |
| 346 | + macosLog('Upstream tarball missing macosx/hermes.framework', 'error'); |
| 347 | + return false; |
| 348 | + } |
| 349 | + |
| 350 | + // Collect existing frameworks from inside the universal xcframework |
| 351 | + const frameworks /*: string[] */ = []; |
| 352 | + for (const entry of xcfwContents) { |
| 353 | + const fwPath = path.join(xcfwPath, entry, 'hermes.framework'); |
| 354 | + if (fs.existsSync(fwPath) && fs.statSync(fwPath).isDirectory()) { |
| 355 | + macosLog(`Found slice: ${fwPath}`); |
| 356 | + frameworks.push('-framework', fwPath); |
| 357 | + } |
| 358 | + } |
| 359 | + |
| 360 | + // Add the standalone macOS framework |
| 361 | + macosLog(`Found standalone macOS slice: ${standaloneMacFw}`); |
| 362 | + frameworks.push('-framework', standaloneMacFw); |
| 363 | + |
| 364 | + // Build new xcframework at a temp path (frameworks reference paths inside the old xcfw) |
| 365 | + const xcfwNew = path.join(frameworksDir, 'universal', 'hermes-new.xcframework'); |
| 366 | + macosLog( |
| 367 | + `Creating new universal xcframework with ${frameworks.filter(f => f !== '-framework').length} slices...`, |
| 368 | + ); |
| 369 | + execSync( |
| 370 | + `xcodebuild -create-xcframework ${frameworks.map(f => `"${f}"`).join(' ')} -output "${xcfwNew}" -allow-internal-distribution`, |
| 371 | + {stdio: 'inherit'}, |
| 372 | + ); |
| 373 | + |
| 374 | + // Swap in the recomposed xcframework |
| 375 | + fs.rmSync(xcfwPath, {recursive: true, force: true}); |
| 376 | + fs.renameSync(xcfwNew, xcfwPath); |
| 377 | + |
| 378 | + // Clean up standalone macOS dir (now included in universal) |
| 379 | + fs.rmSync(path.join(frameworksDir, 'macosx'), {recursive: true, force: true}); |
| 380 | + |
| 381 | + macosLog('Recomposed xcframework:'); |
| 382 | + execSync(`ls -la "${xcfwPath}/"`, {stdio: 'inherit'}); |
| 383 | + |
| 384 | + return true; |
| 385 | +} |
| 386 | + |
288 | 387 | function abort(message /*: string */) { |
289 | 388 | macosLog(message, 'error'); |
290 | 389 | throw new Error(message); |
291 | 390 | } |
292 | 391 |
|
| 392 | +/** |
| 393 | + * Appends a key=value pair to the GitHub Actions output file ($GITHUB_OUTPUT). |
| 394 | + * No-op if $GITHUB_OUTPUT is not set (e.g. running locally). |
| 395 | + */ |
| 396 | +function setActionOutput(key /*: string */, value /*: string */) { |
| 397 | + const outputFile = process.env.GITHUB_OUTPUT; |
| 398 | + if (outputFile) { |
| 399 | + fs.appendFileSync(outputFile, `${key}=${value}\n`); |
| 400 | + } |
| 401 | +} |
| 402 | + |
| 403 | +// CLI entry point — writes results to $GITHUB_OUTPUT for GitHub Actions. |
| 404 | +// Usage: |
| 405 | +// node microsoft-resolveHermes.js download-hermes [Debug|Release] |
| 406 | +// node microsoft-resolveHermes.js recompose-xcframework <tarball> <destroot> |
| 407 | +// node microsoft-resolveHermes.js resolve-commit |
| 408 | +if (require.main === module) { |
| 409 | + const [command, ...args] = process.argv.slice(2); |
| 410 | + |
| 411 | + if (command === 'download-hermes') { |
| 412 | + const buildType = args[0] || 'Debug'; |
| 413 | + downloadUpstreamHermesTarball(buildType).then(result => { |
| 414 | + if (result != null) { |
| 415 | + setActionOutput('tarball', result.tarballPath); |
| 416 | + setActionOutput('version', result.version); |
| 417 | + macosLog( |
| 418 | + `Downloaded upstream Hermes tarball for version ${result.version}`, |
| 419 | + ); |
| 420 | + } else { |
| 421 | + macosLog('No upstream tarball available'); |
| 422 | + } |
| 423 | + }); |
| 424 | + } else if (command === 'recompose-xcframework') { |
| 425 | + const [tarball, destroot] = args; |
| 426 | + if (!tarball || !destroot) { |
| 427 | + console.error( |
| 428 | + 'Usage: node microsoft-resolveHermes.js recompose-xcframework <tarball> <destroot>', |
| 429 | + ); |
| 430 | + process.exit(1); |
| 431 | + } |
| 432 | + const recomposed = recomposeHermesXcframework(tarball, destroot); |
| 433 | + setActionOutput('recomposed', String(recomposed)); |
| 434 | + } else if (command === 'resolve-commit') { |
| 435 | + const {commit} = hermesCommitAtMergeBase(); |
| 436 | + setActionOutput('hermes-commit', commit); |
| 437 | + macosLog(`Resolved Hermes commit: ${commit}`); |
| 438 | + } else { |
| 439 | + console.error( |
| 440 | + `Unknown command: ${command ?? '(none)'}. Available: download-hermes, recompose-xcframework, resolve-commit`, |
| 441 | + ); |
| 442 | + process.exit(1); |
| 443 | + } |
| 444 | +} |
| 445 | + |
293 | 446 | module.exports = { |
294 | 447 | findMatchingHermesVersion, |
295 | 448 | hermesCommitAtMergeBase, |
296 | 449 | findVersionAtMergeBase, |
297 | 450 | getLatestStableVersionFromNPM, |
298 | 451 | downloadUpstreamHermesTarball, |
| 452 | + recomposeHermesXcframework, |
299 | 453 | }; |
0 commit comments