From 1c079699b08a5030349198ce3a97b900be370ff0 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Wed, 10 Dec 2025 14:28:50 +0100 Subject: [PATCH 1/6] [ios][precompile] refactor header files generator Replace the regex-based approach for parsing podspec files with a declarative configuration system for header file collection: Add headers-config.js with explicit podspec configurations defining header patterns, directories, and subspecs - Add vfs.js to generate VFS overlay YAML files for Clang virtual file system support - Refactor headers.js to use the new configuration-based approach with support for nested subspecs and path preservation - Update xcframework.js to handle the new header mapping structure with source/target paths - This provides more reliable and maintainable header file collection for XCFramework builds by avoiding fragile regex parsing of Ruby podspec files. --- .../scripts/ios-prebuild/headers-config.js | 888 ++++++++++++++++++ .../scripts/ios-prebuild/headers.js | 187 ++-- .../scripts/ios-prebuild/xcframework.js | 60 +- 3 files changed, 1050 insertions(+), 85 deletions(-) create mode 100644 packages/react-native/scripts/ios-prebuild/headers-config.js diff --git a/packages/react-native/scripts/ios-prebuild/headers-config.js b/packages/react-native/scripts/ios-prebuild/headers-config.js new file mode 100644 index 000000000000..ff51d276b1dd --- /dev/null +++ b/packages/react-native/scripts/ios-prebuild/headers-config.js @@ -0,0 +1,888 @@ +/** + * 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 + */ + +/*:: +export type PodSpecConfiguration = $ReadOnly<{ + name: string, + headerPatterns: Array, + headerDir?: string, + excludePatterns?: Array, + subSpecs?: $ReadOnlyArray, + preservePaths?: Array, +}>; +*/ + +// Remember that our GLOB library doesn't like {h} in its patterns, so we use **/*.h instead of **/*.{h} +const PodSpecConfigurations /*: {[key: string]: PodSpecConfiguration} */ = { + 'Libraries/ActionSheetIOS/React-RCTActionSheet.podspec': { + name: 'React-RCTActionSheet', + headerPatterns: [], + headerDir: 'RCTActionSheet', + }, + + 'Libraries/AppDelegate/React-RCTAppDelegate.podspec': { + name: 'React-RCTAppDelegate', + headerPatterns: ['**/*.h'], + headerDir: '', + }, + + 'Libraries/Blob/React-RCTBlob.podspec': { + name: 'React-RCTBlob', + headerPatterns: ['**/*.h'], + headerDir: 'RCTBlob', + }, + + 'Libraries/FBLazyVector/FBLazyVector.podspec': { + name: 'FBLazyVector', + headerPatterns: ['**/*.h'], + headerDir: 'FBLazyVector', + }, + + 'Libraries/Image/React-RCTImage.podspec': { + name: 'React-RCTImage', + headerPatterns: ['**/*.h'], + headerDir: 'RCTImage', + }, + + 'Libraries/LinkingIOS/React-RCTLinking.podspec': { + name: 'React-RCTLinking', + headerPatterns: [], + headerDir: 'RCTLinking', + }, + + 'Libraries/NativeAnimation/React-RCTAnimation.podspec': { + name: 'React-RCTAnimation', + headerPatterns: ['**/*.h'], + headerDir: 'RCTAnimation', + }, + + 'Libraries/Network/React-RCTNetwork.podspec': { + name: 'React-RCTNetwork', + headerPatterns: [], + headerDir: 'RCTNetwork', + }, + + 'Libraries/PushNotificationIOS/React-RCTPushNotification.podspec': { + name: '', + headerPatterns: [], + headerDir: '', + }, + + 'Libraries/Required/RCTRequired.podspec': { + name: 'RCTRequired', + headerPatterns: ['*.h'], + headerDir: 'RCTRequired', + }, + + 'Libraries/Settings/React-RCTSettings.podspec': { + name: 'React-RCTSettings', + headerPatterns: ['*.h'], + headerDir: 'RCTSettings', + }, + + 'Libraries/Text/React-RCTText.podspec': { + name: 'React-RCTText', + headerPatterns: ['**/*.h'], + headerDir: 'RCTText', + }, + + 'Libraries/TypeSafety/RCTTypeSafety.podspec': { + name: 'RCTTypeSafety', + headerPatterns: ['**/*.h'], + headerDir: 'RCTTypeSafety', + }, + + 'Libraries/Vibration/React-RCTVibration.podspec': { + name: 'React-RCTVibration', + headerPatterns: ['**/*.h'], + headerDir: 'RCTVibration', + }, + + 'React.podspec': {name: '', headerPatterns: [], headerDir: ''}, + + 'React/CoreModules/React-CoreModules.podspec': { + name: 'React-CoreModules', + headerPatterns: ['**/*.h'], + headerDir: 'CoreModules', + excludePatterns: ['PlatformStubs/**/*'], // TODO: Only for iOS! + }, + + 'React/React-RCTFabric.podspec': { + name: 'React-RCTFabric', + headerPatterns: ['Fabric/**/*.h'], + headerDir: 'React', + excludePatterns: ['**/tests/*', '**/android/*'], + }, + + 'React/React-RCTFBReactNativeSpec.podspec': { + name: 'React-RCTFBReactNativeSpec', + headerPatterns: ['FBReactNativeSpec/**/*.h'], + headerDir: 'FBReactNativeSpec', + excludePatterns: ['FBReactNativeSpec/react/renderer/components/**'], + subSpecs: [ + { + name: 'components', + headerPatterns: [ + 'FBReactNativeSpec/react/renderer/components/FBReactNativeSpec/**/*.h', + ], + headerDir: 'react/renderer/components/FBReactNativeSpec', + }, + ], + }, + + 'React/Runtime/React-RCTRuntime.podspec': { + name: 'React-RCTRuntime', + headerPatterns: ['*.h'], + headerDir: 'React', + }, + + 'ReactApple/Libraries/RCTFoundation/RCTDeprecation/RCTDeprecation.podspec': { + name: 'RCTDeprecation', + headerPatterns: ['Exported/*.h'], + headerDir: '', + }, + + 'ReactApple/RCTSwiftUI/RCTSwiftUI.podspec': { + name: 'RCTSwiftUI', + headerPatterns: ['*.h'], + headerDir: 'RCTSwiftUI', + }, + + 'ReactApple/RCTSwiftUIWrapper/RCTSwiftUIWrapper.podspec': { + name: 'RCTSwiftUIWrapper', + headerPatterns: ['*.h'], + headerDir: 'RCTSwiftUIWrapper', + }, + + 'ReactCommon/callinvoker/React-callinvoker.podspec': { + name: 'React-callinvoker', + headerPatterns: ['**/*.h'], + headerDir: 'ReactCommon', + }, + + 'ReactCommon/cxxreact/React-cxxreact.podspec': { + name: 'React-cxxreact', + headerPatterns: ['*.h'], + headerDir: 'cxxreact', + }, + + 'ReactCommon/hermes/executor/React-jsitracing.podspec': { + name: 'React-jsitracing', + headerPatterns: ['JSITracing.h'], + }, + + 'ReactCommon/hermes/React-hermes.podspec': { + name: 'React-hermes', + headerPatterns: [ + 'executor/*.h', + 'inspector-modern/chrome/*.h', + 'executor/HermesExecutorFactory.h', + ], + headerDir: 'reacthermes', + }, + + 'ReactCommon/jserrorhandler/React-jserrorhandler.podspec': { + name: 'React-jserrorhandler', + headerPatterns: ['JsErrorHandler.h', 'StackTraceParser.h'], + headerDir: 'jserrorhandler', + }, + + 'ReactCommon/jsi/React-jsi.podspec': { + name: 'React-jsi', + headerPatterns: ['**/*.h'], + headerDir: 'jsi', + excludePatterns: ['**/test/*'], + }, + + 'ReactCommon/jsiexecutor/React-jsiexecutor.podspec': { + name: 'React-jsiexecutor', + headerPatterns: ['jsireact/*.h'], + headerDir: 'jsireact', + }, + + 'ReactCommon/jsinspector-modern/cdp/React-jsinspectorcdp.podspec': { + name: 'React-jsinspectorcdp', + headerPatterns: ['*.h'], + headerDir: 'jsinspector-modern/cdp', + }, + + 'ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec': { + name: 'React-jsinspectornetwork', + headerPatterns: ['*.h'], + headerDir: 'jsinspector-modern/network', + }, + + 'ReactCommon/jsinspector-modern/React-jsinspector.podspec': { + name: 'React-jsinspector', + headerPatterns: ['*.h'], + headerDir: 'jsinspector-modern', + }, + + 'ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec': { + name: 'React-jsinspectortracing', + headerPatterns: ['*.h'], + headerDir: 'jsinspector-modern/tracing', + }, + + 'ReactCommon/jsitooling/React-jsitooling.podspec': { + name: 'React-jsitooling', + headerPatterns: ['react/runtime/*.h'], + headerDir: 'react/runtime', + }, + + 'ReactCommon/logger/React-logger.podspec': { + name: 'React-logger', + headerPatterns: ['*.h'], + headerDir: 'logger', + }, + + 'ReactCommon/oscompat/React-oscompat.podspec': { + name: 'React-oscompat', + headerPatterns: ['*.h'], + headerDir: 'oscompat', + }, + + 'ReactCommon/React-Fabric.podspec': { + name: 'React-Fabric', + headerPatterns: [], + headerDir: '', + subSpecs: [ + { + name: 'animated', + headerPatterns: ['react/renderer/animated/**/*.h'], + excludePatterns: ['react/renderer/animated/tests'], + headerDir: 'react/renderer/animated', + }, + + { + name: 'animations', + headerPatterns: ['react/renderer/animations/**/*.h'], + excludePatterns: ['react/renderer/animations/tests'], + headerDir: 'react/renderer/animations', + }, + + { + name: 'animationbackend', + headerPatterns: ['react/renderer/animationbackend/**/*.h'], + headerDir: 'react/renderer/animationbackend', + }, + + { + name: 'attributedstring', + headerPatterns: ['react/renderer/attributedstring/**/*.h'], + excludePatterns: ['react/renderer/attributedstring/tests'], + headerDir: 'react/renderer/attributedstring', + }, + + { + name: 'bridging', + headerPatterns: ['react/renderer/bridging/**/*.h'], + excludePatterns: ['react/renderer/bridging/tests'], + headerDir: 'react/renderer/bridging', + }, + + { + name: 'core', + headerPatterns: ['react/renderer/core/**/*.h'], + excludePatterns: ['react/renderer/core/tests'], + headerDir: 'react/renderer/core', + }, + + { + name: 'componentregistry', + headerPatterns: ['react/renderer/componentregistry/*.h'], + headerDir: 'react/renderer/componentregistry', + }, + + { + name: 'componentregistrynative', + headerPatterns: ['react/renderer/componentregistry/native/**/*.h'], + headerDir: 'react/renderer/componentregistry/native', + }, + + { + name: 'components', + headerPatterns: [], + headerDir: '', + subSpecs: [ + { + name: 'root', + headerPatterns: ['react/renderer/components/root/**/*.h'], + excludePatterns: ['react/renderer/components/root/tests'], + headerDir: 'react/renderer/components/root', + }, + { + name: 'view', + headerPatterns: [ + 'react/renderer/components/view/*.h', + 'react/renderer/components/view/platform/cxx/**/*.h', + ], + headerDir: 'react/renderer/components/view', + }, + + { + name: 'scrollview', + headerPatterns: ['react/renderer/components/scrollview/**/*.h'], + headerDir: 'react/renderer/components/scrollview', + excludePatterns: [ + 'react/renderer/components/scrollview/tests', + 'react/renderer/components/scrollview/platform/android', + ], + }, + + { + name: 'legacyviewmanagerinterop', + headerPatterns: [ + 'react/renderer/components/legacyviewmanagerinterop/**/*.h', + ], + excludePatterns: [ + 'react/renderer/components/legacyviewmanagerinterop/tests', + ], + headerDir: 'react/renderer/components/legacyviewmanagerinterop', + }, + ], + }, + + { + name: 'dom', + headerPatterns: ['react/renderer/dom/**/*.h'], + excludePatterns: ['react/renderer/dom/tests'], + headerDir: 'react/renderer/dom', + }, + + { + name: 'scheduler', + headerPatterns: ['react/renderer/scheduler/**/*.h'], + headerDir: 'react/renderer/scheduler', + }, + + { + name: 'imagemanager', + headerPatterns: ['react/renderer/imagemanager/*.h'], + headerDir: 'react/renderer/imagemanager', + }, + + { + name: 'mounting', + headerPatterns: ['react/renderer/mounting/**/*.h'], + excludePatterns: ['react/renderer/mounting/tests'], + headerDir: 'react/renderer/mounting', + }, + + { + name: 'observers', + headerPatterns: [], + subSpecs: [ + { + name: 'events', + headerPatterns: ['react/renderer/observers/events/**/*.h'], + excludePatterns: ['react/renderer/observers/events/tests'], + headerDir: 'react/renderer/observers/events', + }, + ], + }, + + { + name: 'templateprocessor', + headerPatterns: ['react/renderer/templateprocessor/**/*.h'], + excludePatterns: ['react/renderer/templateprocessor/tests'], + headerDir: 'react/renderer/templateprocessor', + }, + + { + name: 'telemetry', + headerPatterns: ['react/renderer/telemetry/**/*.h'], + excludePatterns: ['react/renderer/telemetry/tests'], + headerDir: 'react/renderer/telemetry', + }, + + { + name: 'consistency', + headerPatterns: ['react/renderer/consistency/**/*.h'], + headerDir: 'react/renderer/consistency', + }, + + { + name: 'uimanager', + subSpecs: [ + { + name: 'consistency', + headerPatterns: ['react/renderer/uimanager/consistency/*.h'], + headerDir: 'react/renderer/uimanager/consistency', + }, + ], + + headerPatterns: ['react/renderer/uimanager/*.h'], + headerDir: 'react/renderer/uimanager', + }, + + { + name: 'leakchecker', + headerPatterns: ['react/renderer/leakchecker/**/*.h'], + excludePatterns: ['react/renderer/leakchecker/tests'], + headerDir: 'react/renderer/leakchecker', + }, + ], + }, + + 'ReactCommon/React-FabricComponents.podspec': { + name: 'React-FabricComponents', + headerPatterns: [], + headerDir: '', + subSpecs: [ + { + name: 'components', + headerPatterns: [], + headerDir: '', + subSpecs: [ + { + name: 'inputaccessory', + headerPatterns: ['react/renderer/components/inputaccessory/**/*.h'], + excludePatterns: ['react/renderer/components/inputaccessory/tests'], + headerDir: 'react/renderer/components/inputaccessory', + }, + + { + name: 'modal', + headerPatterns: ['react/renderer/components/modal/*.h'], + excludePatterns: ['react/renderer/components/modal/tests'], + headerDir: 'react/renderer/components/modal', + }, + + { + name: 'safeareaview', + headerPatterns: ['react/renderer/components/safeareaview/**/*.h'], + excludePatterns: ['react/renderer/components/safeareaview/tests'], + headerDir: 'react/renderer/components/safeareaview', + }, + + { + name: 'scrollview', + headerPatterns: [ + 'react/renderer/components/scrollview/*.h', + 'react/renderer/components/scrollview/platform/cxx/**/*.h', + ], + excludePatterns: ['react/renderer/components/scrollview/tests'], + headerDir: 'react/renderer/components/scrollview', + }, + + { + name: 'text', + headerPatterns: [ + 'react/renderer/components/text/*.h', + 'react/renderer/components/text/platform/cxx/**/*.h', + ], + headerDir: 'react/renderer/components/text', + }, + + { + name: 'iostextinput', + headerPatterns: [ + 'react/renderer/components/textinput/*.h', + 'react/renderer/components/textinput/platform/ios/**/*.h', + ], + headerDir: 'react/renderer/components/iostextinput', + }, + + { + name: 'switch', + headerPatterns: [ + 'react/renderer/components/switch/iosswitch/**/*.h', + ], + excludePatterns: [ + 'react/renderer/components/switch/iosswitch/**/MacOS*.{m,mm,cpp,h}', + ], + headerDir: 'react/renderer/components/switch/', + }, + + { + name: 'textinput', + headerPatterns: ['react/renderer/components/textinput/**/*.h'], + headerDir: 'react/renderer/components/textinput', + }, + + { + name: 'unimplementedview', + headerPatterns: [ + 'react/renderer/components/unimplementedview/**/*.h', + ], + excludePatterns: [ + 'react/renderer/components/unimplementedview/tests', + ], + headerDir: 'react/renderer/components/unimplementedview', + }, + + { + name: 'virtualview', + headerPatterns: [ + 'react/renderer/components/virtualview/**/*.{m,mm,cpp,h}', + ], + excludePatterns: ['react/renderer/components/virtualview/tests'], + headerDir: 'react/renderer/components/virtualview', + }, + + { + name: 'virtualviewexperimental', + headerPatterns: [ + 'react/renderer/components/virtualviewexperimental/**/*.h', + ], + excludePatterns: [ + 'react/renderer/components/virtualviewexperimental/tests', + ], + headerDir: 'react/renderer/components/virtualviewexperimental', + }, + + { + name: 'rncore', + headerPatterns: ['react/renderer/components/rncore/**/*.h'], + headerDir: 'react/renderer/components/rncore', + }, + ], + }, + { + name: 'textlayoutmanager', + + headerPatterns: [ + 'react/renderer/textlayoutmanager/platform/ios/**/*.h', + 'react/renderer/textlayoutmanager/*.h', + ], + excludePatterns: [ + 'react/renderer/textlayoutmanager/tests', + 'react/renderer/textlayoutmanager/platform/android', + 'react/renderer/textlayoutmanager/platform/cxx', + ], + headerDir: 'react/renderer/textlayoutmanager', + }, + ], + }, + + 'ReactCommon/React-FabricImage.podspec': { + name: 'React-FabricImage', + headerPatterns: ['react/renderer/components/image/**/*.h'], + excludePatterns: ['react/renderer/components/image/tests'], + headerDir: 'react/renderer/components/image', + }, + + 'ReactCommon/React-Mapbuffer.podspec': { + name: 'React-Mapbuffer', + headerPatterns: ['react/renderer/mapbuffer/*.h'], + headerDir: 'react/renderer/mapbuffer', + }, + + 'ReactCommon/react/debug/React-debug.podspec': { + name: 'React-debug', + headerPatterns: ['**/*.h'], + headerDir: 'react/debug', + }, + + 'ReactCommon/react/featureflags/React-featureflags.podspec': { + name: 'React-featureflags', + headerPatterns: ['**/*.h'], + headerDir: 'react/featureflags', + }, + + 'ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec': + { + name: 'React-NativeModulesApple', + headerPatterns: ['ReactCommon/**/*.h'], + headerDir: 'ReactCommon', + }, + + 'ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec': + { + name: 'React-defaultsnativemodule', + headerPatterns: ['*.h'], + headerDir: 'react/nativemodule/defaults', + }, + + 'ReactCommon/react/nativemodule/dom/React-domnativemodule.podspec': { + name: 'React-domnativemodule', + headerPatterns: ['*.h'], + headerDir: 'react/nativemodule/dom', + }, + + 'ReactCommon/react/nativemodule/featureflags/React-featureflagsnativemodule.podspec': + { + name: 'React-featureflagsnativemodule', + headerPatterns: ['*.h'], + headerDir: 'react/nativemodule/featureflags', + }, + + 'ReactCommon/react/nativemodule/idlecallbacks/React-idlecallbacksnativemodule.podspec': + { + name: 'React-idlecallbacksnativemodule', + headerPatterns: ['*.h'], + headerDir: 'react/nativemodule/idlecallbacks', + }, + + 'ReactCommon/react/nativemodule/microtasks/React-microtasksnativemodule.podspec': + { + name: 'React-microtasksnativemodule', + headerPatterns: ['*.h'], + headerDir: 'react/nativemodule/microtasks', + }, + // We don't need to include samples in our header file structure. + // 'ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec': + // { + // name: 'ReactCommon-Samples', + // headerPatterns: ['**/*.h'], + // headerDir: 'react/nativemodule/samples', + // }, + + 'ReactCommon/react/nativemodule/webperformance/React-webperformancenativemodule.podspec': + { + name: 'React-webperformancenativemodule', + headerPatterns: ['*.h'], + headerDir: 'react/nativemodule/webperformance', + }, + + 'ReactCommon/react/networking/React-networking.podspec': { + name: 'React-networking', + headerPatterns: ['*.h'], + headerDir: 'react/networking', + }, + + 'ReactCommon/react/performance/cdpmetrics/React-performancecdpmetrics.podspec': + { + name: 'React-performancecdpmetrics', + headerPatterns: ['*.h'], + headerDir: 'react/performance/cdpmetrics', + }, + + 'ReactCommon/react/performance/timeline/React-performancetimeline.podspec': { + name: 'React-performancetimeline', + headerPatterns: ['*.h'], + headerDir: 'react/performance/timeline', + }, + + 'ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec': { + name: 'React-rendererconsistency', + headerPatterns: ['*.h'], + headerDir: 'react/renderer/consistency', + }, + + 'ReactCommon/react/renderer/css/React-renderercss.podspec': { + name: 'React-renderercss', + headerPatterns: ['*.h'], + headerDir: 'react/renderer/css', + }, + + 'ReactCommon/react/renderer/debug/React-rendererdebug.podspec': { + name: 'React-rendererdebug', + headerPatterns: ['*.h'], + headerDir: 'react/renderer/debug', + }, + + 'ReactCommon/react/renderer/graphics/React-graphics.podspec': { + name: 'React-graphics', + headerPatterns: ['*.h', 'platform/ios/**/*.h'], + headerDir: 'react/renderer/graphics', + }, + + 'ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec': + { + name: 'React-ImageManager', + headerPatterns: ['**/*.h'], + headerDir: 'react/renderer/imagemanager', + }, + + 'ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec': + { + name: 'React-runtimescheduler', + headerPatterns: ['*.h'], + headerDir: 'react/renderer/runtimescheduler', + }, + + 'ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec': { + name: 'React-RuntimeApple', + headerPatterns: ['ReactCommon/*.h'], + headerDir: 'ReactCommon', + excludePatterns: ['ReactCommon/RCTJscInstance.h'], + }, + + 'ReactCommon/react/runtime/React-RuntimeCore.podspec': { + name: 'React-RuntimeCore', + headerPatterns: ['*.h', 'nativeviewconfig/*.h'], + headerDir: 'react/runtime', + }, + + 'ReactCommon/react/runtime/React-RuntimeHermes.podspec': { + name: 'React-RuntimeHermes', + headerPatterns: ['hermes/*.h'], + headerDir: 'react/runtime/hermes', + }, + + 'ReactCommon/react/timing/React-timing.podspec': { + name: 'React-timing', + headerPatterns: ['**/*.h'], + headerDir: 'react/timing', + }, + + 'ReactCommon/react/utils/React-utils.podspec': { + name: 'React-utils', + headerPatterns: ['*.h', 'platform/ios/**/*.h'], + headerDir: 'react/utils', + excludePatterns: ['tests'], + }, + + 'ReactCommon/ReactCommon.podspec': { + name: 'ReactCommon', + headerPatterns: [], + headerDir: 'ReactCommon', + subSpecs: [ + { + name: 'turbomodule', + headerPatterns: [], + subSpecs: [ + { + name: 'bridging', + headerPatterns: ['react/bridging/**/*.h'], + headerDir: 'react/bridging', + excludePatterns: ['react/bridging/tests/**/*'], + }, + { + name: 'core', + headerPatterns: ['react/nativemodule/core/ReactCommon/**/*.h'], + }, + ], + }, + ], + }, + + 'ReactCommon/reactperflogger/React-perflogger.podspec': { + name: 'React-perflogger', + headerPatterns: ['reactperflogger/*.h', 'fusebox/*.h'], + headerDir: 'reactperflogger', + }, + + 'ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec': { + name: 'React-runtimeexecutor', + headerPatterns: ['ReactCommon/*.h', 'platform/ios/**/*.h'], + headerDir: 'ReactCommon', + }, + + 'ReactCommon/yoga/Yoga.podspec': { + name: 'Yoga', + headerPatterns: ['yoga/**/*.h'], + headerDir: 'yoga', + preservePaths: ['yoga/**/*.h'], + }, + // These should be distributed through the Hermes xcframework. + // 'sdks/hermes/hermes-engine.podspec': + // { + // name: 'hermes-engine', + // headerPatterns: [], + // headerDir: '', + // preservePaths: ['**/*.*'], + // subSpecs: [ + // { + // name: 'Hermes', + // headerPatterns: ['destroot/include/hermes/*.h'], + // headerDir: 'hermes', + // }, + + // { + // name: 'cdp', + // headerPatterns: ['destroot/include/hermes/cdp/*.h'], + // headerDir: 'hermes/cdp', + // }, + + // { + // name: 'inspector', + // headerPatterns: ['destroot/include/hermes/inspector/*.h'], + // headerDir: 'hermes/inspector', + // }, + + // { + // name: 'inspector_chrome', + // headerPatterns: ['destroot/include/hermes/inspector/chrome/*.h'], + // headerDir: 'hermes/inspector/chrome', + // }, + + // { + // name: 'jsi', + // headerPatterns: ['destroot/include/jsi/*.h'], + // headerDir: 'jsi', + // }, + + // { + // name: 'Public', + // headerPatterns: ['public/hermes/Public/*.h'], + // headerDir: 'hermes/Public', + // }, + // ], + // }, + + 'React-Core.podspec': { + name: 'React-Core', + headerPatterns: [], + headerDir: 'React', + subSpecs: [ + { + name: 'Default', + headerPatterns: ['React/**/*.h'], + excludePatterns: [ + 'React/CoreModules/**/*', + 'React/DevSupport/**/*', + 'React/Fabric/**/*', + 'React/FBReactNativeSpec/**/*', + 'React/Tests/**/*', + 'React/Inspector/**/*', + 'React/Runtime/**/*', + 'React/CxxBridge/JSCExecutorFactory.h', + ], + }, + { + name: 'DevSupport', + headerPatterns: ['React/DevSupport/*.h', 'React/Inspector/*.h'], + }, + {name: 'RCTWebSocket', headerPatterns: ['Libraries/WebSocket/*.h']}, + { + name: 'CoreModulesHeaders', + headerPatterns: ['React/CoreModules/**/*.h'], + }, + { + name: 'RCTActionSheetHeaders', + headerPatterns: ['Libraries/ActionSheetIOS/*.h'], + }, + { + name: 'RCTAnimationHeaders', + headerPatterns: ['Libraries/NativeAnimation/{Drivers/*,Nodes/*,*}.h'], + }, + { + name: 'RCTBlobHeaders', + headerPatterns: [ + 'Libraries/Blob/{RCTBlobManager,RCTFileReaderModule}.h', + ], + }, + {name: 'RCTImageHeaders', headerPatterns: ['Libraries/Image/*.h']}, + { + name: 'RCTLinkingHeaders', + headerPatterns: ['Libraries/LinkingIOS/*.h'], + }, + {name: 'RCTNetworkHeaders', headerPatterns: ['Libraries/Network/*.h']}, + { + name: 'RCTPushNotificationHeaders', + headerPatterns: ['Libraries/PushNotificationIOS/*.h'], + }, + { + name: 'RCTSettingsHeaders', + headerPatterns: ['Libraries/Settings/*.h'], + }, + {name: 'RCTTextHeaders', headerPatterns: ['Libraries/Text/**/*.h']}, + { + name: 'RCTVibrationHeaders', + headerPatterns: ['Libraries/Vibration/*.h'], + }, + ], + }, +}; + +module.exports = PodSpecConfigurations; diff --git a/packages/react-native/scripts/ios-prebuild/headers.js b/packages/react-native/scripts/ios-prebuild/headers.js index 8ef1a23bbf0d..563e6703689b 100644 --- a/packages/react-native/scripts/ios-prebuild/headers.js +++ b/packages/react-native/scripts/ios-prebuild/headers.js @@ -8,83 +8,146 @@ * @format */ -const fs = require('fs'); +const PodSpecConfigurations = require('./headers-config'); +const utils = require('./utils'); const path = require('path'); const {globSync} = require('tinyglobby'); +const {createLogger} = utils; +const headersLog = createLogger('headers'); + +/*:: +import type {PodSpecConfiguration} from './headers-config'; +type HeaderMap = { headerDir: string, specName: string, headers: {source: string, target: string}[]}; +*/ + /** - * This regular expression is designed to match function calls to `podspec_sources` within a podspec file. - * - * Example matches: - * 1. `podspec_sources("source1", "sourceForPrebuilds1")` - * - Captures: "source1" as the first argument, "sourceForPrebuilds1" as the second argument. - * - * 2. `podspec_sources(["source1", "source2"], ["sourceForPrebuilds1", "sourceForPrebuilds2"])` - * - Captures: ["source1", "source2"] as the first argument, ["sourceForPrebuilds1", "sourceForPrebuilds2"] as the second argument. - * - * 3. `podspec_sources('source1', ['sourceForPrebuilds1', 'sourceForPrebuilds2'])` - * - Captures: 'source1' as the first argument, ['sourceForPrebuilds1', 'sourceForPrebuilds2'] as the second argument. + * Enumerates all podspec files in the PodSpecConfigurations structure above and maps them to + * their header files based on the configuration. + * @param {*} rootFolder Root folder to search for podspec files + * @param {*} testHeadersFlag Flag to indicate whether to test headers against a test directory + * @param {*} targetTestFolder Target folder to test headers against + * @returns */ -const regex = - /podspec_sources\s*\(\s*((?:\[[^\]]*\]|"[^"]*"|'[^']*'|[^,])+)\s*,\s*((?:\[[^\]]*\]|"[^"]*"|'[^']*'|[^)])+)\s*\)/gs; - function getHeaderFilesFromPodspecs( rootFolder /*:string*/, -) /*: { [key: string]: string[] }*/ { - // Find podspec files - const podSpecFiles = globSync('**/*.podspec', { - cwd: rootFolder, - absolute: true, - onlyFiles: true, - }); +) /*: { [key: string]: HeaderMap[] }*/ { + // Get podspec files in the configuration mapped to configurations + const podSpecFiles = Object.keys(PodSpecConfigurations).map(podspecPath => + path.resolve(rootFolder, podspecPath), + ); + + headersLog('🔍 Collecting header files from podspec configurations...'); + + const headerMaps /*: { [key: string]: HeaderMap[] }*/ = {}; + + podSpecFiles.forEach(podspecPath => { + const key = path.relative(rootFolder, podspecPath); + const podSpecConfig = PodSpecConfigurations[key]; + if ( + !podSpecConfig || + 'name' in podSpecConfig === false || + podSpecConfig.name === '' + ) { + headersLog( + `⚠️ Skipping podspec at ${podspecPath} due to missing or invalid configuration.`, + ); + return; + } + + const podSpecDirectory = path.dirname(podspecPath); + + // Now we can start collecting header files + const processConfig = ( + config /*: PodSpecConfiguration */, + parents /*: Array*/, + ) => { + const {headerDir, headerPatterns, excludePatterns, subSpecs} = config; - 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 - continue; + // Find header files for configuration + const foundHeaderFiles = headerPatterns + .map(pattern => + globSync(pattern, { + cwd: podSpecDirectory, + absolute: true, + ignore: excludePatterns || [], + }), + ) + .flat(); + + let resolvedHeaderDir /*:string */ = headerDir || ''; + + // If headerDir is not set, we need to resolve it against parent specs + if (parents.length > 0 && !headerDir) { + for (let i = parents.length - 1; i >= 0; i--) { + const parentHeaderDir = parents[i].headerDir; + if (parentHeaderDir) { + resolvedHeaderDir = parentHeaderDir; + break; + } } - // 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]; + } + + // If still not resolved, default to spec name + if (!resolvedHeaderDir) { + resolvedHeaderDir = ''; + } + + // Resolve preservePaths from parent specs too + let resolvedPreservePaths = config.preservePaths || []; + if (resolvedPreservePaths.length === 0 && parents.length > 0) { + for (let i = parents.length - 1; i >= 0; i--) { + const parentPreservePaths = parents[i].preservePaths; + if (parentPreservePaths && parentPreservePaths.length > 0) { + resolvedPreservePaths = parentPreservePaths; + break; + } } + } - // Do the glob! - const p = path.resolve(process.cwd(), path.dirname(podspec)); - const results = globPatterns - .map(g => { - return globSync(g.replace('{h}', 'h'), { - cwd: p, + headerMaps[podspecPath] = (headerMaps[podspecPath] || []).concat({ + headerDir: resolvedHeaderDir, + specName: podSpecConfig.name, + headers: foundHeaderFiles.map(headerFile => { + // Check if we have preservePath set for this file - then we need to get the subfolder structure too + // and not just copy to the root of headerDir - we should also ignore the headerDir part of the path + const isPreserved = resolvedPreservePaths.some(preservePattern => { + return globSync(preservePattern, { + cwd: podSpecDirectory, absolute: true, - expandDirectories: false, - }); - }) - .flat(); - - if (!headers[podspec]) { - headers[podspec] = results; - } else { - headers[podspec].push(...results); - } + ignore: excludePatterns || [], + }).includes(headerFile); + }); + + if (isPreserved) { + // Get the subfolder for the header file + const relativePath = path.dirname( + path.relative(podSpecDirectory, headerFile), + ); + return { + source: headerFile, + target: path.join(relativePath, path.basename(headerFile)), + }; + } + return { + source: headerFile, + target: path.join(resolvedHeaderDir, path.basename(headerFile)), + }; + }), + }); + + // Process subSpecs recursively + if (subSpecs && subSpecs.length > 0) { + subSpecs.forEach(subSpecConfig => { + processConfig(subSpecConfig, [config, ...parents]); + }); } - } + }; + + processConfig(podSpecConfig, []); }); - return headers; + return headerMaps; } module.exports = { diff --git a/packages/react-native/scripts/ios-prebuild/xcframework.js b/packages/react-native/scripts/ios-prebuild/xcframework.js index 1ff9f992ca2b..01d93de6454d 100644 --- a/packages/react-native/scripts/ios-prebuild/xcframework.js +++ b/packages/react-native/scripts/ios-prebuild/xcframework.js @@ -114,7 +114,16 @@ function buildXCFrameworks( // Enumerate podspecs and copy headers, create umbrella headers and module map file Object.keys(podSpecsWithHeaderFiles).forEach(podspec => { - const headerFiles = podSpecsWithHeaderFiles[podspec]; + const headerFiles = podSpecsWithHeaderFiles[podspec] + .map(h => h.headers) + .flat(); + + // Use the first podspec spec name as the podspec name (this is the root spec in the podspec file) + const podSpecName = podSpecsWithHeaderFiles[podspec][0].specName.replace( + '-', + '_', + ); + 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. @@ -129,24 +138,24 @@ function buildXCFrameworks( } // Create a folder for the podspec in the output headers path - const podSpecFolder = path.join(outputHeadersPath, podSpecName); - createFolderIfNotExists(podSpecFolder); + const podSpecTargetFolder = path.join(outputHeadersPath, podSpecName); // 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), + const headerFileTargetPath = path.join( + podSpecTargetFolder, + headerFile.target, ); - fs.copyFileSync(headerFile, targetFile); - return targetFile; + createFolderIfNotExists(path.dirname(headerFileTargetPath)); + fs.copyFileSync(headerFile.source, headerFileTargetPath); + return headerFileTargetPath; }, ); + // Create umbrella header file for the podspec const umbrellaHeaderFilename = path.join( - podSpecFolder, + podSpecTargetFolder, podSpecName + '-umbrella.h', ); @@ -183,7 +192,9 @@ function buildXCFrameworks( return; } - linkArchFolders( + // Copy header files and module map file to each platform slice in the XCFramework + copyHeaderFilesToSlices( + rootFolder, outputPath, moduleMapFile, umbrellaHeaders, @@ -253,13 +264,15 @@ function copySymbols( }); } -function linkArchFolders( +// Copy header files and module map file to each platform slice in the XCFramework. +function copyHeaderFilesToSlices( + rootFolder /*:string*/, outputPath /*:string*/, moduleMapFile /*:string*/, umbrellaHeaderFiles /*:{[key: string]: string}*/, outputHeaderFiles /*: {[key: string]: string[]} */, ) { - frameworkLog('Linking modules and headers to platform folders...'); + frameworkLog('Linking modules and headers to platform folders for slice...'); // Enumerate all platform folders in the output path const platformFolders = fs @@ -309,7 +322,7 @@ function linkArchFolders( createFolderIfNotExists(targetPodSpecFolder); // Link the umbrella header file to the target folder try { - fs.linkSync( + fs.copyFileSync( umbrellaHeaderFile, path.join(targetPodSpecFolder, path.basename(umbrellaHeaderFile)), ); @@ -323,21 +336,22 @@ function linkArchFolders( 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. + // Get the relative path from the root Headers folder to preserve directory structure + // headerFile is like /path/to/Headers/Yoga/yoga/style/Style.h + // We need to extract Yoga/yoga/style/Style.h and copy to the same structure in the slice + const rootHeadersFolder = path.join(outputPath, 'Headers'); + const relativeHeaderPath = path.relative(rootHeadersFolder, headerFile); const targetHeaderFile = path.join( - targetPodSpecFolder, - path.basename(headerFile), + targetHeadersFolder, + relativeHeaderPath, ); + createFolderIfNotExists(path.dirname(targetHeaderFile)); if (!fs.existsSync(targetHeaderFile)) { try { - fs.linkSync(headerFile, targetHeaderFile); + fs.copyFileSync(headerFile, targetHeaderFile); } catch (error) { frameworkLog( - `Error linking header file: ${error.message}. Check if the file exists.`, + `Error copying header file: ${error.message}. Check if the file exists.`, 'error', ); } From 60dd56af5cf62811c16fff04437db66b8fc1d68d Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Wed, 10 Dec 2025 20:37:41 +0100 Subject: [PATCH 2/6] codereview: refactor header file generator even more Now it reads from podspec files, except for some special cases that we have in the config file. Updated RCTSwiftUIWrapper.podspec to use podspec_sources (which we use to detect source) I tested this against the header files I got with the previous iteration, and also with the ones installed by Cocoapods. --- .../RCTSwiftUIWrapper.podspec | 3 +- .../scripts/ios-prebuild/headers-config.js | 506 +----------------- .../scripts/ios-prebuild/headers.js | 329 ++++++++---- 3 files changed, 256 insertions(+), 582 deletions(-) diff --git a/packages/react-native/ReactApple/RCTSwiftUIWrapper/RCTSwiftUIWrapper.podspec b/packages/react-native/ReactApple/RCTSwiftUIWrapper/RCTSwiftUIWrapper.podspec index 93bc0e91bb85..135b2bb5ccb0 100644 --- a/packages/react-native/ReactApple/RCTSwiftUIWrapper/RCTSwiftUIWrapper.podspec +++ b/packages/react-native/ReactApple/RCTSwiftUIWrapper/RCTSwiftUIWrapper.podspec @@ -25,12 +25,11 @@ Pod::Spec.new do |s| s.author = "Meta Platforms, Inc. and its affiliates" s.platforms = min_supported_versions s.source = source - s.source_files = "*.{h,m}" + s.source_files = podspec_sources("*.{h,m}", "*.{h}") s.public_header_files = "*.h" s.module_name = "RCTSwiftUIWrapper" s.header_dir = "RCTSwiftUIWrapper" s.dependency "RCTSwiftUI" - s.pod_target_xcconfig = { "SWIFT_VERSION" => "5.0", } diff --git a/packages/react-native/scripts/ios-prebuild/headers-config.js b/packages/react-native/scripts/ios-prebuild/headers-config.js index ff51d276b1dd..a445357b982c 100644 --- a/packages/react-native/scripts/ios-prebuild/headers-config.js +++ b/packages/react-native/scripts/ios-prebuild/headers-config.js @@ -16,168 +16,16 @@ export type PodSpecConfiguration = $ReadOnly<{ excludePatterns?: Array, subSpecs?: $ReadOnlyArray, preservePaths?: Array, -}>; +} | {disabled: true}>; */ -// Remember that our GLOB library doesn't like {h} in its patterns, so we use **/*.h instead of **/*.{h} -const PodSpecConfigurations /*: {[key: string]: PodSpecConfiguration} */ = { - 'Libraries/ActionSheetIOS/React-RCTActionSheet.podspec': { - name: 'React-RCTActionSheet', - headerPatterns: [], - headerDir: 'RCTActionSheet', - }, - - 'Libraries/AppDelegate/React-RCTAppDelegate.podspec': { - name: 'React-RCTAppDelegate', - headerPatterns: ['**/*.h'], - headerDir: '', - }, - - 'Libraries/Blob/React-RCTBlob.podspec': { - name: 'React-RCTBlob', - headerPatterns: ['**/*.h'], - headerDir: 'RCTBlob', - }, - - 'Libraries/FBLazyVector/FBLazyVector.podspec': { - name: 'FBLazyVector', - headerPatterns: ['**/*.h'], - headerDir: 'FBLazyVector', - }, - - 'Libraries/Image/React-RCTImage.podspec': { - name: 'React-RCTImage', - headerPatterns: ['**/*.h'], - headerDir: 'RCTImage', - }, - - 'Libraries/LinkingIOS/React-RCTLinking.podspec': { - name: 'React-RCTLinking', - headerPatterns: [], - headerDir: 'RCTLinking', - }, - - 'Libraries/NativeAnimation/React-RCTAnimation.podspec': { - name: 'React-RCTAnimation', - headerPatterns: ['**/*.h'], - headerDir: 'RCTAnimation', - }, - - 'Libraries/Network/React-RCTNetwork.podspec': { - name: 'React-RCTNetwork', - headerPatterns: [], - headerDir: 'RCTNetwork', - }, - - 'Libraries/PushNotificationIOS/React-RCTPushNotification.podspec': { - name: '', - headerPatterns: [], - headerDir: '', - }, - - 'Libraries/Required/RCTRequired.podspec': { - name: 'RCTRequired', - headerPatterns: ['*.h'], - headerDir: 'RCTRequired', - }, - - 'Libraries/Settings/React-RCTSettings.podspec': { - name: 'React-RCTSettings', - headerPatterns: ['*.h'], - headerDir: 'RCTSettings', - }, - - 'Libraries/Text/React-RCTText.podspec': { - name: 'React-RCTText', - headerPatterns: ['**/*.h'], - headerDir: 'RCTText', - }, - - 'Libraries/TypeSafety/RCTTypeSafety.podspec': { - name: 'RCTTypeSafety', - headerPatterns: ['**/*.h'], - headerDir: 'RCTTypeSafety', - }, - - 'Libraries/Vibration/React-RCTVibration.podspec': { - name: 'React-RCTVibration', - headerPatterns: ['**/*.h'], - headerDir: 'RCTVibration', - }, - - 'React.podspec': {name: '', headerPatterns: [], headerDir: ''}, - - 'React/CoreModules/React-CoreModules.podspec': { - name: 'React-CoreModules', - headerPatterns: ['**/*.h'], - headerDir: 'CoreModules', - excludePatterns: ['PlatformStubs/**/*'], // TODO: Only for iOS! - }, - - 'React/React-RCTFabric.podspec': { - name: 'React-RCTFabric', - headerPatterns: ['Fabric/**/*.h'], - headerDir: 'React', - excludePatterns: ['**/tests/*', '**/android/*'], - }, - - 'React/React-RCTFBReactNativeSpec.podspec': { - name: 'React-RCTFBReactNativeSpec', - headerPatterns: ['FBReactNativeSpec/**/*.h'], - headerDir: 'FBReactNativeSpec', - excludePatterns: ['FBReactNativeSpec/react/renderer/components/**'], - subSpecs: [ - { - name: 'components', - headerPatterns: [ - 'FBReactNativeSpec/react/renderer/components/FBReactNativeSpec/**/*.h', - ], - headerDir: 'react/renderer/components/FBReactNativeSpec', - }, - ], - }, - - 'React/Runtime/React-RCTRuntime.podspec': { - name: 'React-RCTRuntime', - headerPatterns: ['*.h'], - headerDir: 'React', - }, - - 'ReactApple/Libraries/RCTFoundation/RCTDeprecation/RCTDeprecation.podspec': { - name: 'RCTDeprecation', - headerPatterns: ['Exported/*.h'], - headerDir: '', - }, - - 'ReactApple/RCTSwiftUI/RCTSwiftUI.podspec': { - name: 'RCTSwiftUI', - headerPatterns: ['*.h'], - headerDir: 'RCTSwiftUI', - }, - - 'ReactApple/RCTSwiftUIWrapper/RCTSwiftUIWrapper.podspec': { - name: 'RCTSwiftUIWrapper', - headerPatterns: ['*.h'], - headerDir: 'RCTSwiftUIWrapper', - }, - - 'ReactCommon/callinvoker/React-callinvoker.podspec': { - name: 'React-callinvoker', +const PodspecExceptions /*: {[key: string]: PodSpecConfiguration} */ = { + 'ReactCommon/jsi/React-jsi.podspec': { + name: 'React-jsi', headerPatterns: ['**/*.h'], - headerDir: 'ReactCommon', - }, - - 'ReactCommon/cxxreact/React-cxxreact.podspec': { - name: 'React-cxxreact', - headerPatterns: ['*.h'], - headerDir: 'cxxreact', - }, - - 'ReactCommon/hermes/executor/React-jsitracing.podspec': { - name: 'React-jsitracing', - headerPatterns: ['JSITracing.h'], + headerDir: 'jsi', + excludePatterns: ['**/test/*'], }, - 'ReactCommon/hermes/React-hermes.podspec': { name: 'React-hermes', headerPatterns: [ @@ -187,68 +35,6 @@ const PodSpecConfigurations /*: {[key: string]: PodSpecConfiguration} */ = { ], headerDir: 'reacthermes', }, - - 'ReactCommon/jserrorhandler/React-jserrorhandler.podspec': { - name: 'React-jserrorhandler', - headerPatterns: ['JsErrorHandler.h', 'StackTraceParser.h'], - headerDir: 'jserrorhandler', - }, - - 'ReactCommon/jsi/React-jsi.podspec': { - name: 'React-jsi', - headerPatterns: ['**/*.h'], - headerDir: 'jsi', - excludePatterns: ['**/test/*'], - }, - - 'ReactCommon/jsiexecutor/React-jsiexecutor.podspec': { - name: 'React-jsiexecutor', - headerPatterns: ['jsireact/*.h'], - headerDir: 'jsireact', - }, - - 'ReactCommon/jsinspector-modern/cdp/React-jsinspectorcdp.podspec': { - name: 'React-jsinspectorcdp', - headerPatterns: ['*.h'], - headerDir: 'jsinspector-modern/cdp', - }, - - 'ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec': { - name: 'React-jsinspectornetwork', - headerPatterns: ['*.h'], - headerDir: 'jsinspector-modern/network', - }, - - 'ReactCommon/jsinspector-modern/React-jsinspector.podspec': { - name: 'React-jsinspector', - headerPatterns: ['*.h'], - headerDir: 'jsinspector-modern', - }, - - 'ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec': { - name: 'React-jsinspectortracing', - headerPatterns: ['*.h'], - headerDir: 'jsinspector-modern/tracing', - }, - - 'ReactCommon/jsitooling/React-jsitooling.podspec': { - name: 'React-jsitooling', - headerPatterns: ['react/runtime/*.h'], - headerDir: 'react/runtime', - }, - - 'ReactCommon/logger/React-logger.podspec': { - name: 'React-logger', - headerPatterns: ['*.h'], - headerDir: 'logger', - }, - - 'ReactCommon/oscompat/React-oscompat.podspec': { - name: 'React-oscompat', - headerPatterns: ['*.h'], - headerDir: 'oscompat', - }, - 'ReactCommon/React-Fabric.podspec': { name: 'React-Fabric', headerPatterns: [], @@ -431,7 +217,21 @@ const PodSpecConfigurations /*: {[key: string]: PodSpecConfiguration} */ = { }, ], }, - + 'React/React-RCTFBReactNativeSpec.podspec': { + name: 'React-RCTFBReactNativeSpec', + headerPatterns: ['FBReactNativeSpec/**/*.h'], + headerDir: 'FBReactNativeSpec', + excludePatterns: ['FBReactNativeSpec/react/renderer/components/**'], + subSpecs: [ + { + name: 'components', + headerPatterns: [ + 'FBReactNativeSpec/react/renderer/components/FBReactNativeSpec/**/*.h', + ], + headerDir: 'react/renderer/components/FBReactNativeSpec', + }, + ], + }, 'ReactCommon/React-FabricComponents.podspec': { name: 'React-FabricComponents', headerPatterns: [], @@ -562,264 +362,6 @@ const PodSpecConfigurations /*: {[key: string]: PodSpecConfiguration} */ = { }, ], }, - - 'ReactCommon/React-FabricImage.podspec': { - name: 'React-FabricImage', - headerPatterns: ['react/renderer/components/image/**/*.h'], - excludePatterns: ['react/renderer/components/image/tests'], - headerDir: 'react/renderer/components/image', - }, - - 'ReactCommon/React-Mapbuffer.podspec': { - name: 'React-Mapbuffer', - headerPatterns: ['react/renderer/mapbuffer/*.h'], - headerDir: 'react/renderer/mapbuffer', - }, - - 'ReactCommon/react/debug/React-debug.podspec': { - name: 'React-debug', - headerPatterns: ['**/*.h'], - headerDir: 'react/debug', - }, - - 'ReactCommon/react/featureflags/React-featureflags.podspec': { - name: 'React-featureflags', - headerPatterns: ['**/*.h'], - headerDir: 'react/featureflags', - }, - - 'ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec': - { - name: 'React-NativeModulesApple', - headerPatterns: ['ReactCommon/**/*.h'], - headerDir: 'ReactCommon', - }, - - 'ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec': - { - name: 'React-defaultsnativemodule', - headerPatterns: ['*.h'], - headerDir: 'react/nativemodule/defaults', - }, - - 'ReactCommon/react/nativemodule/dom/React-domnativemodule.podspec': { - name: 'React-domnativemodule', - headerPatterns: ['*.h'], - headerDir: 'react/nativemodule/dom', - }, - - 'ReactCommon/react/nativemodule/featureflags/React-featureflagsnativemodule.podspec': - { - name: 'React-featureflagsnativemodule', - headerPatterns: ['*.h'], - headerDir: 'react/nativemodule/featureflags', - }, - - 'ReactCommon/react/nativemodule/idlecallbacks/React-idlecallbacksnativemodule.podspec': - { - name: 'React-idlecallbacksnativemodule', - headerPatterns: ['*.h'], - headerDir: 'react/nativemodule/idlecallbacks', - }, - - 'ReactCommon/react/nativemodule/microtasks/React-microtasksnativemodule.podspec': - { - name: 'React-microtasksnativemodule', - headerPatterns: ['*.h'], - headerDir: 'react/nativemodule/microtasks', - }, - // We don't need to include samples in our header file structure. - // 'ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec': - // { - // name: 'ReactCommon-Samples', - // headerPatterns: ['**/*.h'], - // headerDir: 'react/nativemodule/samples', - // }, - - 'ReactCommon/react/nativemodule/webperformance/React-webperformancenativemodule.podspec': - { - name: 'React-webperformancenativemodule', - headerPatterns: ['*.h'], - headerDir: 'react/nativemodule/webperformance', - }, - - 'ReactCommon/react/networking/React-networking.podspec': { - name: 'React-networking', - headerPatterns: ['*.h'], - headerDir: 'react/networking', - }, - - 'ReactCommon/react/performance/cdpmetrics/React-performancecdpmetrics.podspec': - { - name: 'React-performancecdpmetrics', - headerPatterns: ['*.h'], - headerDir: 'react/performance/cdpmetrics', - }, - - 'ReactCommon/react/performance/timeline/React-performancetimeline.podspec': { - name: 'React-performancetimeline', - headerPatterns: ['*.h'], - headerDir: 'react/performance/timeline', - }, - - 'ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec': { - name: 'React-rendererconsistency', - headerPatterns: ['*.h'], - headerDir: 'react/renderer/consistency', - }, - - 'ReactCommon/react/renderer/css/React-renderercss.podspec': { - name: 'React-renderercss', - headerPatterns: ['*.h'], - headerDir: 'react/renderer/css', - }, - - 'ReactCommon/react/renderer/debug/React-rendererdebug.podspec': { - name: 'React-rendererdebug', - headerPatterns: ['*.h'], - headerDir: 'react/renderer/debug', - }, - - 'ReactCommon/react/renderer/graphics/React-graphics.podspec': { - name: 'React-graphics', - headerPatterns: ['*.h', 'platform/ios/**/*.h'], - headerDir: 'react/renderer/graphics', - }, - - 'ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec': - { - name: 'React-ImageManager', - headerPatterns: ['**/*.h'], - headerDir: 'react/renderer/imagemanager', - }, - - 'ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec': - { - name: 'React-runtimescheduler', - headerPatterns: ['*.h'], - headerDir: 'react/renderer/runtimescheduler', - }, - - 'ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec': { - name: 'React-RuntimeApple', - headerPatterns: ['ReactCommon/*.h'], - headerDir: 'ReactCommon', - excludePatterns: ['ReactCommon/RCTJscInstance.h'], - }, - - 'ReactCommon/react/runtime/React-RuntimeCore.podspec': { - name: 'React-RuntimeCore', - headerPatterns: ['*.h', 'nativeviewconfig/*.h'], - headerDir: 'react/runtime', - }, - - 'ReactCommon/react/runtime/React-RuntimeHermes.podspec': { - name: 'React-RuntimeHermes', - headerPatterns: ['hermes/*.h'], - headerDir: 'react/runtime/hermes', - }, - - 'ReactCommon/react/timing/React-timing.podspec': { - name: 'React-timing', - headerPatterns: ['**/*.h'], - headerDir: 'react/timing', - }, - - 'ReactCommon/react/utils/React-utils.podspec': { - name: 'React-utils', - headerPatterns: ['*.h', 'platform/ios/**/*.h'], - headerDir: 'react/utils', - excludePatterns: ['tests'], - }, - - 'ReactCommon/ReactCommon.podspec': { - name: 'ReactCommon', - headerPatterns: [], - headerDir: 'ReactCommon', - subSpecs: [ - { - name: 'turbomodule', - headerPatterns: [], - subSpecs: [ - { - name: 'bridging', - headerPatterns: ['react/bridging/**/*.h'], - headerDir: 'react/bridging', - excludePatterns: ['react/bridging/tests/**/*'], - }, - { - name: 'core', - headerPatterns: ['react/nativemodule/core/ReactCommon/**/*.h'], - }, - ], - }, - ], - }, - - 'ReactCommon/reactperflogger/React-perflogger.podspec': { - name: 'React-perflogger', - headerPatterns: ['reactperflogger/*.h', 'fusebox/*.h'], - headerDir: 'reactperflogger', - }, - - 'ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec': { - name: 'React-runtimeexecutor', - headerPatterns: ['ReactCommon/*.h', 'platform/ios/**/*.h'], - headerDir: 'ReactCommon', - }, - - 'ReactCommon/yoga/Yoga.podspec': { - name: 'Yoga', - headerPatterns: ['yoga/**/*.h'], - headerDir: 'yoga', - preservePaths: ['yoga/**/*.h'], - }, - // These should be distributed through the Hermes xcframework. - // 'sdks/hermes/hermes-engine.podspec': - // { - // name: 'hermes-engine', - // headerPatterns: [], - // headerDir: '', - // preservePaths: ['**/*.*'], - // subSpecs: [ - // { - // name: 'Hermes', - // headerPatterns: ['destroot/include/hermes/*.h'], - // headerDir: 'hermes', - // }, - - // { - // name: 'cdp', - // headerPatterns: ['destroot/include/hermes/cdp/*.h'], - // headerDir: 'hermes/cdp', - // }, - - // { - // name: 'inspector', - // headerPatterns: ['destroot/include/hermes/inspector/*.h'], - // headerDir: 'hermes/inspector', - // }, - - // { - // name: 'inspector_chrome', - // headerPatterns: ['destroot/include/hermes/inspector/chrome/*.h'], - // headerDir: 'hermes/inspector/chrome', - // }, - - // { - // name: 'jsi', - // headerPatterns: ['destroot/include/jsi/*.h'], - // headerDir: 'jsi', - // }, - - // { - // name: 'Public', - // headerPatterns: ['public/hermes/Public/*.h'], - // headerDir: 'hermes/Public', - // }, - // ], - // }, - 'React-Core.podspec': { name: 'React-Core', headerPatterns: [], @@ -883,6 +425,10 @@ const PodSpecConfigurations /*: {[key: string]: PodSpecConfiguration} */ = { }, ], }, + 'React.podspec': {disabled: true}, + 'Libraries/PushNotificationIOS/React-RCTPushNotification.podspec': { + disabled: true, + }, }; -module.exports = PodSpecConfigurations; +module.exports = {PodspecExceptions}; diff --git a/packages/react-native/scripts/ios-prebuild/headers.js b/packages/react-native/scripts/ios-prebuild/headers.js index 563e6703689b..15a99989be29 100644 --- a/packages/react-native/scripts/ios-prebuild/headers.js +++ b/packages/react-native/scripts/ios-prebuild/headers.js @@ -8,7 +8,7 @@ * @format */ -const PodSpecConfigurations = require('./headers-config'); +const {PodspecExceptions} = require('./headers-config'); const utils = require('./utils'); const path = require('path'); const {globSync} = require('tinyglobby'); @@ -21,131 +21,260 @@ import type {PodSpecConfiguration} from './headers-config'; type HeaderMap = { headerDir: string, specName: string, headers: {source: string, target: string}[]}; */ -/** - * Enumerates all podspec files in the PodSpecConfigurations structure above and maps them to - * their header files based on the configuration. - * @param {*} rootFolder Root folder to search for podspec files - * @param {*} testHeadersFlag Flag to indicate whether to test headers against a test directory - * @param {*} targetTestFolder Target folder to test headers against - * @returns - */ function getHeaderFilesFromPodspecs( rootFolder /*:string*/, ) /*: { [key: string]: HeaderMap[] }*/ { - // Get podspec files in the configuration mapped to configurations - const podSpecFiles = Object.keys(PodSpecConfigurations).map(podspecPath => - path.resolve(rootFolder, podspecPath), + const result /*: { [key: string]: HeaderMap[] }*/ = {}; + + // 1. Find all podspec files in the rootFolder + const podspecFiles = globSync('**/*.podspec', { + cwd: rootFolder, + absolute: true, + ignore: ['**/node_modules/**', '**/Pods/**'], + }); + + headersLog( + '🔍 Collecting header files from all podspec files in the project...', ); - headersLog('🔍 Collecting header files from podspec configurations...'); + // 2. For each podspec file, we would need to parse it and extract header information. We should + // do this by checking if the file contains the text 'podspec_sources'. + podspecFiles.forEach(podspecPath => { + // Check if this podspec has an exception registered + const relativeKey = path.relative(rootFolder, podspecPath); + const exception = PodspecExceptions[relativeKey]; - const headerMaps /*: { [key: string]: HeaderMap[] }*/ = {}; + if (exception) { + // Check if the exception is disabled + if ('disabled' in exception && exception.disabled === true) { + headersLog(`⏭️ Skipping disabled podspec: ${relativeKey}`); + return; + } - podSpecFiles.forEach(podspecPath => { - const key = path.relative(rootFolder, podspecPath); - const podSpecConfig = PodSpecConfigurations[key]; - if ( - !podSpecConfig || - 'name' in podSpecConfig === false || - podSpecConfig.name === '' - ) { - headersLog( - `⚠️ Skipping podspec at ${podspecPath} due to missing or invalid configuration.`, + // Use getHeaderFilesFromPodspec for podspecs with exceptions + const headerMaps = getHeaderFilesFromPodspec( + exception, + path.dirname(podspecPath), ); + if (headerMaps !== null) { + result[podspecPath] = headerMaps; + } return; } - const podSpecDirectory = path.dirname(podspecPath); + // Open file and read content + const fileContent = require('fs').readFileSync(podspecPath, 'utf8'); - // Now we can start collecting header files - const processConfig = ( - config /*: PodSpecConfiguration */, - parents /*: Array*/, - ) => { - const {headerDir, headerPatterns, excludePatterns, subSpecs} = config; + // Check if it contains 'podspec_sources' + if (fileContent.includes('podspec_sources')) { + // Parse podspec_sources(source_files, header_patterns) - we want the SECOND argument. + // Examples: + // podspec_sources("*.{cpp,h}", "**/*.h") + // podspec_sources(["a.m", "b.h"], "*.h") + // podspec_sources(["a.m", "b.h"], ["c.h", "d.h"]) + // podspec_sources(source_files, ["*.h", "platform/ios/**/*.h"]) # first arg is a variable + // + // Regex explanation: + // podspec_sources\( - match "podspec_sources(" + // (?:\[[^\]]*\]|"[^"]*"|[\w]+) - first arg: either [...] or "..." or a variable name + // \s*,\s* - comma separator with optional whitespace + // (\[[^\]]*\]|"[^"]*") - second arg (captured): either [...] or "..." + // \) - closing paren + const headerPatternRegex = + /podspec_sources\((?:\[[^\]]*\]|"[^"]*"|\w+)\s*,\s*(\[[^\]]*\]|"[^"]*")\)/gm; + const matches = [...fileContent.matchAll(headerPatternRegex)]; - // Find header files for configuration - const foundHeaderFiles = headerPatterns - .map(pattern => - globSync(pattern, { - cwd: podSpecDirectory, - absolute: true, - ignore: excludePatterns || [], - }), - ) - .flat(); - - let resolvedHeaderDir /*:string */ = headerDir || ''; - - // If headerDir is not set, we need to resolve it against parent specs - if (parents.length > 0 && !headerDir) { - for (let i = parents.length - 1; i >= 0; i--) { - const parentHeaderDir = parents[i].headerDir; - if (parentHeaderDir) { - resolvedHeaderDir = parentHeaderDir; - break; - } + // Also extract exclude_files patterns from the podspec + // Examples: + // s.exclude_files = "tests/**/*.h" + // s.exclude_files = ["tests/**/*.h", "internal/**/*.h"] + // ss.exclude_files = "..." + const excludeFilesRegex = /\.exclude_files\s*=\s*(\[[^\]]*\]|"[^"]*")/gm; + const excludeMatches = [...fileContent.matchAll(excludeFilesRegex)]; + + // Parse exclude patterns + const excludePatterns = excludeMatches.flatMap(match => { + const arg = match[1].trim(); + if (arg.startsWith('[')) { + const arrayContent = arg.slice(1, arg.lastIndexOf(']')); + return arrayContent + .split(',') + .map(s => s.trim().replace(/['"]/g, '')) + .filter(s => s.length > 0); + } else { + return [arg.replace(/['"]/g, '').trim()].filter(s => s.length > 0); } + }); + + // Add default excludes + const allExcludes = [...excludePatterns]; + + if (matches.length > 0) { + // Extract header patterns (second argument) from all matches + const patterns = matches.flatMap(match => { + const secondArg = match[1].trim(); + + // Parse the second argument - it can be a string or an array + if (secondArg.startsWith('[')) { + // It's an array, extract the contents and split by comma + const arrayContent = secondArg.slice(1, secondArg.lastIndexOf(']')); + return arrayContent + .split(',') + .map(s => s.trim().replace(/['"]/g, '')) + .filter(s => s.length > 0); + } else { + // It's a single string + return [secondArg.replace(/['"]/g, '').trim()].filter( + s => s.length > 0, + ); + } + }); + + // Now we can find header files based on these patterns + const foundHeaderFiles = patterns + .map(pattern => { + // our GLOB library doesn't like {h} in its patterns, so we use **/*.h instead of **/*.{h} + if (pattern.includes('{h}')) { + pattern = pattern.replaceAll('{h}', 'h'); + } + return globSync(pattern, { + cwd: path.dirname(podspecPath), + ignore: allExcludes, + absolute: true, + }); + }) + .flat(); + + result[podspecPath] = [ + { + headerDir: '', // We don't have headerDir info here + specName: path.basename(podspecPath, '.podspec'), + headers: foundHeaderFiles.map(headerFile => ({ + source: headerFile, + target: path.basename(headerFile), + })), + }, + ]; } + } + }); + + return result; +} + +/** + * Extracts header files from a single podspec based on its configuration. + * @param {PodSpecConfiguration} podSpecConfig The podspec configuration object + * @param {string} podSpecDirectory Directory where the podspec is located + * @returns {HeaderMap[] | null} Array of header maps or null if configuration is invalid + */ +function getHeaderFilesFromPodspec( + podSpecConfig /*: PodSpecConfiguration*/, + podSpecDirectory /*:string*/, +) /*: HeaderMap[] | null*/ { + if ( + !podSpecConfig || + 'name' in podSpecConfig === false || + podSpecConfig.name === '' + ) { + headersLog(`⚠️ Skipping podspec due to missing or invalid configuration.`); + return null; + } + + const headerMaps /*: HeaderMap[] */ = []; - // If still not resolved, default to spec name - if (!resolvedHeaderDir) { - resolvedHeaderDir = ''; + // Now we can start collecting header files + const processConfig = ( + config /*: PodSpecConfiguration */, + parents /*: Array*/, + ) => { + if (config.disabled === true) { + return; + } + + const {headerDir, headerPatterns, excludePatterns, subSpecs} = config; + + // Find header files for configuration + const foundHeaderFiles = headerPatterns + .map(pattern => + globSync(pattern, { + cwd: podSpecDirectory, + absolute: true, + ignore: excludePatterns || [], + }), + ) + .flat(); + + let resolvedHeaderDir /*:string */ = headerDir || ''; + + // If headerDir is not set, we need to resolve it against parent specs + if (parents.length > 0 && !headerDir) { + for (let i = parents.length - 1; i >= 0; i--) { + const parentHeaderDir = parents[i].headerDir; + if (parentHeaderDir) { + resolvedHeaderDir = parentHeaderDir; + break; + } } + } - // Resolve preservePaths from parent specs too - let resolvedPreservePaths = config.preservePaths || []; - if (resolvedPreservePaths.length === 0 && parents.length > 0) { - for (let i = parents.length - 1; i >= 0; i--) { - const parentPreservePaths = parents[i].preservePaths; - if (parentPreservePaths && parentPreservePaths.length > 0) { - resolvedPreservePaths = parentPreservePaths; - break; - } + // If still not resolved, default to spec name + if (!resolvedHeaderDir) { + resolvedHeaderDir = ''; + } + + // Resolve preservePaths from parent specs too + let resolvedPreservePaths = config.preservePaths || []; + if (resolvedPreservePaths.length === 0 && parents.length > 0) { + for (let i = parents.length - 1; i >= 0; i--) { + const parentPreservePaths = parents[i].preservePaths; + if (parentPreservePaths && parentPreservePaths.length > 0) { + resolvedPreservePaths = parentPreservePaths; + break; } } + } - headerMaps[podspecPath] = (headerMaps[podspecPath] || []).concat({ - headerDir: resolvedHeaderDir, - specName: podSpecConfig.name, - headers: foundHeaderFiles.map(headerFile => { - // Check if we have preservePath set for this file - then we need to get the subfolder structure too - // and not just copy to the root of headerDir - we should also ignore the headerDir part of the path - const isPreserved = resolvedPreservePaths.some(preservePattern => { - return globSync(preservePattern, { - cwd: podSpecDirectory, - absolute: true, - ignore: excludePatterns || [], - }).includes(headerFile); - }); - - if (isPreserved) { - // Get the subfolder for the header file - const relativePath = path.dirname( - path.relative(podSpecDirectory, headerFile), - ); - return { - source: headerFile, - target: path.join(relativePath, path.basename(headerFile)), - }; - } + headerMaps.push({ + headerDir: resolvedHeaderDir, + specName: config.name, + headers: foundHeaderFiles.map(headerFile => { + // Check if we have preservePath set for this file - then we need to get the subfolder structure too + // and not just copy to the root of headerDir - we should also ignore the headerDir part of the path + const isPreserved = resolvedPreservePaths.some(preservePattern => { + return globSync(preservePattern, { + cwd: podSpecDirectory, + absolute: true, + ignore: excludePatterns || [], + }).includes(headerFile); + }); + + if (isPreserved) { + // Get the subfolder for the header file + const relativePath = path.dirname( + path.relative(podSpecDirectory, headerFile), + ); return { source: headerFile, - target: path.join(resolvedHeaderDir, path.basename(headerFile)), + target: path.join(relativePath, path.basename(headerFile)), }; - }), - }); + } + return { + source: headerFile, + target: path.join(resolvedHeaderDir, path.basename(headerFile)), + }; + }), + }); - // Process subSpecs recursively - if (subSpecs && subSpecs.length > 0) { - subSpecs.forEach(subSpecConfig => { - processConfig(subSpecConfig, [config, ...parents]); - }); - } - }; + // Process subSpecs recursively + if (subSpecs && subSpecs.length > 0) { + subSpecs.forEach(subSpecConfig => { + processConfig(subSpecConfig, [config, ...parents]); + }); + } + }; - processConfig(podSpecConfig, []); - }); + processConfig(podSpecConfig, []); return headerMaps; } From bf4f3817a0d06c98f4d3b787d5a1546b12e5e752 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Mon, 26 Jan 2026 20:22:27 +0100 Subject: [PATCH 3/6] codereview: Added comments --- .../scripts/ios-prebuild/headers-config.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/react-native/scripts/ios-prebuild/headers-config.js b/packages/react-native/scripts/ios-prebuild/headers-config.js index a445357b982c..f6c3a610ad1c 100644 --- a/packages/react-native/scripts/ios-prebuild/headers-config.js +++ b/packages/react-native/scripts/ios-prebuild/headers-config.js @@ -19,6 +19,26 @@ export type PodSpecConfiguration = $ReadOnly<{ } | {disabled: true}>; */ +/* + * Podspec Header Configuration Exceptions + * + * Most podspecs follow a standard pattern for header file locations. However, some pods + * require custom configuration for where their headers should be placed in the prebuilt + * framework. This map defines those exceptions. + * + * Each entry is keyed by the relative path to the podspec file and contains: + * - name: The pod name + * - headerPatterns: Glob patterns for header files to include + * - headerDir: Target directory for the headers in the framework + * - excludePatterns: (optional) Glob patterns for files to exclude + * - subSpecs: (optional) Nested subspecs with their own header configurations + * - disabled: (optional) Set to true to skip processing this podspec entirely + * + * Examples: + * - React-jsi: Headers are placed in the 'jsi' directory + * - React-Fabric: Composed of multiple subspecs (animated, animations, core, etc.), + * each requiring headers in their respective 'react/renderer/...' directories + */ const PodspecExceptions /*: {[key: string]: PodSpecConfiguration} */ = { 'ReactCommon/jsi/React-jsi.podspec': { name: 'React-jsi', From 613b8d5624b5bba0bfc5f07f9e9a43fbe59985fb Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Mon, 26 Jan 2026 20:25:23 +0100 Subject: [PATCH 4/6] codereview: Fixed comments --- packages/react-native/scripts/ios-prebuild/xcframework.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native/scripts/ios-prebuild/xcframework.js b/packages/react-native/scripts/ios-prebuild/xcframework.js index 01d93de6454d..659d6bb83e55 100644 --- a/packages/react-native/scripts/ios-prebuild/xcframework.js +++ b/packages/react-native/scripts/ios-prebuild/xcframework.js @@ -313,7 +313,7 @@ function copyHeaderFilesToSlices( 'Headers', ); - // Link umbrella / header files into the platform folder + // Copy umbrella / header files into the platform folder Object.keys(umbrellaHeaderFiles).forEach(podSpecName => { const umbrellaHeaderFile = umbrellaHeaderFiles[podSpecName]; @@ -328,7 +328,7 @@ function copyHeaderFilesToSlices( ); } catch (error) { frameworkLog( - `Error linking umbrella header file: ${error.message}. Check if the file exists.`, + `Error copying umbrella header file: ${umbrellaHeaderFile}\nError: ${error.message}. Check if the file exists.`, 'error', ); } From dbea340aca05dde84acd77a258441d70b657f452 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Mon, 26 Jan 2026 20:26:31 +0100 Subject: [PATCH 5/6] codereview: fixed comment --- packages/react-native/scripts/ios-prebuild/xcframework.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/scripts/ios-prebuild/xcframework.js b/packages/react-native/scripts/ios-prebuild/xcframework.js index 659d6bb83e55..e8355512f44c 100644 --- a/packages/react-native/scripts/ios-prebuild/xcframework.js +++ b/packages/react-native/scripts/ios-prebuild/xcframework.js @@ -320,7 +320,7 @@ function copyHeaderFilesToSlices( // 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 + // Copy the umbrella header file to the target folder try { fs.copyFileSync( umbrellaHeaderFile, From 06923a1a154f84041e86c6e94c425fa49cbbfe7e Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Tue, 27 Jan 2026 15:26:52 +0100 Subject: [PATCH 6/6] codereview: removed warning --- packages/react-native/scripts/ios-prebuild/xcframework.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/react-native/scripts/ios-prebuild/xcframework.js b/packages/react-native/scripts/ios-prebuild/xcframework.js index e8355512f44c..00bd5c6cf571 100644 --- a/packages/react-native/scripts/ios-prebuild/xcframework.js +++ b/packages/react-native/scripts/ios-prebuild/xcframework.js @@ -118,12 +118,6 @@ function buildXCFrameworks( .map(h => h.headers) .flat(); - // Use the first podspec spec name as the podspec name (this is the root spec in the podspec file) - const podSpecName = podSpecsWithHeaderFiles[podspec][0].specName.replace( - '-', - '_', - ); - 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.