Skip to content

Commit 381297a

Browse files
Saadnajmiclaude
andcommitted
feat: rebuild Hermes xcframework to include macOS slice from Maven tarball
When downloading prebuilt Hermes from Maven, the tarball contains a standalone macOS framework at destroot/Library/Frameworks/macosx/ but the xcframework at destroot/Library/Frameworks/universal/ does not include it. After extraction, rebuild the xcframework using xcodebuild -create-xcframework to include the macOS slice. This is a workaround until the upstream build-ios-framework.sh change (commit 2) is adopted by Meta's CI pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent aff1a03 commit 381297a

File tree

1 file changed

+115
-0
lines changed
  • packages/react-native/scripts/ios-prebuild

1 file changed

+115
-0
lines changed

packages/react-native/scripts/ios-prebuild/hermes.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ async function prepareHermesArtifactsAsync(
234234
fs.unlinkSync(localPath);
235235
}
236236

237+
// [macOS] The Maven tarball contains a standalone macOS framework at
238+
// destroot/Library/Frameworks/macosx/ but does NOT include it in the
239+
// xcframework at destroot/Library/Frameworks/universal/. SPM's BinaryTarget
240+
// requires an xcframework, so we rebuild it to include the macOS slice.
241+
ensureMacOSSliceInXCFramework(artifactsPath);
242+
// macOS]
243+
237244
return artifactsPath;
238245
}
239246

@@ -525,6 +532,114 @@ async function downloadHermesTarball(
525532
}
526533

527534
// [macOS
535+
/**
536+
* The Hermes tarball from Maven contains a standalone macOS framework at
537+
* destroot/Library/Frameworks/macosx/hermes.framework (or hermesvm.framework)
538+
* but the xcframework at destroot/Library/Frameworks/universal/ does not include
539+
* a macOS slice. This function rebuilds the xcframework to include it.
540+
*
541+
* This is needed because SPM's BinaryTarget requires an xcframework, and
542+
* xcodebuild needs the macOS slice present to build for macOS.
543+
*/
544+
function ensureMacOSSliceInXCFramework(artifactsPath /*: string */) {
545+
const frameworksDir = path.join(
546+
artifactsPath,
547+
'destroot',
548+
'Library',
549+
'Frameworks',
550+
);
551+
const macosDir = path.join(frameworksDir, 'macosx');
552+
const universalDir = path.join(frameworksDir, 'universal');
553+
554+
if (!fs.existsSync(macosDir)) {
555+
hermesLog('No macOS framework found in tarball, skipping xcframework rebuild');
556+
return;
557+
}
558+
559+
// Find the framework name (hermes.framework or hermesvm.framework)
560+
const macosFrameworks = fs.readdirSync(macosDir).filter(f => f.endsWith('.framework'));
561+
if (macosFrameworks.length === 0) {
562+
hermesLog('No .framework found in macosx directory, skipping');
563+
return;
564+
}
565+
const frameworkName = macosFrameworks[0];
566+
567+
// Find the existing xcframework
568+
const xcframeworks = fs.existsSync(universalDir)
569+
? fs.readdirSync(universalDir).filter(f => f.endsWith('.xcframework'))
570+
: [];
571+
572+
if (xcframeworks.length === 0) {
573+
hermesLog('No existing xcframework found, creating one from macOS framework only');
574+
fs.mkdirSync(universalDir, {recursive: true});
575+
const xcframeworkName = frameworkName.replace('.framework', '.xcframework');
576+
execSync(
577+
`xcodebuild -create-xcframework ` +
578+
`-framework "${path.join(macosDir, frameworkName)}" ` +
579+
`-output "${path.join(universalDir, xcframeworkName)}"`,
580+
{stdio: 'inherit'},
581+
);
582+
hermesLog(`Created ${xcframeworkName} with macOS slice`);
583+
return;
584+
}
585+
586+
const xcframeworkName = xcframeworks[0];
587+
const xcframeworkPath = path.join(universalDir, xcframeworkName);
588+
589+
// Check if macOS slice already exists in the xcframework
590+
const existingSlices = fs.readdirSync(xcframeworkPath).filter(d => d.startsWith('macos-'));
591+
if (existingSlices.length > 0) {
592+
hermesLog('macOS slice already present in xcframework, skipping rebuild');
593+
return;
594+
}
595+
596+
hermesLog(`Rebuilding ${xcframeworkName} to include macOS slice...`);
597+
598+
// Collect all existing framework slices from the xcframework
599+
const sliceDirs = fs.readdirSync(xcframeworkPath).filter(d => {
600+
const slicePath = path.join(xcframeworkPath, d);
601+
return fs.statSync(slicePath).isDirectory() && d !== '_CodeSignature';
602+
});
603+
604+
// Build the -framework arguments for xcodebuild -create-xcframework
605+
const frameworkArgs = sliceDirs.map(sliceDir => {
606+
const slicePath = path.join(xcframeworkPath, sliceDir);
607+
const frameworks = fs.readdirSync(slicePath).filter(f => f.endsWith('.framework'));
608+
if (frameworks.length > 0) {
609+
return `-framework "${path.join(slicePath, frameworks[0])}"`;
610+
}
611+
return null;
612+
}).filter(Boolean);
613+
614+
// Add the macOS framework
615+
frameworkArgs.push(`-framework "${path.join(macosDir, frameworkName)}"`);
616+
617+
// Rebuild the xcframework
618+
const tmpXCFramework = path.join(universalDir, `${xcframeworkName}.tmp`);
619+
try {
620+
execSync(
621+
`xcodebuild -create-xcframework ${frameworkArgs.join(' ')} -output "${tmpXCFramework}"`,
622+
{stdio: 'inherit'},
623+
);
624+
625+
// Replace the original xcframework
626+
fs.rmSync(xcframeworkPath, {recursive: true, force: true});
627+
fs.renameSync(tmpXCFramework, xcframeworkPath);
628+
629+
hermesLog(`Rebuilt ${xcframeworkName} with macOS slice added`);
630+
} catch (e) {
631+
// Clean up on failure
632+
if (fs.existsSync(tmpXCFramework)) {
633+
fs.rmSync(tmpXCFramework, {recursive: true, force: true});
634+
}
635+
hermesLog(
636+
`Warning: Failed to rebuild xcframework with macOS slice: ${e.message}. ` +
637+
`The macOS framework is available at ${path.join(macosDir, frameworkName)} ` +
638+
`but could not be added to the xcframework.`,
639+
);
640+
}
641+
}
642+
528643
/**
529644
* Handles the case where no prebuilt Hermes artifacts are available.
530645
* Determines the Hermes commit at the merge base with facebook/react-native

0 commit comments

Comments
 (0)