Update the xcframework.js script to support swift (#52135)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52135

Update the xcframework.js script to support Swift

## Context
This PR introduces the first working version of building React Native apps on iOS using prebuilt RNCore and cocoapods.

- Added React-Core-prebuilt.podspec for installing/consuming XCFrameworks
- Added logic in react_native_pods.rb for switching between build from source and using prebuilts
- Added rncore.rb - utilities for the ReactCore prebuilts
- Updated rndependencies with some extra error handling modelled after rncode.rb
- Added support for hard linking headers and modules in each inner framework in the XCFramework in xcframework.js

## Swift:
To enable support for the objective-c types from swift, the swift compiler uses a module map to gather exports from the framework (module.modulemap). This file basically points to an umbrella header file that exports the valid objective-c types (non c++) to Swift. In addition these files are read from the DerivedData and not the project source - so it is a bit hard to control everyting.

I was initially not able to use cocoapods own module definitions (module_name, module_file props) to use a custom module map. I finally found that these files are expected in the deriveddata (build folder) where only the active inner framework is copied - so then I had to hard link both module map and header files for each arch.

## Changelog:

[INTERNAL] - Update the xcframework.js script to support Swift

Pull Request resolved: https://github.com/facebook/react-native/pull/52109

Test Plan:
Run with RN Tester. We need to remove all extra pods from RNTester pod file since none of them are yet compatible with prebuilt (they reference non-prebuilt pods)

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D76980285

Pulled By: cipolleschi

fbshipit-source-id: 4e5486b79c406ba4b375e2ada24cbe5450e2346f
This commit is contained in:
Christian Falch
2025-06-23 08:13:56 -07:00
committed by Facebook GitHub Bot
parent 152cb538f6
commit 69e028da45
+115 -5
View File
@@ -69,6 +69,8 @@ const HEADERFILE_IGNORE_LIST = [
'JsArgumentHelpers-inl.h',
'RCTJscInstance.h',
'RCTJSThreadManager.h',
'YGEnums.h',
'YGNode.h',
];
function buildXCFrameworks(
@@ -106,8 +108,9 @@ function buildXCFrameworks(
} catch (error) {
frameworkLog(
`Error building XCFramework: ${error.message}. Check if the build was successful.`,
'warning',
'error',
);
return;
}
// Copy header files from the headers folder that we used to build the swift package
@@ -124,7 +127,22 @@ function buildXCFrameworks(
);
// Create the module map file
createModuleMapFile(outputPath, umbrellaHeaderFile);
const moduleMapFile = createModuleMapFile(outputPath, umbrellaHeaderFile);
if (!moduleMapFile) {
frameworkLog(
'Failed to create module map file. The XCFramework may not work correctly. Stopping.',
'error',
);
return;
}
// Get the platforms in the framework folder and copy modulemaps and headers into each platform folder
linkArchFolders(
outputPath,
moduleMapFile,
umbrellaHeaderFile,
outputHeaderFiles,
);
// Copy Symbols to symbols folder
const symbolPaths = frameworkFolders.map(framework =>
@@ -144,6 +162,91 @@ function buildXCFrameworks(
}
}
function linkArchFolders(
outputPath /*:string*/,
moduleMapFile /*:string*/,
umbrellaHeaderFile /*:string*/,
outputHeaderFiles /*: Array<string> */,
) {
frameworkLog('Linking modules and headers to platform folders...');
const headerRootFolder = path.dirname(umbrellaHeaderFile);
fs.readdirSync(outputPath)
.filter(folder => {
const folderPath = path.join(outputPath, folder);
return (
fs.statSync(folderPath).isDirectory() &&
folder !== 'Headers' &&
folder !== 'Modules'
);
})
.forEach(folder => {
// Get full platform folder path
const platformFolder = path.join(outputPath, folder);
// Link the Modules folder into the platform folder
const targetModulesFolder = path.join(
platformFolder,
'React.Framework',
'Modules',
);
createFolderIfNotExists(targetModulesFolder);
try {
fs.linkSync(
moduleMapFile,
path.join(targetModulesFolder, path.basename(moduleMapFile)),
);
} catch (error) {
frameworkLog(
`Error copying module map file: ${error.message}. Check if the file exists at ${moduleMapFile}.`,
'error',
);
}
// Copy headers folder into the platform folder
const targetHeadersFolder = path.join(
platformFolder,
'React.Framework',
'Headers',
);
// Link header files into the platform folder
outputHeaderFiles.forEach(headerFile => {
// Get the relative path of the header file based on the root folder
const relativePath = path.relative(headerRootFolder, headerFile);
// Create the target folder for the header file
const targetFolder = path.join(
targetHeadersFolder,
path.dirname(relativePath),
);
// Create the target folder if it doesn't exist
createFolderIfNotExists(targetFolder);
// Link the header file to the target folder
try {
fs.linkSync(
headerFile,
path.join(targetFolder, path.basename(headerFile)),
);
} catch (error) {
frameworkLog(
`Error linking header file: ${error.message}. Check if the file exists.`,
'error',
);
}
});
// Link the umbrella header file to the target headers folder
try {
const targetUmbrellaPath = path.join(
targetHeadersFolder,
'React-umbrella.h',
);
fs.linkSync(umbrellaHeaderFile, targetUmbrellaPath);
} catch (error) {
frameworkLog(
`Error linking umbrella header file: ${error.message}. Check if the file exists.`,
'error',
);
}
});
}
function copyHeaderFiles(
headersSourceFolder /*: string */,
outputPath /*: string */,
@@ -249,7 +352,11 @@ function createUmbrellaHeaderFile(
return umbrellaHeaderFile;
}
const cppHeaderRegex = /(#include|#import)\s*<[^.>]+>|\bnamespace\s+[\w:]+::/;
// This regex matches some C++ construct that might be present in a header file.
// To uniquely identify them. We need to exclude headers with C++ constructs from the module map
// otherwise Swift won't be able to import the React.xcframework
const cppHeaderRegex =
/(#include|#import)\s*<[^.>]+>|\bnamespace\s+[\w:]+::|NS_ENUM\s*\([^)]*\)|NS_OPTIONS\s*\([^)]*\)|typedef\s+enum|static\s+const|@interface|static\s+inline/;
function isCppHeaderFile(headerFilePath /*: string */) /*: boolean */ {
// Check if there is a cpp or mm file with the same name
@@ -299,21 +406,24 @@ function createModuleMapFile(
// Create the module map file
const moduleMapFile = path.join(moduleMapFolder, 'module.modulemap');
frameworkLog('Creating module map file: ' + moduleMapFile);
const moduleMapContent = `module React {
umbrella header "../Headers/${path.basename(umbrellaPath)}"
const moduleMapContent = `framework module React {
umbrella header "${path.basename(umbrellaPath)}"
export *
module * { export * }
}`;
try {
fs.writeFileSync(moduleMapFile, moduleMapContent);
return moduleMapFile;
} catch (error) {
frameworkLog(
`Error creating module map file: ${error.message}. Check if the file exists.`,
'warning',
);
return null;
}
}
function cleanPlatformFolders(outputPath /*:string*/) {
if (!fs.existsSync(outputPath)) {
return;