Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/prebuild-ios-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ jobs:
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
run: |
cd packages/react-native/.build/output/xcframeworks/${{matrix.flavor}}
tar -cz -f ../ReactCore${{matrix.flavor}}.xcframework.tar.gz React.xcframework
# Ship BOTH xcframeworks: React-Core-prebuilt's prepare_command flattens
# ReactNativeHeaders.xcframework's Headers (incl. module.modulemap) into the
# pod. Omitting it leaves consumers without React-Core-prebuilt/Headers/module.modulemap.
tar -cz -f ../ReactCore${{matrix.flavor}}.xcframework.tar.gz React.xcframework ReactNativeHeaders.xcframework
- name: Compress and Rename dSYM
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
run: |
Expand Down
49 changes: 32 additions & 17 deletions packages/react-native/React-Core-prebuilt.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,51 @@ Pod::Spec.new do |s|
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source

# We vend two xcframeworks that ship together in the prebuilt tarball:
# - React.xcframework: the compiled core. Its per-slice React.framework carries
# every <React/...> header + the framework module map, so `#import <React/...>`
# and `@import React;` resolve through FRAMEWORK_SEARCH_PATHS automatically.
# - ReactNativeHeaders.xcframework: headers-only. Carries every other namespace
# (<react/...>, <yoga/...>, folly, glog, ...). Its headers are flattened into a
# top-level Headers/ (see prepare_command) and exposed via the standard pod
# header search path. (<hermes/...> is supplied by the hermes-engine pod here;
# it is folded into ReactNativeHeaders only on the SwiftPM consumer side.)
# There is no clang VFS overlay.
s.vendored_frameworks = "React.xcframework"

s.preserve_paths = '**/*.*'
s.header_mappings_dir = 'React.xcframework/Headers'
s.source_files = 'React.xcframework/Headers/**/*.{h,hpp}'

s.module_name = 'React'
s.module_map = 'React.xcframework/Modules/module.modulemap'
s.public_header_files = 'React.xcframework/Headers/**/*.h'
s.header_mappings_dir = 'Headers'
s.source_files = 'Headers/**/*.{h,hpp}'
s.public_header_files = 'Headers/**/*.h'

add_rn_third_party_dependencies(s)

# We need to make sure that the React.xcframework is copied correctly - in the downloaded tarball
# the root directory is the framework, but when using it we need to have it in a subdirectory
# called React.xcframework, so we need to move the contents of the tarball into that directory.
# This is done in the prepare_command.
# We need to make sure that the headers are copied to the right place - local tar.gz has a different structure
# than the one from the maven repo
# The downloaded tarball ships React.xcframework and ReactNativeHeaders.xcframework
# at its root. We make sure React.xcframework is in its own subdirectory (the Maven
# tarball lays the framework contents at the root; the local tar.gz has a different
# structure) and flatten ReactNativeHeaders' headers into a top-level Headers/ dir
# so CocoaPods exposes them on the header search path.
s.prepare_command = <<~'CMD'
CURRENT_PATH=$(pwd)
XCFRAMEWORK_PATH="${CURRENT_PATH}/React.xcframework"

# Check if XCFRAMEWORK_PATH is empty
if [ -z "$XCFRAMEWORK_PATH" ]; then
echo "ERROR: XCFRAMEWORK_PATH is empty."
exit 0
# Flatten ReactNativeHeaders' headers (identical across slices) into Headers/
# BEFORE we sweep stray root entries into React.xcframework.
mkdir -p Headers
RNH_XCFRAMEWORK_PATH=$(find "$CURRENT_PATH" -type d -name "ReactNativeHeaders.xcframework" | head -n 1)
if [ -n "$RNH_XCFRAMEWORK_PATH" ]; then
RNH_HEADERS_PATH=$(find "$RNH_XCFRAMEWORK_PATH" -type d -name "Headers" | head -n 1)
if [ -n "$RNH_HEADERS_PATH" ]; then
cp -R "$RNH_HEADERS_PATH/." Headers
fi
rm -rf "$RNH_XCFRAMEWORK_PATH"
fi

mkdir -p "${XCFRAMEWORK_PATH}"
find "$CURRENT_PATH" -mindepth 1 -maxdepth 1 ! -name "$(basename "$XCFRAMEWORK_PATH")" -exec mv {} "$XCFRAMEWORK_PATH" \;
find "$CURRENT_PATH" -mindepth 1 -maxdepth 1 \
! -name "$(basename "$XCFRAMEWORK_PATH")" ! -name "Headers" \
-exec mv {} "$XCFRAMEWORK_PATH" \;
CMD

# If we are passing a local tarball, we don't want to switch between Debug and Release
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/scripts/cocoapods/fabric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def setup_fabric!(react_native_path: "../node_modules/react-native")
pod 'React-Fabric', :path => "#{react_native_path}/ReactCommon"
pod 'React-FabricComponents', :path => "#{react_native_path}/ReactCommon"
pod 'React-graphics', :path => "#{react_native_path}/ReactCommon/react/renderer/graphics"
pod 'React-RCTFabric', :path => "#{react_native_path}/React", :modular_headers => true
rncore_pod 'React-RCTFabric', :path => "#{react_native_path}/React", :modular_headers => true
pod 'React-ImageManager', :path => "#{react_native_path}/ReactCommon/react/renderer/imagemanager/platform/ios"
pod 'React-FabricImage', :path => "#{react_native_path}/ReactCommon"
end
110 changes: 32 additions & 78 deletions packages/react-native/scripts/cocoapods/rncore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,16 @@

### Adds ReactNativeCore-prebuilt as a dependency to the given podspec if we're not
### building ReactNativeCore from source (then this function does nothing).
###
### `<React/...>` resolves through the vendored React.framework; every other namespace
### (`<react/...>`, `<yoga/...>`, `<hermes/...>`, ...) resolves through the flattened
### ReactNativeHeaders headers that React-Core-prebuilt exposes. The header search path
### and the ReactNativeHeaders module-map activation are NOT added here: they are applied
### post-install by configure_aggregate_xcconfig, which covers aggregate, third-party AND
### these pods from a single injection site. No clang VFS overlay.
def add_rncore_dependency(s)
if !ReactNativeCoreUtils.build_rncore_from_source()
# Add the dependency
s.dependency "React-Core-prebuilt"

current_pod_target_xcconfig = s.to_hash["pod_target_xcconfig"] || {}
current_pod_target_xcconfig = current_pod_target_xcconfig.to_h unless current_pod_target_xcconfig.is_a?(Hash)

# Add VFS overlay flags for both Objective-C and Swift
# The VFS overlay file is pre-resolved at pod install time for each platform slice.
# We reference it directly in the xcframework using the React-VFS.yaml file that
# is written to the React-Core-prebuilt folder during setup_vfs_overlay.
# See scripts/ios-prebuild/__docs__/README.md for more details on VFS overlays.
vfs_overlay_flag = "-ivfsoverlay $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
current_pod_target_xcconfig["OTHER_CFLAGS"] ||= "$(inherited)"
current_pod_target_xcconfig["OTHER_CFLAGS"] += " #{vfs_overlay_flag}"
current_pod_target_xcconfig["OTHER_CPLUSPLUSFLAGS"] ||= "$(inherited)"
current_pod_target_xcconfig["OTHER_CPLUSPLUSFLAGS"] += " #{vfs_overlay_flag}"
# For Swift, we need to use -Xcc to pass flags to the underlying Clang compiler
# Both the flag and its argument need separate -Xcc prefixes
current_pod_target_xcconfig["OTHER_SWIFT_FLAGS"] ||= "$(inherited)"
current_pod_target_xcconfig["OTHER_SWIFT_FLAGS"] += " -Xcc -ivfsoverlay -Xcc $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"

s.pod_target_xcconfig = current_pod_target_xcconfig
end
end

Expand Down Expand Up @@ -521,96 +507,64 @@ def self.get_nightly_npm_version()
return latest_nightly
end

# Processes the VFS overlay file from the React.xcframework to resolve the ${ROOT_PATH} placeholder.
# This method should be called from react_native_post_install after pod install completes.
# Single post-install injection site for the prebuilt header resolution. Adds the
# ReactNativeHeaders search path + module-map activation to the aggregate (main app)
# target AND every pod target — RN core pods, third-party pods alike. (add_rncore_dependency
# only declares the React-Core-prebuilt dependency; it no longer touches xcconfigs.)
#
# The VFS overlay file maps header import paths to their actual locations within the xcframework.
# Since the xcframework contains platform-specific slices, we generate a resolved VFS file for each
# slice and also create a default VFS file that can be used immediately (before script phases run).
def self.process_vfs_overlay()
return if @@build_from_source

prebuilt_path = File.join(Pod::Config.instance.project_pods_root, "React-Core-prebuilt")
xcframework_path = File.join(prebuilt_path, "React.xcframework")
vfs_template_path = File.join(xcframework_path, "React-VFS-template.yaml")

unless File.exist?(vfs_template_path)
rncore_log("VFS overlay template not found at #{vfs_template_path}", :error)
exit 1
end

rncore_log("Processing VFS overlay file...")

# Read the template content
vfs_template_content = File.read(vfs_template_path)

# Write the VFS file - use the top-level xcframework path
# so that ${ROOT_PATH}/Headers points to the xcframework's Headers folder
resolved_vfs_content = vfs_template_content.gsub('${ROOT_PATH}', xcframework_path)
resolved_vfs_path = File.join(prebuilt_path, "React-VFS.yaml")
File.write(resolved_vfs_path, resolved_vfs_content)
rncore_log(" Created VFS overlay at #{resolved_vfs_path}")

rncore_log("VFS overlay setup complete")
end

# Configures the xcconfig files for aggregate (main app) targets to enable VFS overlay for React Native Core.
# This is needed because the main app target does not go through podspec processing,
# so it won't get the VFS overlay flags from add_rncore_dependency.
# `<React/...>` resolves through the vendored React.framework; this adds the search
# path to the flattened ReactNativeHeaders headers (every other namespace). There is
# no clang VFS overlay.
#
# Parameters:
# - installer: The CocoaPods installer object
def self.configure_aggregate_xcconfig(installer)
return if @@build_from_source

prebuilt_path = File.join(Pod::Config.instance.project_pods_root, "React-Core-prebuilt")
vfs_overlay_path = File.join(prebuilt_path, "React-VFS.yaml")

unless File.exist?(vfs_overlay_path)
rncore_log("VFS overlay not found at #{vfs_overlay_path}, skipping prebuilt xcconfig configuration", :error)
exit 1
end

rncore_log("Configuring xcconfig for prebuilt React Native Core...")

vfs_overlay_flag = " -ivfsoverlay \"#{vfs_overlay_path}\""
swift_vfs_overlay_flag = " -Xcc -ivfsoverlay -Xcc \"#{vfs_overlay_path}\""
headers_search_path = " \"$(PODS_ROOT)/React-Core-prebuilt/Headers\""

# Add flags to aggregate target xcconfigs (these are used by the main app target)
# Add the header search path to aggregate target xcconfigs (used by the main app target)
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.xcconfigs.each do |config_name, config_file|
add_vfs_overlay_flags(config_file.attributes, vfs_overlay_flag, swift_vfs_overlay_flag)
add_prebuilt_header_search_paths(config_file.attributes, headers_search_path)
xcconfig_path = aggregate_target.xcconfig_path(config_name)
config_file.save_as(xcconfig_path)
end
end

# Add flags to ALL pod targets (for third-party pods that don't call add_rncore_dependency)
# Add the header search path to ALL pod targets (for third-party pods that don't call add_rncore_dependency)
installer.pod_targets.each do |pod_target|
pod_target.build_settings.each do |config_name, build_settings|
xcconfig_path = pod_target.xcconfig_path(config_name)
next unless File.exist?(xcconfig_path)

xcconfig = Xcodeproj::Config.new(xcconfig_path)

# Check if VFS overlay is already present
other_cflags = xcconfig.attributes["OTHER_CFLAGS"] || ""
next if other_cflags.include?("ivfsoverlay")
# Skip if the prebuilt header search path is already present
header_search_paths = xcconfig.attributes["HEADER_SEARCH_PATHS"] || ""
next if header_search_paths.include?("React-Core-prebuilt/Headers")

add_vfs_overlay_flags(xcconfig.attributes, vfs_overlay_flag, swift_vfs_overlay_flag)
add_prebuilt_header_search_paths(xcconfig.attributes, headers_search_path)
xcconfig.save_as(xcconfig_path)
end
end

rncore_log("Prebuilt xcconfig configuration complete")
end

# Helper method to add VFS overlay flags to an xcconfig attributes map
def self.add_vfs_overlay_flags(attributes, vfs_overlay_flag, swift_vfs_overlay_flag)
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CFLAGS", vfs_overlay_flag)
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CPLUSPLUSFLAGS", vfs_overlay_flag)
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", swift_vfs_overlay_flag)
# Helper method to add the prebuilt ReactNativeHeaders header search path to an xcconfig attributes map
def self.add_prebuilt_header_search_paths(attributes, headers_search_path)
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "HEADER_SEARCH_PATHS", headers_search_path)
# Suppress incomplete umbrella warnings for the prebuilt frameworks (it is expected, as our umbrella headers do not include all headers)
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", " -Xcc -Wno-incomplete-umbrella")
# Activate the ReactNativeHeaders module map so the relocated namespaces
# (`yoga`, `RCTDeprecation`, `ReactNativeHeaders_react`, ...) are modular —
# otherwise the React framework's clang explicit-module precompile trips
# -Wnon-modular-include-in-framework-module on `<yoga/...>` / `<react/...>`.
module_map_flag = " -fmodule-map-file=$(PODS_ROOT)/React-Core-prebuilt/Headers/module.modulemap"
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_CFLAGS", module_map_flag)
ReactNativePodsUtils.add_flag_to_map_with_inheritance(attributes, "OTHER_SWIFT_FLAGS", " -Xcc" + module_map_flag)
end
end
Loading
Loading