Skip to content
Open
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
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
99 changes: 27 additions & 72 deletions packages/react-native/scripts/cocoapods/rncore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,21 @@
### building ReactNativeCore from source (then this function does nothing).
def add_rncore_dependency(s)
if !ReactNativeCoreUtils.build_rncore_from_source()
# Add the dependency
# `<React/...>` resolves through the vendored React.framework; every other
# namespace (`<react/...>`, `<yoga/...>`, `<hermes/...>`, ...) resolves
# through the flattened ReactNativeHeaders headers that React-Core-prebuilt
# exposes on its header search path. No clang VFS overlay.
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"
# HEADER_SEARCH_PATHS may already be a String or an Array (e.g. after
# add_rn_third_party_dependencies); normalize to an Array before appending.
header_search_paths = current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] || []
header_search_paths = header_search_paths.split(" ") if header_search_paths.is_a?(String)
header_search_paths << "\"$(PODS_ROOT)/React-Core-prebuilt/Headers\""
current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] = header_search_paths

s.pod_target_xcconfig = current_pod_target_xcconfig
end
Expand Down Expand Up @@ -521,95 +516,55 @@ 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.
# Configures the xcconfig files for aggregate (main app) targets and third-party pod
# targets so the prebuilt ReactNativeHeaders are resolvable. These targets do not go
# through add_rncore_dependency, so they won't otherwise get the header search path.
#
# 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")
end
Expand Down
157 changes: 40 additions & 117 deletions packages/react-native/scripts/ios-prebuild/__docs__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,123 +111,46 @@ The build process uses specific `xcodebuild` flags:
- Build times vary depending on the target platform and configuration
- XCFrameworks support multiple architectures in a single bundle

## Known Issues

The generated XCFrameworks currently use CocoaPods-style header structures
rather than standard framework header conventions. This may cause modularity
issues when:

- Consuming the XCFrameworks in projects that expect standard framework headers
- Building dependent frameworks that rely on proper module boundaries
- Integrating with Swift Package Manager projects expecting modular headers

## VFS Overlay System

The prebuilt XCFrameworks use Clang's Virtual File System (VFS) overlay
mechanism to enable header imports without modifying the actual header file
structure. This is necessary because React Native's headers are organized
differently than standard framework conventions.

### Overview

The VFS overlay creates a virtual mapping between the import paths used in code
(e.g., `#import <react/renderer/graphics/Size.h>`) and the actual physical
locations of headers within the XCFramework. This allows the prebuilt frameworks
to work seamlessly while maintaining the original import syntax.

### Build-Time VFS Generation (`vfs.js`)

The `vfs.js` script creates a VFS overlay template during the prebuild process:

1. **Header Collection** (`headers.js`): Scans all podspec files in the React
Native package to discover header files and their target import paths.

2. **VFS Structure Building**: The `buildVFSStructure()` function creates a
hierarchical directory tree representation from the header mappings. Clang's
VFS overlay requires directories to contain their children in a tree
structure.

3. **YAML Generation**: The `generateVFSOverlayYAML()` function converts the VFS
structure into Clang's expected YAML format.

4. **Template Creation**: The generated overlay uses `${ROOT_PATH}` as a
placeholder for the actual installation path. This template is included in
the XCFramework as `React-VFS-template.yaml`.

#### Key Functions

- `createVFSOverlay(rootFolder)`: Main entry point that generates the complete
VFS overlay YAML string
- `createVFSOverlayContents(rootFolder)`: Creates the VFS overlay object
structure
- `buildVFSStructure(mappings)`: Builds the hierarchical directory tree from
flat mappings
- `resolveVFSOverlay(vfsTemplate, rootPath)`: Replaces `${ROOT_PATH}` with the
actual path

### Runtime VFS Processing (CocoaPods)

When consuming prebuilt frameworks via CocoaPods, the VFS overlay is processed
at pod install time by `rncore.rb`:

#### `process_vfs_overlay()`

Called during `react_native_post_install`, this method:

1. Reads the `React-VFS-template.yaml` from the XCFramework
2. Resolves the `${ROOT_PATH}` placeholder with the actual XCFramework path
3. Writes the resolved overlay to
`$(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml`

#### `add_rncore_dependency(s)`

Adds VFS overlay compiler flags to podspecs that depend on React Native:

```ruby
# For C/C++ compilation
OTHER_CFLAGS += "-ivfsoverlay $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
OTHER_CPLUSPLUSFLAGS += "-ivfsoverlay $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"

# For Swift compilation (flags passed to underlying Clang)
OTHER_SWIFT_FLAGS += "-Xcc -ivfsoverlay -Xcc $(PODS_ROOT)/React-Core-prebuilt/React-VFS.yaml"
```

#### `configure_aggregate_xcconfig(installer)`

Configures VFS overlay flags for:

- **Aggregate targets**: Main app targets that don't go through podspec
processing
- **All pod targets**: Third-party pods that don't explicitly call
`add_rncore_dependency`

This ensures all compilation units in the project can resolve React Native
headers through the VFS overlay.

### VFS Overlay Format

The VFS overlay uses Clang's hierarchical YAML format:

```yaml
version: 0
case-sensitive: false
roots:
- name: '${ROOT_PATH}/Headers'
type: 'directory'
contents:
- name: 'react'
type: 'directory'
contents:
- name: 'renderer'
type: 'directory'
contents:
- name: 'Size.h'
type: 'file'
external-contents: '${ROOT_PATH}/Headers/React/react/renderer/Size.h'
```

The structure maps virtual paths (what the compiler sees) to physical paths
(where the files actually exist in the XCFramework).
## Header Resolution (headers-spec layout)

The prebuilt XCFrameworks ship a **headers-spec layout** so that header imports
resolve through plain header/framework search paths — there is **no clang VFS
overlay**. The layout contract is defined and validated in code:

- `headers-spec.js`: the executable layout contract (rules R1–R8) — which
namespaces are hoisted, which carry module maps, and how collisions are
rejected.
- `headers-inventory.js`: scans the source tree to build the live header
inventory that feeds the spec.
- `headers-compose.js`: emits the layout. `emitReactFrameworkHeaders()` writes the
`React/` and bare-aliased headers into every slice's `React.framework/Headers`,
and `buildReactNativeHeadersXcframework()` assembles the headers-only
`ReactNativeHeaders.xcframework` carrying every other namespace (incl. `react/`)
plus the third-party dependency namespaces (`folly`, `glog`, `boost`, `fmt`,
`double-conversion`, `fast_float`). The Hermes public headers (`<hermes/...>`)
are folded in only on the SwiftPM consumer side (`ensureHeadersLayout`); the
published prebuild artifact does not yet carry them (TODO in `xcframework.js`).

### Artifacts

The prebuild (`xcframework.js`) always produces:

- `React.xcframework` — the compiled React core. Each slice's `React.framework`
carries the headers-spec layout (every `<React/...>` header + the framework
module map), which is what both CocoaPods and SwiftPM consume.
- `ReactNativeHeaders.xcframework` — headers-only; carries every other namespace.
Consumed by SwiftPM as a `binaryTarget` and by CocoaPods via the
`React-Core-prebuilt` pod (headers flattened onto the header search path).

### CocoaPods consumption

The `React-Core-prebuilt` pod vends `React.xcframework` (so `<React/...>` and
`@import React;` resolve through the framework module via `FRAMEWORK_SEARCH_PATHS`)
and flattens `ReactNativeHeaders.xcframework`'s headers into a top-level `Headers/`
exposed on the pod header search path (so `<react/...>`, `<yoga/...>`, `<folly/...>`
resolve). `rncore.rb` adds the `HEADER_SEARCH_PATHS` entry to
`React-Core-prebuilt/Headers` for podspec, aggregate (main app), and third-party
pod targets. No `-ivfsoverlay` flags are added.

## Integrating in your project with Cocoapods

Expand Down
Loading
Loading