[ios][prebuild] refactor header/umbrella/modulemap

To make sure we are exposing the same public API to swift as without prebuilt, this commit changes the follow:

- ModuleMap / Umbrella file is generated from template, not dynamically to align with non-prebuilt (cocoapods)
- Headers are extracted by reading from podspec files and expanding their globs. This is now easy since we can use the podspec_sources function to look for file globs.
This commit is contained in:
Christian Falch
2025-06-26 11:00:13 +02:00
parent 8469327c44
commit ba055ebfda
2 changed files with 873 additions and 318 deletions
+383
View File
@@ -0,0 +1,383 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const regex =
/podspec_sources\s*\(\s*((?:\[[^\]]*\]|"[^"]*"|'[^']*'|[^,])+)\s*,\s*((?:\[[^\]]*\]|"[^"]*"|'[^']*'|[^)])+)\s*\)/gs;
function getHeaderFilesFromPodspecs(
rootFolder /*:string*/,
) /*: { [key: string]: string[] }*/ {
// Find podspec files
const podSpecFiles = glob.sync('**/*.podspec', {
cwd: rootFolder,
absolute: true,
});
const headers /*: { [key: string]: string[] }*/ = {};
podSpecFiles.forEach(podspec => {
const content = fs.readFileSync(podspec, 'utf8');
// Find all podspec_sources calls
let match;
while ((match = regex.exec(content)) !== null) {
if (match) {
let globPatterns /*: string[] */;
let arg2 = match[2]?.trim().replace(/['"]/g, '');
if (!arg2) {
// Skip
return;
}
// Check if arg2 is an array (e.g., ['a', 'b'])
if (arg2.startsWith('[') && arg2.endsWith(']')) {
// Remove the brackets and split by comma
globPatterns = arg2
.slice(1, -1)
.split(',')
.map(item => item.trim());
} else {
globPatterns = [arg2];
}
// Do the glob!
const p = path.resolve(process.cwd(), path.dirname(podspec));
const results = globPatterns
.map(g => {
return glob.sync(g.replace('{h}', 'h'), {
cwd: p,
absolute: true,
});
})
.flat();
if (!headers[podspec]) {
headers[podspec] = results;
} else {
headers[podspec].push(...results);
}
}
}
});
return headers;
}
function includeHeaderFileInUmbrellaFile(
headerFilePath /*: string */,
) /*: boolean */ {
// Check if there is a cpp or mm file with the same name
// const fileName = path.basename(headerFilePath, path.extname(headerFilePath));
// const dirName = path.dirname(headerFilePath);
// const checkFileExists = (extension /*: string */) /*: boolean */ => {
// const cppFilePath = path.join(dirName, fileName + extension);
// if (fs.existsSync(cppFilePath)) {
// const fileStat = fs.statSync(cppFilePath);
// return fileStat.isFile();
// }
// return false;
// };
// if (checkFileExists('.cpp') || checkFileExists('.mm')) {
// // If there is a cpp or mm file with the same name, we assume it is a C++ header file
// return true;
// }
// Check if the file contains c++ code
// const fileContent = fs.readFileSync(headerFilePath, 'utf8');
// return cppHeaderRegex.test(fileContent);
return HEADERFILE_WHITE_LIST.includes(path.basename(headerFilePath));
}
// These are the headers that are included in umbrella files
const HEADERFILE_WHITE_LIST = [
'RCTArchConfiguratorProtocol.h',
'RCTJSRuntimeConfiguratorProtocol.h',
'RCTDependencyProvider.h',
'RCTUIConfiguratorProtocol.h',
'RCTAppDelegate.h',
'RCTAppSetupUtils.h',
'RCTDefaultReactNativeFactoryDelegate.h',
'RCTReactNativeFactory.h',
'RCTRootViewFactory.h',
'CoreModulesPlugins.h',
'RCTAccessibilityManager+Internal.h',
'RCTAccessibilityManager.h',
'RCTActionSheetManager.h',
'RCTAlertController.h',
'RCTAlertManager.h',
'RCTAppearance.h',
'RCTAppState.h',
'RCTClipboard.h',
'RCTDeviceInfo.h',
'RCTDevLoadingView.h',
'RCTDevMenu.h',
'RCTDevSettings.h',
'RCTDevToolsRuntimeSettingsModule.h',
'RCTEventDispatcher.h',
'RCTExceptionsManager.h',
'RCTFPSGraph.h',
'RCTI18nManager.h',
'RCTKeyboardObserver.h',
'RCTLogBox.h',
'RCTLogBoxView.h',
'RCTPlatform.h',
'RCTRedBox.h',
'RCTSourceCode.h',
'RCTStatusBarManager.h',
'RCTTiming.h',
'RCTWebSocketModule.h',
'RCTAssert.h',
'RCTBridge+Inspector.h',
'RCTBridge+Private.h',
'RCTBridge.h',
'RCTBridgeConstants.h',
'RCTBridgeDelegate.h',
'RCTBridgeMethod.h',
'RCTBridgeModule.h',
'RCTBridgeModuleDecorator.h',
'RCTBridgeProxy+Cxx.h',
'RCTBridgeProxy.h',
'RCTBundleManager.h',
'RCTBundleURLProvider.h',
'RCTCallInvoker.h',
'RCTCallInvokerModule.h',
'RCTComponentEvent.h',
'RCTConstants.h',
'RCTConvert.h',
'RCTCxxConvert.h',
'RCTDefines.h',
'RCTDisplayLink.h',
'RCTErrorCustomizer.h',
'RCTErrorInfo.h',
'RCTEventDispatcherProtocol.h',
'RCTFrameUpdate.h',
'RCTImageSource.h',
'RCTInitializing.h',
'RCTInvalidating.h',
'RCTJavaScriptExecutor.h',
'RCTJavaScriptLoader.h',
'RCTJSStackFrame.h',
'RCTJSThread.h',
'RCTKeyCommands.h',
'RCTLog.h',
'RCTManagedPointer.h',
'RCTMockDef.h',
'RCTModuleData.h',
'RCTModuleMethod.h',
'RCTMultipartDataTask.h',
'RCTMultipartStreamReader.h',
'RCTNullability.h',
'RCTParserUtils.h',
'RCTPerformanceLogger.h',
'RCTPerformanceLoggerLabels.h',
'RCTPLTag.h',
'RCTRedBoxSetEnabled.h',
'RCTReloadCommand.h',
'RCTRootContentView.h',
'RCTRootView.h',
'RCTRootViewDelegate.h',
'RCTRootViewInternal.h',
'RCTTouchEvent.h',
'RCTTouchHandler.h',
'RCTTurboModuleRegistry.h',
'RCTURLRequestDelegate.h',
'RCTURLRequestHandler.h',
'RCTUtils.h',
'RCTUtilsUIOverride.h',
'RCTVersion.h',
'RCTSurface.h',
'RCTSurfaceDelegate.h',
'RCTSurfaceProtocol.h',
'RCTSurfaceRootShadowView.h',
'RCTSurfaceRootShadowViewDelegate.h',
'RCTSurfaceRootView.h',
'RCTSurfaceStage.h',
'RCTSurfaceView+Internal.h',
'RCTSurfaceView.h',
'RCTSurfaceHostingProxyRootView.h',
'RCTSurfaceHostingView.h',
'RCTSurfaceSizeMeasureMode.h',
'FBXXHashUtils.h',
'RCTLocalizedString.h',
'RCTEventEmitter.h',
'RCTI18nUtil.h',
'RCTLayoutAnimation.h',
'RCTLayoutAnimationGroup.h',
'RCTRedBoxExtraDataViewController.h',
'RCTSurfacePresenterStub.h',
'RCTUIManager.h',
'RCTUIManagerObserverCoordinator.h',
'RCTUIManagerUtils.h',
'RCTMacros.h',
'RCTProfile.h',
'RCTActivityIndicatorView.h',
'RCTActivityIndicatorViewManager.h',
'RCTAnimationType.h',
'RCTAutoInsetsProtocol.h',
'RCTBorderCurve.h',
'RCTBorderDrawing.h',
'RCTBorderStyle.h',
'RCTComponent.h',
'RCTComponentData.h',
'RCTConvert+CoreLocation.h',
'RCTConvert+Transform.h',
'RCTCursor.h',
'RCTDebuggingOverlay.h',
'RCTDebuggingOverlayManager.h',
'RCTFont.h',
'RCTLayout.h',
'RCTModalHostView.h',
'RCTModalHostViewController.h',
'RCTModalHostViewManager.h',
'RCTModalManager.h',
'RCTPointerEvents.h',
'RCTRootShadowView.h',
'RCTShadowView+Internal.h',
'RCTShadowView+Layout.h',
'RCTShadowView.h',
'RCTSwitch.h',
'RCTSwitchManager.h',
'RCTTextDecorationLineType.h',
'RCTView.h',
'RCTViewManager.h',
'RCTViewUtils.h',
'RCTWrapperViewController.h',
'RCTRefreshableProtocol.h',
'RCTRefreshControl.h',
'RCTRefreshControlManager.h',
'RCTSafeAreaShadowView.h',
'RCTSafeAreaView.h',
'RCTSafeAreaViewLocalData.h',
'RCTSafeAreaViewManager.h',
'RCTScrollableProtocol.h',
'RCTScrollContentShadowView.h',
'RCTScrollContentView.h',
'RCTScrollContentViewManager.h',
'RCTScrollEvent.h',
'RCTScrollView.h',
'RCTScrollViewManager.h',
'UIView+Private.h',
'UIView+React.h',
'RCTDevLoadingViewProtocol.h',
'RCTDevLoadingViewSetEnabled.h',
'RCTInspectorDevServerHelper.h',
'RCTInspectorNetworkHelper.h',
'RCTInspectorUtils.h',
'RCTPackagerClient.h',
'RCTPackagerConnection.h',
'RCTPausedInDebuggerOverlayController.h',
'RCTInspector.h',
'RCTInspectorPackagerConnection.h',
'RCTAnimationDriver.h',
'RCTDecayAnimation.h',
'RCTEventAnimation.h',
'RCTFrameAnimation.h',
'RCTSpringAnimation.h',
'RCTAdditionAnimatedNode.h',
'RCTAnimatedNode.h',
'RCTColorAnimatedNode.h',
'RCTDiffClampAnimatedNode.h',
'RCTDivisionAnimatedNode.h',
'RCTInterpolationAnimatedNode.h',
'RCTModuloAnimatedNode.h',
'RCTMultiplicationAnimatedNode.h',
'RCTObjectAnimatedNode.h',
'RCTPropsAnimatedNode.h',
'RCTStyleAnimatedNode.h',
'RCTSubtractionAnimatedNode.h',
'RCTTrackingAnimatedNode.h',
'RCTTransformAnimatedNode.h',
'RCTValueAnimatedNode.h',
'RCTAnimationPlugins.h',
'RCTAnimationUtils.h',
'RCTNativeAnimatedModule.h',
'RCTNativeAnimatedNodesManager.h',
'RCTNativeAnimatedTurboModule.h',
'RCTBlobManager.h',
'RCTFileReaderModule.h',
'RCTAnimatedImage.h',
'RCTBundleAssetImageLoader.h',
'RCTDisplayWeakRefreshable.h',
'RCTGIFImageDecoder.h',
'RCTImageBlurUtils.h',
'RCTImageCache.h',
'RCTImageDataDecoder.h',
'RCTImageEditingManager.h',
'RCTImageLoader.h',
'RCTImageLoaderLoggable.h',
'RCTImageLoaderProtocol.h',
'RCTImageLoaderWithAttributionProtocol.h',
'RCTImagePlugins.h',
'RCTImageShadowView.h',
'RCTImageStoreManager.h',
'RCTImageURLLoader.h',
'RCTImageURLLoaderWithAttribution.h',
'RCTImageUtils.h',
'RCTImageView.h',
'RCTImageViewManager.h',
'RCTLocalAssetImageLoader.h',
'RCTResizeMode.h',
'RCTUIImageViewAnimated.h',
'RCTLinkingManager.h',
'RCTLinkingPlugins.h',
'RCTDataRequestHandler.h',
'RCTFileRequestHandler.h',
'RCTHTTPRequestHandler.h',
'RCTInspectorNetworkReporter.h',
'RCTNetworking.h',
'RCTNetworkPlugins.h',
'RCTNetworkTask.h',
'RCTPushNotificationManager.h',
'RCTPushNotificationPlugins.h',
'RCTSettingsManager.h',
'RCTSettingsPlugins.h',
'RCTBaseTextShadowView.h',
'RCTBaseTextViewManager.h',
'RCTRawTextShadowView.h',
'RCTRawTextViewManager.h',
'RCTConvert+Text.h',
'RCTTextAttributes.h',
'RCTTextTransform.h',
'NSTextStorage+FontScaling.h',
'RCTDynamicTypeRamp.h',
'RCTTextShadowView.h',
'RCTTextView.h',
'RCTTextViewManager.h',
'RCTMultilineTextInputView.h',
'RCTMultilineTextInputViewManager.h',
'RCTUITextView.h',
'RCTBackedTextInputDelegate.h',
'RCTBackedTextInputDelegateAdapter.h',
'RCTBackedTextInputViewProtocol.h',
'RCTBaseTextInputShadowView.h',
'RCTBaseTextInputView.h',
'RCTBaseTextInputViewManager.h',
'RCTInputAccessoryShadowView.h',
'RCTInputAccessoryView.h',
'RCTInputAccessoryViewContent.h',
'RCTInputAccessoryViewManager.h',
'RCTTextSelection.h',
'RCTSinglelineTextInputView.h',
'RCTSinglelineTextInputViewManager.h',
'RCTUITextField.h',
'RCTVirtualTextShadowView.h',
'RCTVirtualTextView.h',
'RCTVirtualTextViewManager.h',
'RCTVibration.h',
'RCTVibrationPlugins.h',
'RCTReconnectingWebSocket.h',
];
module.exports = {
getHeaderFilesFromPodspecs,
includeHeaderFileInUmbrellaFile,
};
+490 -318
View File
@@ -10,69 +10,14 @@
/*:: import type {BuildFlavor} from './types'; */
const {getHeaderFilesFromPodspecs} = require('./headers');
const {createFolderIfNotExists, createLogger} = require('./utils');
const {execSync} = require('child_process');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const frameworkLog = createLogger('XCFramework');
// These are the headers that are ignored by the modulemap.
// The modulemap is required only for Swift files that needs to import the React framework.
// We want to ignore all the headers that contains some C++ symbol, as they will not build with Swift.
const HEADERFILE_IGNORE_LIST = [
'RCTCxxMethod.h',
'RCTComponentViewClassDescriptor.h',
'RCTActivityIndicatorViewManager.h',
'RCTComponentViewDescriptor.h',
'RCTComponentViewFactory.h',
'RCTComponentViewProtocol.h',
'RCTComponentViewRegistry.h',
'RCTConstants.h',
'RCTConversions.h',
'RCTConvert+CoreLocation.h',
'RCTConvert+Text.h',
'RCTConvert+Transform.h',
'RCTFabricComponentsPlugins.h',
'RCTFabricModalHostViewController.h',
'RCTFabricSurface.h',
'RCTFollyConvert.h',
'RCTLegacyViewManagerInteropCoordinatorAdapter.h',
'RCTLinearGradient.h',
'RCTRadialGradient.h',
'RCTMountingManager.h',
'RCTMountingTransactionObserving.h',
'RCTParagraphComponentAccessibilityProvider.h',
'RuntimeExecutor.h',
'RCTSurfacePresenter.h',
'RCTSurfacePresenterBridgeAdapter.h',
'*View.h',
'*ViewProtocol.h',
'*ComponentView.h',
'RCTViewAccessibilityElement.h',
'RCTImageManager*.h',
'RCTImagePrimitivesConversions.h',
'RCTSyncImageManager.h',
'CallbackWrapper.h',
'LongLivedObject.h',
'Bridging.h',
'RCTLegacyViewManagerInteropCoordinator.h',
'conversions.h',
'Float.h',
'Geometry.h',
'ObjCTimerRegistry.h',
'RCTContextContainerHandling.h',
'RCTHost*.h',
'ReactCdp.h',
'RCTInstance.h',
'JsArgumentHelpers-inl.h',
'RCTJscInstance.h',
'RCTJSThreadManager.h',
'YGEnums.h',
'YGNode.h',
];
function buildXCFrameworks(
rootFolder /*: string */,
buildFolder /*: string */,
@@ -88,7 +33,15 @@ function buildXCFrameworks(
'React.xcframework',
);
// Delete all target platform folders (everything but the Headers and Modules folders)
cleanPlatformFolders(outputPath);
try {
fs.rmSync(outputPath, {recursive: true, force: true});
} catch (error) {
frameworkLog(
`Error deleting folder: ${outputPath}. Check if the folder exists.`,
'error',
);
return;
}
// Build the XCFrameworks by using each framework folder as input
const frameworks = frameworkFolders
@@ -113,21 +66,80 @@ function buildXCFrameworks(
return;
}
// Copy header files from the headers folder that we used to build the swift package
const outputHeaderFiles = copyHeaderFiles(
path.join(buildFolder, 'Headers'),
outputPath,
true,
);
// Use the header files from podspecs
const podSpecsWithHeaderFiles = getHeaderFilesFromPodspecs(rootFolder);
// Create the umbrella header file
const umbrellaHeaderFile = createUmbrellaHeaderFile(
outputPath,
outputHeaderFiles,
);
// Delete header files to the output path
const outputHeadersPath = path.join(outputPath, 'Headers');
// Create the module map file
const moduleMapFile = createModuleMapFile(outputPath, umbrellaHeaderFile);
// Store umbrella headers keyed on podspec names
const umbrellaHeaders /*: {[key: string]: string} */ = {};
const copiedHeaderFilesWithPodspecNames /*: {[key: string]: string[]} */ = {};
// Enumerate podspecs and copy headers, create umbrella headers and module map file
Object.keys(podSpecsWithHeaderFiles).forEach(podspec => {
const headerFiles = podSpecsWithHeaderFiles[podspec];
if (headerFiles.length > 0) {
// Get podspec name without directory and extension and make sure it is a valid identifier
// by replacing any non-alphanumeric characters with an underscore.
const podSpecName = path
.basename(podspec, '.podspec')
.replace(/[^a-zA-Z0-9_]/g, '_');
// Create a folder for the podspec in the output headers path
const podSpecFolder = path.join(outputHeadersPath, podSpecName);
createFolderIfNotExists(podSpecFolder);
// Copy each header file to the podspec folder
copiedHeaderFilesWithPodspecNames[podSpecName] = headerFiles.map(
headerFile => {
// Header files shall be flattened into the podSpecFoldder:
const targetFile = path.join(
podSpecFolder,
path.basename(headerFile),
);
fs.copyFileSync(headerFile, targetFile);
return targetFile;
},
);
// Create umbrella header file for the podspec
const umbrellaHeaderFilename = path.join(
podSpecFolder,
podSpecName + '-umbrella.h',
);
// const didCreateUmbrellaFile = createUmbrellaHeaderFile(
// podSpecName,
// umbrellaHeaderFilename,
// copiedHeaderFilesWithPodspecNames[podSpecName],
// );
// // Store the umbrella header filename in the umbrellaHeaders object
// if (didCreateUmbrellaFile) {
// umbrellaHeaders[podSpecName] = umbrellaHeaderFilename;
// }
if (
podSpecName === 'React_Core' ||
podSpecName === 'React_RCTAppDelegate'
) {
if (podSpecName === 'React_Core') {
fs.writeFileSync(umbrellaHeaderFilename, REACT_CORE_UMBRELLA_HEADER);
} else {
fs.writeFileSync(
umbrellaHeaderFilename,
RCT_APP_DELEGATE_UMBRELLA_HEADER,
);
}
// Store the umbrella header filename in the umbrellaHeaders object
umbrellaHeaders[podSpecName] = umbrellaHeaderFilename;
}
}
});
// Create the module map file using the header files in podSpecsWithHeaderFiles
const moduleMapFile = createModuleMapFile(outputPath, umbrellaHeaders);
if (!moduleMapFile) {
frameworkLog(
'Failed to create module map file. The XCFramework may not work correctly. Stopping.',
@@ -136,19 +148,19 @@ function buildXCFrameworks(
return;
}
// Get the platforms in the framework folder and copy modulemaps and headers into each platform folder
linkArchFolders(
outputPath,
moduleMapFile,
umbrellaHeaderFile,
outputHeaderFiles,
umbrellaHeaders,
copiedHeaderFilesWithPodspecNames,
);
// Copy Symbols to symbols folder
const symbolPaths = frameworkFolders.map(framework =>
path.join(framework, `..`, `..`, `React.framework.dSYM`),
);
console.log('Copying symbols to symbols folder...');
frameworkLog('Copying symbols to symbols folder...');
const symbolOutput = path.join(outputPath, '..', 'Symbols');
symbolPaths.forEach(symbol => {
const destination = extractDestinationFromPath(symbol);
@@ -165,79 +177,63 @@ function buildXCFrameworks(
function linkArchFolders(
outputPath /*:string*/,
moduleMapFile /*:string*/,
umbrellaHeaderFile /*:string*/,
outputHeaderFiles /*: Array<string> */,
umbrellaHeaderFiles /*:{[key: string]: string}*/,
outputHeaderFiles /*: {[key: string]: string[]} */,
) {
frameworkLog('Linking modules and headers to platform folders...');
const headerRootFolder = path.dirname(umbrellaHeaderFile);
fs.readdirSync(outputPath)
// Enumerate all platform folders in the output path
const platformFolders = fs
.readdirSync(outputPath)
.map(folder => path.join(outputPath, folder))
.filter(folder => {
const folderPath = path.join(outputPath, folder);
return (
fs.statSync(folderPath).isDirectory() &&
folder !== 'Headers' &&
folder !== 'Modules'
fs.statSync(folder).isDirectory() &&
!folder.endsWith('Headers') &&
!folder.endsWith('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',
});
platformFolders.forEach(platformFolder => {
// 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)),
);
createFolderIfNotExists(targetModulesFolder);
} 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 umbreall / header files into the platform folder
Object.keys(umbrellaHeaderFiles).forEach(podSpecName => {
const umbrellaHeaderFile = umbrellaHeaderFiles[podSpecName];
// Create the target folder for the umbrella header file
const targetPodSpecFolder = path.join(targetHeadersFolder, podSpecName);
createFolderIfNotExists(targetPodSpecFolder);
// Link the umbrella header file to the target folder
try {
fs.linkSync(
moduleMapFile,
path.join(targetModulesFolder, path.basename(moduleMapFile)),
umbrellaHeaderFile,
path.join(targetPodSpecFolder, path.basename(umbrellaHeaderFile)),
);
} 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.`,
@@ -245,159 +241,36 @@ function linkArchFolders(
);
}
});
}
function copyHeaderFiles(
headersSourceFolder /*: string */,
outputPath /*: string */,
cleanOutputPath /*: boolean */ = false,
) {
// Re-create headers folder in the output XCFramework
const headersTargetFolder = path.join(outputPath, 'Headers');
if (cleanOutputPath && fs.existsSync(headersTargetFolder)) {
// Delete the headers folder if it exists
try {
fs.rmSync(headersTargetFolder, {recursive: true, force: true});
} catch (error) {
frameworkLog(
`Error deleting headers folder: ${error.message}. Check if the folder exists.`,
'warning',
);
}
}
createFolderIfNotExists(headersTargetFolder);
// Now we can copy headers to the headers folder. We need to create the same folder structure as in the
// header files inside the Headers folder:
frameworkLog('Copying header files to: ' + headersTargetFolder);
const headerFiles = glob.sync('**/*.{h,hpp}', {
cwd: headersSourceFolder,
absolute: true,
Object.keys(outputHeaderFiles).forEach(podSpecName => {
outputHeaderFiles[podSpecName].forEach(headerFile => {
// Create the target folder for the umbrella header file
const targetPodSpecFolder = path.join(targetHeadersFolder, podSpecName);
createFolderIfNotExists(targetPodSpecFolder);
// Link the header file to the target folder - here we might have a few files with the same name
// since we're flattening the imports. Yoga has two files - these can be ignored.
const targetHeaderFile = path.join(
targetPodSpecFolder,
path.basename(headerFile),
);
if (!fs.existsSync(targetHeaderFile)) {
try {
fs.linkSync(headerFile, targetHeaderFile);
} catch (error) {
frameworkLog(
`Error linking header file: ${error.message}. Check if the file exists.`,
'error',
);
}
}
});
});
});
const outputHeaderFiles /*: Array<string> */ = [];
headerFiles.forEach(headerFile => {
// The headerFile is a full path to a header file. We need to get the relative path based on the
// rootpath parameter.
const relativePath = path.relative(headersSourceFolder, headerFile);
const targetFolder = path.join(
headersTargetFolder,
path.dirname(relativePath),
);
const targetFile = path.join(headersTargetFolder, relativePath);
createFolderIfNotExists(targetFolder);
try {
// Check if the file contains c++ code
fs.copyFileSync(headerFile, targetFile);
outputHeaderFiles.push(targetFile);
} catch (error) {
frameworkLog(
`Error copying header file: ${error.message}. Check if the file exists.`,
'warning',
);
}
});
return outputHeaderFiles;
}
function createUmbrellaHeaderFile(
outputPath /*: string */,
headerFiles /*: Array<string> */,
) /*: string */ {
// Create the umbrella header file
const umbrellaHeaderPath = path.join(outputPath, 'Headers');
const umbrellaHeaderFile = path.join(umbrellaHeaderPath, 'React-umbrella.h');
frameworkLog('Creating umbrella header file: ' + umbrellaHeaderFile);
// Create the umbrella header file
let umbrellaHeader = `
// Generated by React Native
// Do not edit this file directly. It is generated by the React Native build process.
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
`;
headerFiles.forEach(headerFile => {
if (!isCppHeaderFile(headerFile) && !isHeaderFileIgnored(headerFile)) {
// The headerFile is a full path to a header file. We need to get the relative path based on the
// rootpath parameter.
const relativePath = path.relative(umbrellaHeaderPath, headerFile);
umbrellaHeader += `#import "${relativePath}"\n`;
}
});
umbrellaHeader += '\n';
// Write out the umbrella header file
try {
fs.writeFileSync(umbrellaHeaderFile, umbrellaHeader);
} catch (error) {
frameworkLog(
`Error creating umbrella header file: ${error.message}. Check if the file exists.`,
'warning',
);
}
return umbrellaHeaderFile;
}
// 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
const fileName = path.basename(headerFilePath, path.extname(headerFilePath));
const dirName = path.dirname(headerFilePath);
const checkFileExists = (extension /*: string */) /*: boolean */ => {
const cppFilePath = path.join(dirName, fileName + extension);
if (fs.existsSync(cppFilePath)) {
const fileStat = fs.statSync(cppFilePath);
return fileStat.isFile();
}
return false;
};
if (checkFileExists('.cpp') || checkFileExists('.mm')) {
// If there is a cpp or mm file with the same name, we assume it is a C++ header file
return true;
}
// Check if the file contains c++ code
const fileContent = fs.readFileSync(headerFilePath, 'utf8');
return cppHeaderRegex.test(fileContent);
}
function isHeaderFileIgnored(headerFile /*: string */) /*: boolean */ {
// Check if the header file is in the ignore list
return HEADERFILE_IGNORE_LIST.some(pattern =>
// Glob match the header file name to the ignore list
glob
.sync(pattern, {
cwd: path.dirname(headerFile),
absolute: true,
})
.some(ignoredHeaderFile => {
return path.basename(headerFile) === path.basename(ignoredHeaderFile);
}),
);
}
function createModuleMapFile(
outputPath /*: string */,
umbrellaPath /*: string */,
umbrellaHeaders /*: {[key: string]: string} */,
) {
// Create/get the module map folder
const moduleMapFolder = path.join(outputPath, 'Modules');
@@ -405,51 +278,21 @@ function createModuleMapFile(
// Create the module map file
const moduleMapFile = path.join(moduleMapFolder, 'module.modulemap');
frameworkLog('Creating module map file: ' + moduleMapFile);
const moduleMapContent = `framework module React {
umbrella header "${path.basename(umbrellaPath)}"
export *
module * { export * }
}`;
try {
fs.writeFileSync(moduleMapFile, moduleMapContent);
fs.writeFileSync(moduleMapFile, RN_MODULEMAP);
return moduleMapFile;
} catch (error) {
frameworkLog(
`Error creating module map file: ${error.message}. Check if the file exists.`,
'warning',
'error',
);
return null;
}
}
function cleanPlatformFolders(outputPath /*:string*/) {
if (!fs.existsSync(outputPath)) {
return;
}
const targetPlatformFolders = fs.readdirSync(outputPath).filter(folder => {
const folderPath = path.join(outputPath, folder);
return (
fs.statSync(folderPath).isDirectory() &&
folder !== 'Headers' &&
folder !== 'Modules'
);
});
targetPlatformFolders.forEach(folder => {
const folderPath = path.join(outputPath, folder);
frameworkLog('Deleting folder: ' + folderPath);
try {
fs.rmSync(folderPath, {recursive: true, force: true});
} catch (error) {
frameworkLog(
`Error deleting folder: ${folderPath}. Check if the folder exists.`,
'warning',
);
}
});
}
function extractDestinationFromPath(symbolPath /*: string */) /*: string */ {
if (symbolPath.includes('iphoneos')) {
return 'iphoneos';
@@ -472,7 +315,7 @@ function signXCFramework(
identity /*: string */,
xcframeworkPath /*: string */,
) {
console.log('Signing XCFramework...');
frameworkLog('Signing XCFramework...');
const command = `codesign --timestamp --sign "${identity}" ${xcframeworkPath}`;
execSync(command, {stdio: 'inherit'});
}
@@ -480,3 +323,332 @@ function signXCFramework(
module.exports = {
buildXCFrameworks,
};
const REACT_CORE_UMBRELLA_HEADER = `
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "React/CoreModulesPlugins.h"
#import "React/RCTAccessibilityManager+Internal.h"
#import "React/RCTAccessibilityManager.h"
#import "React/RCTActionSheetManager.h"
#import "React/RCTAlertController.h"
#import "React/RCTAlertManager.h"
#import "React/RCTAppearance.h"
#import "React/RCTAppState.h"
#import "React/RCTClipboard.h"
#import "React/RCTDeviceInfo.h"
#import "React/RCTDevLoadingView.h"
#import "React/RCTDevMenu.h"
#import "React/RCTDevSettings.h"
#import "React/RCTDevToolsRuntimeSettingsModule.h"
#import "React/RCTEventDispatcher.h"
#import "React/RCTExceptionsManager.h"
#import "React/RCTFPSGraph.h"
#import "React/RCTI18nManager.h"
#import "React/RCTKeyboardObserver.h"
#import "React/RCTLogBox.h"
#import "React/RCTLogBoxView.h"
#import "React/RCTPlatform.h"
#import "React/RCTRedBox.h"
#import "React/RCTSourceCode.h"
#import "React/RCTStatusBarManager.h"
#import "React/RCTTiming.h"
#import "React/RCTWebSocketModule.h"
#import "React/RCTAssert.h"
#import "React/RCTBridge+Inspector.h"
#import "React/RCTBridge+Private.h"
#import "React/RCTBridge.h"
#import "React/RCTBridgeConstants.h"
#import "React/RCTBridgeDelegate.h"
#import "React/RCTBridgeMethod.h"
#import "React/RCTBridgeModule.h"
#import "React/RCTBridgeModuleDecorator.h"
#import "React/RCTBridgeProxy+Cxx.h"
#import "React/RCTBridgeProxy.h"
#import "React/RCTBundleManager.h"
#import "React/RCTBundleURLProvider.h"
#import "React/RCTCallInvoker.h"
#import "React/RCTCallInvokerModule.h"
#import "React/RCTComponentEvent.h"
#import "React/RCTConstants.h"
#import "React/RCTConvert.h"
#import "React/RCTCxxConvert.h"
#import "React/RCTDefines.h"
#import "React/RCTDisplayLink.h"
#import "React/RCTErrorCustomizer.h"
#import "React/RCTErrorInfo.h"
#import "React/RCTEventDispatcherProtocol.h"
#import "React/RCTFrameUpdate.h"
#import "React/RCTImageSource.h"
#import "React/RCTInitializing.h"
#import "React/RCTInvalidating.h"
#import "React/RCTJavaScriptExecutor.h"
#import "React/RCTJavaScriptLoader.h"
#import "React/RCTJSStackFrame.h"
#import "React/RCTJSThread.h"
#import "React/RCTKeyCommands.h"
#import "React/RCTLog.h"
#import "React/RCTManagedPointer.h"
#import "React/RCTMockDef.h"
#import "React/RCTModuleData.h"
#import "React/RCTModuleMethod.h"
#import "React/RCTMultipartDataTask.h"
#import "React/RCTMultipartStreamReader.h"
#import "React/RCTNullability.h"
#import "React/RCTParserUtils.h"
#import "React/RCTPerformanceLogger.h"
#import "React/RCTPerformanceLoggerLabels.h"
#import "React/RCTPLTag.h"
#import "React/RCTRedBoxSetEnabled.h"
#import "React/RCTReloadCommand.h"
#import "React/RCTRootContentView.h"
#import "React/RCTRootView.h"
#import "React/RCTRootViewDelegate.h"
#import "React/RCTRootViewInternal.h"
#import "React/RCTTouchEvent.h"
#import "React/RCTTouchHandler.h"
#import "React/RCTTurboModuleRegistry.h"
#import "React/RCTURLRequestDelegate.h"
#import "React/RCTURLRequestHandler.h"
#import "React/RCTUtils.h"
#import "React/RCTUtilsUIOverride.h"
#import "React/RCTVersion.h"
#import "React/RCTSurface.h"
#import "React/RCTSurfaceDelegate.h"
#import "React/RCTSurfaceProtocol.h"
#import "React/RCTSurfaceRootShadowView.h"
#import "React/RCTSurfaceRootShadowViewDelegate.h"
#import "React/RCTSurfaceRootView.h"
#import "React/RCTSurfaceStage.h"
#import "React/RCTSurfaceView+Internal.h"
#import "React/RCTSurfaceView.h"
#import "React/RCTSurfaceHostingProxyRootView.h"
#import "React/RCTSurfaceHostingView.h"
#import "React/RCTSurfaceSizeMeasureMode.h"
#import "React/FBXXHashUtils.h"
#import "React/RCTLocalizedString.h"
#import "React/RCTEventEmitter.h"
#import "React/RCTI18nUtil.h"
#import "React/RCTLayoutAnimation.h"
#import "React/RCTLayoutAnimationGroup.h"
#import "React/RCTRedBoxExtraDataViewController.h"
#import "React/RCTSurfacePresenterStub.h"
#import "React/RCTUIManager.h"
#import "React/RCTUIManagerObserverCoordinator.h"
#import "React/RCTUIManagerUtils.h"
#import "React/RCTMacros.h"
#import "React/RCTProfile.h"
#import "React/RCTActivityIndicatorView.h"
#import "React/RCTActivityIndicatorViewManager.h"
#import "React/RCTAnimationType.h"
#import "React/RCTAutoInsetsProtocol.h"
#import "React/RCTBorderCurve.h"
#import "React/RCTBorderDrawing.h"
#import "React/RCTBorderStyle.h"
#import "React/RCTComponent.h"
#import "React/RCTComponentData.h"
#import "React/RCTConvert+CoreLocation.h"
#import "React/RCTConvert+Transform.h"
#import "React/RCTCursor.h"
#import "React/RCTDebuggingOverlay.h"
#import "React/RCTDebuggingOverlayManager.h"
#import "React/RCTFont.h"
#import "React/RCTLayout.h"
#import "React/RCTModalHostView.h"
#import "React/RCTModalHostViewController.h"
#import "React/RCTModalHostViewManager.h"
#import "React/RCTModalManager.h"
#import "React/RCTPointerEvents.h"
#import "React/RCTRootShadowView.h"
#import "React/RCTShadowView+Internal.h"
#import "React/RCTShadowView+Layout.h"
#import "React/RCTShadowView.h"
#import "React/RCTSwitch.h"
#import "React/RCTSwitchManager.h"
#import "React/RCTTextDecorationLineType.h"
#import "React/RCTView.h"
#import "React/RCTViewManager.h"
#import "React/RCTViewUtils.h"
#import "React/RCTWrapperViewController.h"
#import "React/RCTRefreshableProtocol.h"
#import "React/RCTRefreshControl.h"
#import "React/RCTRefreshControlManager.h"
#import "React/RCTSafeAreaShadowView.h"
#import "React/RCTSafeAreaView.h"
#import "React/RCTSafeAreaViewLocalData.h"
#import "React/RCTSafeAreaViewManager.h"
#import "React/RCTScrollableProtocol.h"
#import "React/RCTScrollContentShadowView.h"
#import "React/RCTScrollContentView.h"
#import "React/RCTScrollContentViewManager.h"
#import "React/RCTScrollEvent.h"
#import "React/RCTScrollView.h"
#import "React/RCTScrollViewManager.h"
#import "React/UIView+Private.h"
#import "React/UIView+React.h"
#import "React/RCTDevLoadingViewProtocol.h"
#import "React/RCTDevLoadingViewSetEnabled.h"
#import "React/RCTInspectorDevServerHelper.h"
#import "React/RCTInspectorNetworkHelper.h"
#import "React/RCTInspectorUtils.h"
#import "React/RCTPackagerClient.h"
#import "React/RCTPackagerConnection.h"
#import "React/RCTPausedInDebuggerOverlayController.h"
#import "React/RCTInspector.h"
#import "React/RCTInspectorPackagerConnection.h"
#import "React/RCTAnimationDriver.h"
#import "React/RCTDecayAnimation.h"
#import "React/RCTEventAnimation.h"
#import "React/RCTFrameAnimation.h"
#import "React/RCTSpringAnimation.h"
#import "React/RCTAdditionAnimatedNode.h"
#import "React/RCTAnimatedNode.h"
#import "React/RCTColorAnimatedNode.h"
#import "React/RCTDiffClampAnimatedNode.h"
#import "React/RCTDivisionAnimatedNode.h"
#import "React/RCTInterpolationAnimatedNode.h"
#import "React/RCTModuloAnimatedNode.h"
#import "React/RCTMultiplicationAnimatedNode.h"
#import "React/RCTObjectAnimatedNode.h"
#import "React/RCTPropsAnimatedNode.h"
#import "React/RCTStyleAnimatedNode.h"
#import "React/RCTSubtractionAnimatedNode.h"
#import "React/RCTTrackingAnimatedNode.h"
#import "React/RCTTransformAnimatedNode.h"
#import "React/RCTValueAnimatedNode.h"
#import "React/RCTAnimationPlugins.h"
#import "React/RCTAnimationUtils.h"
#import "React/RCTNativeAnimatedModule.h"
#import "React/RCTNativeAnimatedNodesManager.h"
#import "React/RCTNativeAnimatedTurboModule.h"
#import "React/RCTBlobManager.h"
#import "React/RCTFileReaderModule.h"
#import "React/RCTAnimatedImage.h"
#import "React/RCTBundleAssetImageLoader.h"
#import "React/RCTDisplayWeakRefreshable.h"
#import "React/RCTGIFImageDecoder.h"
#import "React/RCTImageBlurUtils.h"
#import "React/RCTImageCache.h"
#import "React/RCTImageDataDecoder.h"
#import "React/RCTImageEditingManager.h"
#import "React/RCTImageLoader.h"
#import "React/RCTImageLoaderLoggable.h"
#import "React/RCTImageLoaderProtocol.h"
#import "React/RCTImageLoaderWithAttributionProtocol.h"
#import "React/RCTImagePlugins.h"
#import "React/RCTImageShadowView.h"
#import "React/RCTImageStoreManager.h"
#import "React/RCTImageURLLoader.h"
#import "React/RCTImageURLLoaderWithAttribution.h"
#import "React/RCTImageUtils.h"
#import "React/RCTImageView.h"
#import "React/RCTImageViewManager.h"
#import "React/RCTLocalAssetImageLoader.h"
#import "React/RCTResizeMode.h"
#import "React/RCTUIImageViewAnimated.h"
#import "React/RCTLinkingManager.h"
#import "React/RCTLinkingPlugins.h"
#import "React/RCTDataRequestHandler.h"
#import "React/RCTFileRequestHandler.h"
#import "React/RCTHTTPRequestHandler.h"
#import "React/RCTInspectorNetworkReporter.h"
#import "React/RCTNetworking.h"
#import "React/RCTNetworkPlugins.h"
#import "React/RCTNetworkTask.h"
#import "React/RCTPushNotificationManager.h"
#import "React/RCTPushNotificationPlugins.h"
#import "React/RCTSettingsManager.h"
#import "React/RCTSettingsPlugins.h"
#import "React/RCTBaseTextShadowView.h"
#import "React/RCTBaseTextViewManager.h"
#import "React/RCTRawTextShadowView.h"
#import "React/RCTRawTextViewManager.h"
#import "React/RCTConvert+Text.h"
#import "React/RCTTextAttributes.h"
#import "React/RCTTextTransform.h"
#import "React/NSTextStorage+FontScaling.h"
#import "React/RCTDynamicTypeRamp.h"
#import "React/RCTTextShadowView.h"
#import "React/RCTTextView.h"
#import "React/RCTTextViewManager.h"
#import "React/RCTMultilineTextInputView.h"
#import "React/RCTMultilineTextInputViewManager.h"
#import "React/RCTUITextView.h"
#import "React/RCTBackedTextInputDelegate.h"
#import "React/RCTBackedTextInputDelegateAdapter.h"
#import "React/RCTBackedTextInputViewProtocol.h"
#import "React/RCTBaseTextInputShadowView.h"
#import "React/RCTBaseTextInputView.h"
#import "React/RCTBaseTextInputViewManager.h"
#import "React/RCTInputAccessoryShadowView.h"
#import "React/RCTInputAccessoryView.h"
#import "React/RCTInputAccessoryViewContent.h"
#import "React/RCTInputAccessoryViewManager.h"
#import "React/RCTTextSelection.h"
#import "React/RCTSinglelineTextInputView.h"
#import "React/RCTSinglelineTextInputViewManager.h"
#import "React/RCTUITextField.h"
#import "React/RCTVirtualTextShadowView.h"
#import "React/RCTVirtualTextView.h"
#import "React/RCTVirtualTextViewManager.h"
#import "React/RCTVibration.h"
#import "React/RCTVibrationPlugins.h"
#import "React/RCTReconnectingWebSocket.h"
FOUNDATION_EXPORT double ReactVersionNumber;
FOUNDATION_EXPORT const unsigned char ReactVersionString[];
`;
const RCT_APP_DELEGATE_UMBRELLA_HEADER = `
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "RCTAppDelegate.h"
#import "RCTAppSetupUtils.h"
#import "RCTArchConfiguratorProtocol.h"
#import "RCTDefaultReactNativeFactoryDelegate.h"
#import "RCTDependencyProvider.h"
#import "RCTJSRuntimeConfiguratorProtocol.h"
#import "RCTReactNativeFactory.h"
#import "RCTRootViewFactory.h"
#import "RCTUIConfiguratorProtocol.h"
FOUNDATION_EXPORT double React_RCTAppDelegateVersionNumber;
FOUNDATION_EXPORT const unsigned char React_RCTAppDelegateVersionString[];
`;
const RN_MODULEMAP = `
framework module React {
umbrella header "React_Core/React_Core-umbrella.h"
export *
module * { export * }
}
framework module React_RCTAppDelegate {
umbrella header "React_RCTAppDelegate/React_RCTAppDelegate-umbrella.h"
export *
module * { export * }
}
`;