Skip to content

Commit a6a3c04

Browse files
chrfalchclaude
andcommitted
feat(ios-prebuild)!: make ReactNativeHeaders pure-RN
Every namespace now has exactly ONE physical home: RN namespaces in ReactNativeHeaders, third-party deps namespaces in the ReactNativeDependenciesHeaders sidecar / the ReactNativeDependencies pod. Relocated deps copies are what made the 2026-07-03 SocketRocket regression possible (duplicate @interface + poisoned module graph under use_frameworks when a real pod coexisted with relocated copies) - that bug class is now structurally impossible, and the SocketRocket exclusion list is deleted rather than maintained. - headers-spec.js: R2 rewritten; DEPS_NAMESPACES now includes SocketRocket (= the sidecar contents = the artifact's Headers/ dirs, set-equality enforced); DEPS_NAMESPACES_NOT_RELOCATED deleted. - headers-compose.js: RNH stages RN headers only; ensureHeadersLayout also builds the sidecar from the cache slot's deps headers and returns it, so SwiftPM consumers need no published sidecar. - headers-verify.js: structural gate flipped - deps namespaces must be ABSENT from RNH; compile gates take the deps artifact's Headers on the include path (exactly how consumers resolve them). - CocoaPods: NEW ReactNativeDependenciesUtils.configure_aggregate_xcconfig injects $(PODS_ROOT)/ReactNativeDependencies/Headers into the aggregate and every pod target in prebuilt-deps mode, mirroring the rncore injection. Pods previously resolved folly/glog through the globally-injected React-Core-prebuilt/Headers flatten of RNH; after the flip the deps pod is the single global home, so the mirror injection is what keeps arbitrary pods compiling. Verified: headers gate green (include-health, structural, compile smokes vs the deps include path); rn-tester prebuilt static + dynamic (the regression config), helloworld static, source-core+prebuilt-deps (React compiled from source resolves folly via the deps pod), and a source-mode control with an unchanged dependency graph. BREAKING (prebuilt artifacts only): ReactNativeHeaders.xcframework no longer contains folly/glog/boost/fmt/double-conversion/fast_float; consumers reaching them through React-Core-prebuilt/Headers must use the ReactNativeDependencies pod / ReactNativeDependenciesHeaders sidecar paths instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6917845 commit a6a3c04

12 files changed

Lines changed: 213 additions & 199 deletions

File tree

.github/workflows/prebuild-ios-core.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,11 @@ jobs:
167167
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
168168
shell: bash
169169
run: |
170-
# ReactNativeHeaders.xcframework (built by the compose step) folds in
171-
# the third-party deps namespaces (folly/glog/boost/...), so the deps
172-
# headers must be staged here too — not just in build-slices.
170+
# ReactNativeHeaders.xcframework is pure-RN (the deps namespaces ship
171+
# in the ReactNativeDependenciesHeaders sidecar built by the deps
172+
# prebuild), but the headers-verify compile gates still need the deps
173+
# headers on their include path (folly/glog/... reached from RN's
174+
# public headers), so the deps artifact is staged here too.
173175
tar -xzf /tmp/third-party/ReactNativeDependencies${{ matrix.flavor }}.xcframework.tar.gz -C /tmp/third-party/
174176
mkdir -p packages/react-native/third-party/
175177
mv /tmp/third-party/packages/react-native/third-party/ReactNativeDependencies.xcframework packages/react-native/third-party/ReactNativeDependencies.xcframework

packages/react-native/React-Core-prebuilt.podspec

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ Pod::Spec.new do |s|
2222
# - React.xcframework: the compiled core. Its per-slice React.framework carries
2323
# every <React/...> header + the framework module map, so `#import <React/...>`
2424
# and `@import React;` resolve through FRAMEWORK_SEARCH_PATHS automatically.
25-
# - ReactNativeHeaders.xcframework: headers-only. Carries every other namespace
26-
# (<react/...>, <yoga/...>, folly, glog, ...). Its headers are flattened into a
27-
# top-level Headers/ (see prepare_command) and exposed via the standard pod
28-
# header search path. (<hermes/...> is supplied by the hermes-engine pod here;
29-
# it is folded into ReactNativeHeaders only on the SwiftPM consumer side.)
25+
# - ReactNativeHeaders.xcframework: headers-only, PURE-RN. Carries every other
26+
# RN namespace (<react/...>, <yoga/...>, ...). Its headers are flattened into
27+
# a top-level Headers/ (see prepare_command) and exposed via the standard pod
28+
# header search path. The third-party deps namespaces (folly/glog/boost/...)
29+
# are NOT here — the ReactNativeDependencies pod serves them from its own
30+
# artifact (see scripts/cocoapods/__docs__/prebuilt-deps.md), wired through
31+
# add_rn_third_party_dependencies below. (<hermes/...> is supplied by the
32+
# hermes-engine pod here; it is folded into ReactNativeHeaders only on the
33+
# SwiftPM consumer side.)
3034
# There is no clang VFS overlay.
3135
s.vendored_frameworks = "React.xcframework"
3236

packages/react-native/scripts/cocoapods/__docs__/prebuilt-deps.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ xcframework binary, and the artifact's own
1313
`Headers/{folly,glog,boost,fmt,double-conversion,fast_float, SocketRocket}` are
1414
flattened into the pod's `Headers/` by the podspec's `prepare_command`.
1515
Consumers resolve bare `<folly/...>` / `<SocketRocket/...>` via CocoaPods
16-
public-header linkage from `s.dependency "ReactNativeDependencies"`, plus an
17-
explicit `HEADER_SEARCH_PATHS` entry
18-
(`$(PODS_ROOT)/ReactNativeDependencies/Headers`). The real source pods are
19-
neither depended on nor searched.
16+
public-header linkage from `s.dependency "ReactNativeDependencies"`, plus
17+
`HEADER_SEARCH_PATHS` entries pointing at
18+
`$(PODS_ROOT)/ReactNativeDependencies/Headers`: per-podspec via
19+
`add_rn_third_party_dependencies`, and globally (aggregate + every pod target)
20+
via `ReactNativeDependenciesUtils.configure_aggregate_xcconfig` at post-install
21+
— ReactNativeHeaders is pure-RN, so this is the only global home of the deps
22+
namespaces. The real source pods are neither depended on nor searched.
2023

21-
NOTE: this is a CocoaPods-level contract. The deps XCFRAMEWORK itself is NOT
22-
self-serving: it is framework-type without `HeadersPath`, so its root `Headers/`
23-
is invisible to SPM binaryTargets (verified 2026-07-04 — `HeadersPath` is
24-
rejected on framework entries). In SPM the six C++ namespaces are served by
25-
ReactNativeHeaders.xcframework; serving them from the deps side requires the
26-
phase-2 headers-only library sidecar (see rn-deps-self-serving plan).
24+
For SPM, the deps XCFRAMEWORK itself cannot serve headers: it is framework-type
25+
without `HeadersPath`, and its root `Headers/` is invisible to SPM binaryTargets
26+
(verified 2026-07-04 — `HeadersPath` is rejected on framework entries). The deps
27+
prebuild therefore emits a headers-only LIBRARY-type sidecar,
28+
`ReactNativeDependenciesHeaders.xcframework` (same recipe as ReactNativeHeaders:
29+
stub archives + per-slice `Headers/`), which SPM auto-serves with zero flags.
30+
The sidecar ships inside the deps tarball and as a standalone artifact.
2731

2832
## Why SocketRocket is vended here
2933

packages/react-native/scripts/cocoapods/rncore.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,9 @@ def self.get_nightly_npm_version()
513513
# only declares the React-Core-prebuilt dependency; it no longer touches xcconfigs.)
514514
#
515515
# `<React/...>` resolves through the vendored React.framework; this adds the search
516-
# path to the flattened ReactNativeHeaders headers (every other namespace). There is
516+
# path to the flattened ReactNativeHeaders headers (every other RN namespace —
517+
# the third-party deps namespaces are served by the ReactNativeDependencies pod,
518+
# see ReactNativeDependenciesUtils.configure_aggregate_xcconfig). There is
517519
# no clang VFS overlay.
518520
#
519521
# Parameters:

packages/react-native/scripts/cocoapods/rndependencies.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,52 @@ def self.abort_if_use_local_rndeps_with_no_file()
150150
end
151151
end
152152

153+
# Single post-install injection site for the prebuilt deps header resolution.
154+
# Adds the flattened ReactNativeDependencies/Headers search path to the
155+
# aggregate (main app) target AND every pod target, mirroring
156+
# ReactNativeCoreUtils.configure_aggregate_xcconfig. ReactNativeHeaders is
157+
# pure-RN, so this path is the single global home of the third-party
158+
# namespaces (folly/glog/boost/fmt/double-conversion/fast_float/
159+
# SocketRocket): pods that never call add_rn_third_party_dependencies (nor
160+
# depend on a facade) still compile RN headers that textually reach
161+
# <folly/...>. No module-map activation needed — the deps headers are
162+
# served textually; modules come from the ReactNativeDependencies pod.
163+
def self.configure_aggregate_xcconfig(installer)
164+
return if @@build_from_source
165+
166+
rndeps_log("Configuring xcconfig for prebuilt React Native Dependencies...")
167+
headers_search_path = " \"$(PODS_ROOT)/ReactNativeDependencies/Headers\""
168+
169+
# Add the header search path to aggregate target xcconfigs (used by the main app target)
170+
installer.aggregate_targets.each do |aggregate_target|
171+
aggregate_target.xcconfigs.each do |config_name, config_file|
172+
ReactNativePodsUtils.add_flag_to_map_with_inheritance(config_file.attributes, "HEADER_SEARCH_PATHS", headers_search_path)
173+
xcconfig_path = aggregate_target.xcconfig_path(config_name)
174+
config_file.save_as(xcconfig_path)
175+
end
176+
end
177+
178+
# Add the header search path to ALL pod targets (for pods that don't go
179+
# through add_rn_third_party_dependencies)
180+
installer.pod_targets.each do |pod_target|
181+
pod_target.build_settings.each do |config_name, build_settings|
182+
xcconfig_path = pod_target.xcconfig_path(config_name)
183+
next unless File.exist?(xcconfig_path)
184+
185+
xcconfig = Xcodeproj::Config.new(xcconfig_path)
186+
187+
# Skip if the deps header search path is already present
188+
header_search_paths = xcconfig.attributes["HEADER_SEARCH_PATHS"] || ""
189+
next if header_search_paths.include?("ReactNativeDependencies/Headers")
190+
191+
ReactNativePodsUtils.add_flag_to_map_with_inheritance(xcconfig.attributes, "HEADER_SEARCH_PATHS", headers_search_path)
192+
xcconfig.save_as(xcconfig_path)
193+
end
194+
end
195+
196+
rndeps_log("Prebuilt deps xcconfig configuration complete")
197+
end
198+
153199
def self.podspec_source_download_prebuild_release_tarball()
154200
# Warn if @@react_native_path is not set
155201
if @@react_native_path == ""

packages/react-native/scripts/ios-prebuild/__tests__/headers-spec-test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
'use strict';
1212

1313
const {
14+
DEPS_NAMESPACES,
1415
planFromInventory,
1516
renderNamespaceModuleMap,
1617
renderReactModuleMap,
@@ -218,6 +219,22 @@ describe('R11 redirect shims for dual-identity headers', () => {
218219
});
219220
});
220221

222+
describe('DEPS_NAMESPACES (R2 — the deps sidecar namespace set)', () => {
223+
test('includes SocketRocket: one physical home, in the sidecar', () => {
224+
// Pre-sidecar, SocketRocket was excluded from relocation because a REAL
225+
// pod vended it (the 2026-07-03 dual-copy regression). With the sidecar
226+
// being the deps' single header home, it must be declared like every
227+
// other deps namespace.
228+
expect(DEPS_NAMESPACES).toContain('SocketRocket');
229+
});
230+
231+
test('plan.depsNamespaces mirrors the spec list', () => {
232+
expect(planFromInventory(validManifest()).depsNamespaces).toEqual(
233+
DEPS_NAMESPACES,
234+
);
235+
});
236+
});
237+
221238
describe('headers-verify gate pieces', () => {
222239
const {
223240
diffAgainstBaseline,

0 commit comments

Comments
 (0)