refactor(ios): remove clang VFS overlay, resolve headers via new ReactNativeHeaders framework#57285
refactor(ios): remove clang VFS overlay, resolve headers via new ReactNativeHeaders framework#57285chrfalch wants to merge 21 commits into
Conversation
|
CI is really red @chrfalch |
I know - working on it! Forgot to put it in draft, @cortinico :) |
`yarn format-check` (prettier) was failing CI on PR #57285. Run prettier on the ios-prebuild headers scripts (headers-compose.js, headers-inventory.js), replace-rncore-version.js, and __docs__/README.md so format-check passes. No logic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`yarn format-check` (prettier) was failing CI on PR #57285. Run prettier on the ios-prebuild headers scripts (headers-compose.js, headers-inventory.js), replace-rncore-version.js, and __docs__/README.md so format-check passes. No logic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
920e88b to
684fcf7
Compare
…S overlay ## Summary In RN 0.87 we'll hopefully release a new version of our precompiled binaries that works without the VFS overlay: - react/react-native#57285 - removal of VFS overlay - react/react-native#57305 - moved resource bundles to xcframework Meaning that we'll get completely self-contained XCFrameworks from RN which is a prerequisite for SwiftPM to not become too complex. ## How This PR addds support for detecting wether we're running with an RN version that uses these new frameworks or not - making sure we fallback to the old version when running with pre 0.87. In addition it updates the pod install pipeline with the same tests. NOTE: When merging 0.87 into Expo (as our current React Native), we can remove a lot of code in a follow-up PR that removes the VFS support all-over. There are a couple of 3rd party packages not compatible with RN 0.87 yet, so I tested with some patches for these packages. In addition a small binary fix was added to fix a missing method in 0.87 in EXpo's code. ## Test-plan - ✅ Built Bare-Expo, minimal-tester and precompiled with the new binaries from React native - ✅ Verified that the same versions are using VFS and precompiling correctly when using RN 0.86
The minimal machinery to build the packaged header structures: - headers-spec.js: the executable layout contract (rules R1-R8) — which namespaces are hoisted into the React framework, which carry module maps, and how collisions are rejected. - headers-inventory.js: scans the source tree and classifies every shipped header (language surface + modularizability bucket) — the input to the spec. computeInventory() feeds the build in-memory; the CLI writes a JSON manifest. - headers-compose.js: emits the layout — writes the <React/...> headers + umbrella + module map into each React.framework slice (detected by the framework's presence), and assembles the headers-only ReactNativeHeaders.xcframework (every other namespace + deps + Hermes). Called by xcframework.js during compose. This is the alternative header source that lets consumers resolve React Native headers without a clang VFS overlay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Emit the headers-spec layout unconditionally and delete the VFS overlay across
JS, CI publish, and Ruby. Consumers resolve headers the way the SwiftPM branch
does: <React/...> from the vendored React.framework, every other namespace from
ReactNativeHeaders. No root Headers/ on the xcframework, no VFS.
JS:
- xcframework.js: always emit the React.framework spec layout and build
ReactNativeHeaders.xcframework (was gated behind RN_ZERO_I_LAYOUT=1). Remove
the legacy header path entirely — the podspec->root-Headers enumeration,
createModuleMapFile, and copyHeaderFilesToSlices — so the published
React.xcframework is a standard framework (Info.plist + per-slice
React.framework/{Headers,Modules}), no root Headers/ or Modules/. Ship
ReactNativeHeaders.xcframework inside the reactnative-core tarball (sibling of
React.xcframework) so the prebuilt pod can vend both; keep the standalone
ReactNativeHeaders.xcframework.tar.gz for the SPM path. Drop the
React-VFS-template.yaml emit and the ./vfs import.
- vfs.js: deleted (its only consumer was xcframework.js).
- types.js: drop the now-unused VFSEntry/VFSOverlay/HeaderMapping types.
- replace-rncore-version.js: drop the React-VFS.yaml preservation rationale.
Ruby/CocoaPods:
- React-Core-prebuilt.podspec: vend React.xcframework (its per-slice
React.framework + module map serves <React/...> and @import React via
FRAMEWORK_SEARCH_PATHS); flatten ReactNativeHeaders' headers into a top-level
Headers/ in prepare_command and expose them via the pod header search path.
Drop the VFS-era root header_mappings_dir/module_map.
- rncore.rb: remove the -ivfsoverlay injection and process_vfs_overlay;
add_rncore_dependency and configure_aggregate_xcconfig now add a
HEADER_SEARCH_PATHS to React-Core-prebuilt/Headers for podspec, aggregate, and
third-party targets.
- react_native_pods.rb: drop the process_vfs_overlay post-install call.
Docs: replace the "VFS Overlay System" section with the headers-spec layout;
drop the obsolete "Known Issues" (pre-headers-spec) section.
Verified end-to-end: prebuild compose produces a VFS-free, root-Headers-free
React.xcframework; rn-tester pod install + xcodebuild (prebuilt path) BUILD
SUCCEEDED with zero -ivfsoverlay.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
React.framework is a clang module; when an SPM consumer precompiles it, a
modular React/ header that #imports <react/...> hit
-Wnon-modular-include-in-framework-module because the lowercase react/
namespace (served from ReactNativeHeaders, per R1's Linux/Windows-safe layout)
was deliberately kept out of any module.
Give react/ a module where it already lives instead of relocating it (relocation
would require case-folding react.framework -> React.framework, which only works
on case-insensitive filesystems):
- headers-spec.js: drop the react/ namespace-module exemption so its
objc-modular-candidates get a module; emit that module as
ReactNativeHeaders_react (a module literally named 'react' would alias the
React framework module on a case-insensitive filesystem). Module names are
internal; <react/...> still resolves by header path and is now modular.
- headers-inventory.js: classify C++ default member initializers in aggregates
(e.g. struct { NSString *family = nil; } in RCTFontProperties.h) as ObjC++ so
these are not misclassified objc-modular-candidate and pulled into a plain
ObjC module they cannot compile in.
The unguarded ObjC/C react/ headers (e.g. JSRuntimeFactoryCAPI.h) now resolve
modularly; the C++ react/renderer/* includes are #ifdef __cplusplus-guarded and
skipped during the ObjC module emit, so they need no module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In prebuilt mode the React core pods' code + headers live entirely in React.xcframework / React-Core-prebuilt. Re-installing their SOURCE podspecs made them ship duplicate headers that shadow the prebuilt artifact and break the React framework's clang explicit-module precompile (-Wnon-modular-include-in-framework-module) under Xcode 26. Install those core pods as dependency-only FACADES instead: generated podspecs with no sources/headers, installed via :path (so nothing is fetched), each depending on React-Core-prebuilt. Version, subspecs, default_subspec and resources (e.g. the privacy manifest) are DERIVED from the real podspec so the facade stays graph- and resource-equivalent to the source pod. With the shadowing gone the React module precompiles cleanly with SWIFT_ENABLE_EXPLICIT_MODULES on, so the Xcode-26 workaround (#53457) is removed. The prebuilt header search path + ReactNativeHeaders module-map activation are consolidated into a single post-install injection site (configure_aggregate_xcconfig); add_rncore_dependency now only declares the React-Core-prebuilt dependency. rn-tester's NativeComponentExample uses the canonical <React/...> include for RCTFabricComponentsPlugins.h (resolved from the framework) so it builds against the facaded React-RCTFabric in prebuilt mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`yarn format-check` (prettier) was failing CI on PR #57285. Run prettier on the ios-prebuild headers scripts (headers-compose.js, headers-inventory.js), replace-rncore-version.js, and __docs__/README.md so format-check passes. No logic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tarball The prebuilt React core now ships two xcframeworks — React.xcframework and the headers-only ReactNativeHeaders.xcframework. React-Core-prebuilt's prepare_command flattens the latter's Headers (including module.modulemap) into the pod. The compose step only tar'd React.xcframework, so consumers got no React-Core-prebuilt/Headers/module.modulemap and failed the build with "module map file ... not found". Tar both xcframeworks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le (R9) The public-umbrella model (which replaced the VFS overlay) excludes `+Private` and objc-blocked headers from React.framework's module, so privileged framework consumers (e.g. Expo) that `#import <React/RCTBridge+Private.h>`, `<React/RCTMountingManager.h>`, etc. fail to compile under explicit modules even though the headers still ship in React.framework/Headers. Add R9: a curated allowlist appended to the React module map — `RCTBridge+Private.h` as a real `header` (objc-modular-candidate, reaches no C++) and the six Fabric headers as `textual header` (objc-blocked; a real member would re-trip -Wnon-modular-include, and their <react/...> C++ includes resolve at the consumer's use site). Backwards-compatible: existing `#import <React/...>` (and Swift `import React`) sites are unchanged. Fails closed if an allowlisted header is removed/renamed or drifts bucket. Note: RCTUIKit.h / RCTRootContentView.h are absent from source entirely and need restoration, not exposure — out of scope here.
The flattened ReactNativeHeaders layout ships the individual React_RCTAppDelegate/*.h headers but no per-namespace umbrella. Consumers like Expo probe `<React_RCTAppDelegate/React_RCTAppDelegate-umbrella.h>` via __has_include (RCTAppDelegateUmbrella.h); with the umbrella gone the probe fails and RCTReactNativeFactory / RCTRootViewFactory are never declared, breaking the Expo pod's clang module. Add R10: emit a per-namespace umbrella (content DERIVED from namespaceModules so it can't drift — e.g. RCTArchConfiguratorProtocol.h, gone from this branch, is correctly omitted) and add it to that namespace's module so the import stays modular under explicit modules. Targeted via UMBRELLA_NAMESPACES (currently just React_RCTAppDelegate, the only umbrella Expo imports); fails closed if a listed namespace loses its modular headers.
…ic facade Community Fabric modules quote-import "RCTFabricComponentsPlugins.h" (~47x: slider, maps, pager-view, keyboard-controller, ...). In source, React-RCTFabric vended it at header_dir "React", so it landed in dependents' CocoaPods header maps and the bare quoted name resolved. In prebuilt mode React-RCTFabric is a dependency-only facade that ships no headers — the only copy is baked angle-only into React.framework (it's objc-blocked, excluded from the framework module map), so quoted imports fail to resolve. Re-vend JUST that one header from the facade (FACADE_REEXPOSED_HEADERS), copied as a self-contained snapshot at header_dir "React", restoring dependents' header maps exactly as the source pod did. Header-only (no compiled sources, no duplicate symbols — the impl stays in React.framework). Re-exposing a single header does not put <react/...>/<yoga/...> on -I, so it does not reintroduce the non-modular-include shadowing the modular layout eliminates. Fails closed if the glob matches nothing.
…the headers compose buildReactNativeHeadersXcframework copied each declared deps namespace (folly/glog/boost/fmt/double-conversion/fast_float) from the staged ReactNativeDependencies headers, but only console.warn'd on a missing one and kept going — silently shipping a ReactNativeHeaders.xcframework without third-party header resolution (consumers then fail on <folly/...> etc.). The summary log also printed the INTENDED namespace list, masking the gap. Throw instead: a missing declared deps namespace means deps weren't staged (third-party/ReactNativeDependencies.xcframework/Headers — from a full prebuild or the cache slot), so refuse to emit an incomplete artifact.
…est imports Two CI fixes for the prebuild-ios-core workflow: - prebuild-ios-core.yml: the compose-xcframework job downloaded the build slices and React headers but never staged third-party/ReactNativeDependencies.xcframework. buildReactNativeHeadersXcframework folds the third-party deps namespaces (folly/glog/boost/...) into ReactNativeHeaders.xcframework, so it fail-closed with "deps namespace 'folly' missing ... refusing to ship an incomplete ReactNativeHeaders.xcframework". Add the Download + Extract ReactNativeDependencies steps (mirroring build-slices) so the deps headers are present before composing. - headers-spec-test.js: reorder requires so the `../headers-spec` import sorts before `fs`, fixing the @react-native/monorepo/sort-imports warning that failed `eslint --max-warnings 0` in test_js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- copy facade resources into the facade pod dir: CocoaPods file accessors cannot glob past the pod root, so the ..-escaping globs shipped the privacy-manifest / i18n resource bundles empty - quote -fmodule-map-file so a PODS_ROOT containing spaces stays a single clang argument (matches the quoted HEADER_SEARCH_PATHS beside it) - fail closed in React-Core-prebuilt's prepare_command and in replace-rncore-version.js when the tarball lacks ReactNativeHeaders.xcframework, instead of silently leaving an empty or deleted Headers/ with the module-map flag dangling - thread rnRoot through planFromInventory/isUmbrellaSafe instead of the hardcoded hosting-package root (the one spot that didn't take the inventory's root) - drop the dead ios-prebuild templates/ files (their only consumers were removed by the headers-spec compose) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…1, verify, ratchet) Hardens the header layout against new/changed headers so consumer-facing regressions fail the PREBUILD instead of a downstream (rn-tester/Expo/ community) build: - headers-verify.js (new; runs in the prebuild compose CI job): include-health ratchet against a committed baseline (notShipped/unresolved/quoted- unresolvable includes in shipped headers — 27 baselined today); structural byte-compare of the composed module maps/umbrellas against the spec render; compile smokes — an ObjC TU precompiling the React module (every umbrella header) + all 14 R5 namespace modules + the R10 umbrella + __has_include asserts, an Expo-shaped ObjC++ TU (the R9 textual Fabric surface), and a Swift TU (import React + RCTBridge.moduleRegistry). - R11: one source, one content location. 116 sources ship under multiple spellings (React/X.h + CoreModules/RCTImage/RCTAnimation/... forms, bare React_RCTAppDelegate aliases). The flattened layout duplicated their declarations, so any -fmodules consumer touching two spellings (even transitively via a legacy spelling) hit redefinition errors — found by the gate's first run. The module-owned spelling keeps the content; every other spelling is emitted as a one-line redirect shim. - Single source of truth for third-party namespaces: the inventory's include classifier now derives from DEPS_NAMESPACES, and compose enforces set-equality with the deps artifact in BOTH directions (missing OR undeclared namespace fails). The undeclared direction immediately surfaced SocketRocket, which the deps artifact ships but was never relocated. - R5 exemption assert: an invalid-module-identifier namespace gaining a modular-candidate header now fails the plan instead of silently shipping a non-modular header. - Inventory records quoted includes that don't resolve to a shipped header (quotedNotShipped) instead of dropping them. Docs: __docs__/headers-rules.md documents rules R1-R11, the emission pipeline, and the resilience model.
…iveHeaders Declaring SocketRocket in DEPS_NAMESPACES (added to satisfy the deps set-equality guard) relocated textual copies of its headers into ReactNativeHeaders and thus into the flattened React-Core-prebuilt/Headers, which sits on every pod's HEADER_SEARCH_PATHS. The real SocketRocket pod still exists in consumer graphs and vends the same headers, so the copies collide: duplicate @interface definitions when the SocketRocket pod compiles (rn-tester, prebuilt + USE_FRAMEWORKS=dynamic), and a poisoned module graph in use_frameworks/explicit-modules apps that don't compile SocketRocket sources (Expo: ReactCodegen/RCTModulesConformingToProtocolsProvider.h "file not found"). Bisect confirmed removing the SocketRocket dir flips red->green and the R11 shims are not implicated. Add DEPS_NAMESPACES_NOT_RELOCATED: such namespaces count as declared for the set-equality guard (a genuinely new dep still fails closed), the include classifier still recognizes them, and the headers gate now asserts they stay ABSENT from the artifact. Note: no CI lane covers prebuilt + use_frameworks (the dynamic-frameworks lane builds from source), which is why CI stayed green.
62cabbe to
b4b1a94
Compare
…den compose freshness + shell usage
Review feedback on the header modularization (VFS-overlay removal):
- computeInventory() now returns natural-path collisions; computeSpecPlan()
fails closed on them (R8) so two sources projecting to the same Headers/
path can no longer be silently merged (only identities[0].source was kept).
- ensureHeadersLayout()'s freshness marker folds a sha256 of the compose
tooling (headers-{inventory,spec,compose}.js) so a local script edit forces
a recompose — the source xcframework's Info.plist mtime alone couldn't.
- scanHeader() strips /* ... */ block comments (multi-line aware) so a
documentation line mentioning namespace/template/constexpr can't trip the
C++ detector and shrink the umbrella.
- Anonymous aggregates with C++ member initializers (typedef struct { x = ..; })
are now detected as ObjC++ (tag name made optional).
- Multi-hyphen pod names namespaced correctly (.replace(/-/g,'_')).
- cp/tar shell-outs switched to execFileSync with arg arrays (CodeQL
"command from environment values"; parity with headers-verify.js).
- Clearer hermes-missing warning; headers-spec R9/R10 added to the contract
docblock (was R1–R8 then R11).
- New headers-inventory-test.js covering scanHeader (cxx guards, #else/#elif
flips, anonymous aggregate, block/line comments).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the detailed review! Addressed in Majors
Question — Minors / nits — all done: |
… sort-imports) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/bin/cp -c (APFS clonefile) is macOS-only; Linux CI exercises these paths through the jest integration tests, where GNU cp rejects it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oolchain calls The xcrun/clang/libtool/lipo/xcodebuild invocations in buildReactNativeHeadersXcframework used execSync with template-interpolated double-quoted paths; a path containing a quote or dollar sign could break or shell-expand. Converted to execFileSync argument arrays (matching headers-verify.js and the existing cp calls); libtool's 2>/dev/null shell redirect becomes stdio: ['ignore', 'pipe', 'ignore']. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D111448598. |
cortinico
left a comment
There was a problem hiding this comment.
Review automatically exported from Phabricator review in Meta.
…Frameworks (#57305) Summary: **Depends on #57285 In source builds, `React-Core` ships non-header resources — its privacy manifest (`PrivacyInfo.xcprivacy`) and its localized strings (RCTI18nStrings) — via the podspec's resource_bundles. In the prebuilt path those source pods aren't installed (CocoaPods facades) or aren't present at all (SwiftPM), so these resources were silently dropped: prebuilt/SwiftPM apps shipped no React Native privacy manifest, and localized strings were unavailable. This embeds them in React.framework at prebuild time so they ship uniformly across CocoaPods-prebuilt and SwiftPM, with source builds unchanged: - **Privacy manifest** — the PrivacyInfo.xcprivacy of the pods baked into React.framework are merged into one manifest at the framework root. React.framework is a dynamic framework, so Xcode's privacy-report aggregation picks it up automatically (no runtime involvement). - **RCTI18nStrings** — rebuilt as RCTI18nStrings.bundle inside React.framework. RCTLocalizedString.mm now resolves the bundle from its own framework first (bundleForClass:), falling back to the app's main bundle for static source builds. It also fixes a latent bug in `React-Core.podspec`: a later resource_bundles = was overwriting the earlier resource_bundle =, so source builds had stopped shipping RCTI18nStrings. Both bundles are now declared together. With the artifact owning these resources, the prebuilt RNCore facade no longer carries them. ## Changelog: [IOS][FIXED] - Ship React-Core's privacy manifest and localized strings (RCTI18nStrings) inside the prebuilt React.xcframework, so CocoaPods-prebuilt and SwiftPM apps include them Pull Request resolved: #57305 Test Plan: - ✅ yarn jest packages/react-native/scripts/ios-prebuild — unit + integration tests for the merge/discovery/bundle-build logic (red/green). - ✅ Built React.xcframework from this branch and confirmed each slice's React.framework carries the merged PrivacyInfo.xcprivacy and RCTI18nStrings.bundle (37 locales). - ✅ Cold-built rn-tester in prebuilt mode and verified the app bundle contains Frameworks/React.framework/{PrivacyInfo.xcprivacy, RCTI18nStrings.bundle}. - ✅ Confirmed React-Core.podspec now reports both resource bundles (RCTI18nStrings, React-Core_privacy) for source builds. Reviewed By: fabriziocucci Differential Revision: D111448862 Pulled By: cipolleschi fbshipit-source-id: a95360f51cc1131510ef03f7e48c689575d1c22d
Summary:⚠️ This is a stacked PR — review/merge the base PRs first Builds on, and should land after (bottom-up): 1. #57285 — Remove the Clang VFS overlay / modularize React headers (base of the stack). <React/…> via the framework module map; lowercase namespaces (react/, yoga/, jsi/, …) via the ReactNativeHeaders module map. 2. #57305 — Prebuilt artifact resources + ReactNativeHeaders.xcframework. Ships the prebuilt core artifacts (incl. ReactNativeHeaders in the tarball + embedded React.framework resources) and the CocoaPods React-Core-prebuilt facades. 3. #57440 — Self-serving prebuilt ReactNativeDependencies + dependency-only facades. The deps pod becomes the single header authority for the third-party namespaces (RCT-Folly/glog/boost/…); community `s.dependency "RCT-Folly"` resolves to local facades instead of trunk source pods. 4. #57442 — ReactNativeDependenciesHeaders sidecar + pure-RN ReactNativeHeaders (immediate base). The deps prebuild emits a headers-only LIBRARY-type sidecar (the binary deps xcframework is framework-type — invisible to SwiftPM binaryTargets), ReactNativeHeaders drops the third-party namespaces, and both headers artifacts publish standalone to Maven. The whole stack is rebased onto current main (2026-07-06). This PR consumes the final **five-artifact set**: React, ReactNativeHeaders (pure-RN), ReactNativeDependencies, ReactNativeDependenciesHeaders, hermes-engine. Adds npx react-native spm and the package-generation tooling that turns an app into a SwiftPM-integrated RN app using the base stack's prebuilt XCFrameworks. CocoaPods stays supported — additive, opt-in, no Ruby toolchain. Integration is injected into the existing .xcodeproj in place (nothing generated/renamed/replaced), recorded in .spm-injected.json so it reverses exactly. ### What this PR adds - The spm CLI (add/update/deinit/scaffold + hidden sync/codegen/download), zero-arg auto-resolution, --deintegrate for CocoaPods→SwiftPM. - SwiftPM package generation (scripts/spm/): autolinking→Package.swift, Codegen→React-GeneratedCode, core XCFramework binary targets, artifact download/cache, surgical pbxproj inject/remove. - ReactNativeDependenciesHeaders wired as the 5th binaryTarget: the headers-only companions (ReactNativeHeaders from the core tarball, the deps sidecar from the deps tarball) are staged automatically out of their parent tarballs — no --headers-tarball priming; REQUIRED_ARTIFACTS covers all five. - Community-library scaffolding from podspecs (incl. root-level-source podspecs via generated include/<SwiftName>/ shims, sibling wiring for transitive spm.dependencies, self-ingestion guards). - In-place Xcode integration (XCLocalSwiftPackageReference + Sync build phase + scheme pre-action + auto-sync; ${PODS_ROOT}-anchored REACT_NATIVE_PATH replaced on --deintegrate with exact deinit rollback). - rn-tester + helloworld SwiftPM consumption + test library; npm-packaging hygiene; a small RNCoreFacades.podspec_dir fix. ## Documentation - **Tool docs** (usage, quick start, architecture): [`scripts/spm/__doc__/spm-scripts.md`](https://github.com/react/react-native/blob/chrfalch/swift-package-manager/packages/react-native/scripts/spm/__doc__/spm-scripts.md) - **Header-paths contract** (how the five artifacts serve headers): [`scripts/spm/__doc__/spm-header-paths-contract.md`](https://github.com/react/react-native/blob/chrfalch/swift-package-manager/packages/react-native/scripts/spm/__doc__/spm-header-paths-contract.md) - **RFC** (updated): [react-native-community/discussions-and-proposals#994](react-native-community/discussions-and-proposals#994) ## Changelog: [IOS] [ADDED] - `npx react-native spm` command + SwiftPM package-generation tooling (opt-in; CocoaPods stays supported) Pull Request resolved: #57332 Test Plan: 350 scripts/spm unit tests (incl. byte-identical add→deinit round-trip); manual E2E against the five-artifact set: fresh app via `cli init`→`spm add --deintegrate`→build (artifact resources verified in the app), rn-tester in-place migration (RNTesterPods.xcodeproj, test libraries + spmModules), helloworld in-place migration — all BUILD SUCCEEDED. Verified `npx react-native spm <cmd>` from the command line in each journey. ## Scope & limitations iOS, prebuilt-only (no build-from-source yet); full spm.xcframework/spm.source library metadata not yet (app-local spm.modules + scaffolding are); Expo not yet. ## Follow-ups Remote-mode ReactNative Package.swift must vend the ReactNativeDependenciesHeaders product (spm-distribution repo); library self-containment for repo-portable manifests; build-from-source. Reviewed By: mdvacca Differential Revision: D111449548 Pulled By: cipolleschi fbshipit-source-id: 87a873bf5c8be2d60f7893611e16924bfa730a46
|
@cipolleschi merged this pull request in 376bd0e. |
…tNativeHeaders framework (#57285) Summary: The prebuilt `React.xcframework` previously relied on a Clang VFS overlay (`React-VFS.yaml`) to make headers importable, because the headers were laid out in CocoaPods-style namespaced folders rather than standard framework conventions. The overlay had to be generated at build time, re-resolved at pod-install time per slice, and injected as `-ivfsoverlay` flags into every Obj-C, C++, and Swift compile (including aggregate and third-party pod targets). This is fragile, hard to reason about, and incompatible with SwiftPM consumption. This PR removes the VFS overlay entirely and resolves headers through standard framework/header-search-path mechanics instead. **Headers are now emitted into the artifact according to an explicit, executable spec:** - **`React.xcframework`** — each slice's `React.framework` carries every `<React/...>` header plus a framework module map, so `#import <React/...>` and `import React` resolve through `FRAMEWORK_SEARCH_PATHS` automatically. - **`ReactNativeHeaders.xcframework`** (new, headers-only) — carries every other namespace (`<react/...>`, `<yoga/...>`, `folly`, `glog`, …), shipped alongside in the prebuilt tarball and exposed via a single header search path. - This makes `ReactNativeDependencies` binary-only. No clang VFS overlay, no per-target `-ivfsoverlay` flags. The layout is driven by a single source of truth (`headers-spec.js`, rules R1–R11) that both the prebuild compose step and downstream SwiftPM tooling derive from, so the shipped header set cannot drift from the spec. Source headers are byte-identical to the repo — the only consumer-facing change needed is bare-form angle includes (`#import <RCTAppDelegate.h>` → `#import <React/RCTAppDelegate.h>`). **Consumer surfaces the flattened layout initially dropped are restored** (validated against Expo and community Fabric modules): - Private headers (`RCTBridge+Private.h` + the Fabric `RCTComponentView*` family) are exposed in the `React` module map — modular where safe, `textual` where they reach C++ — so frameworks like Expo compile unchanged, incl. Swift access to `RCTBridge.moduleRegistry`. - `React_RCTAppDelegate-umbrella.h` is re-emitted (derived from the live header set) for consumers probing it via `__has_include`. - Sources shipping under multiple include spellings (`React/X.h` + legacy `CoreModules/…`, `RCTImage/…`, bare aliases — 116 today) keep content at ONE module-owned spelling; other spellings become generated redirect shims, so `-fmodules` consumers cannot hit duplicate declarations. - The `React-RCTFabric` facade re-vends `RCTFabricComponentsPlugins.h` at `header_dir "React"`, keeping community Fabric modules' quoted `#import "RCTFabricComponentsPlugins.h"` working as with source pods. **The layout is verified at generator time** (`headers-verify.js`, runs in the prebuild compose CI job): unresolvable includes ratchet against a committed baseline, composed module maps/umbrellas must byte-match the spec render, and consumer-shaped compile smokes must pass (the `React` module, every namespace module, an Expo-shaped ObjC++ fixture, and a Swift `moduleRegistry` fixture). Fail-closed guards cover header collisions, allowlist drift, and missing OR undeclared third-party deps namespaces (the latter surfaced `SocketRocket`, which is deliberately NOT relocated — the real pod vends it, and textual copies collide under `use_frameworks`; the gate asserts its absence). **Key changes** - **New**: `headers-spec.js` (the executable layout contract, R1–R11), `headers-compose.js` (emitter for both xcframeworks), `headers-inventory.js` (podspec-driven header classifier feeding the spec + a diagnostic manifest), `headers-verify.js` (generator-time gate + CI step), `__docs__/headers-rules.md` (rules + rationale). - **Removed**: `vfs.js`, VFS types in `types.js`, and the VFS processing/flag-injection paths in `rncore.rb` and `xcframework.js`. - **Updated**: `React-Core-prebuilt.podspec` (vends both xcframeworks, flattens `ReactNativeHeaders` headers into `Headers/` via `prepare_command`, fails closed on incomplete tarballs), `rncore.rb` / `react_native_pods.rb` (header search path instead of overlay flags), `prebuild-ios-core.yml` (core tarball ships both xcframeworks; compose job verifies the composed headers), README (VFS docs replaced with the new model). - Added facades to the Podspecs that shouldn't be in use when running using precompiled frameworks to satisfy dependencies as empty pod-specs (with the single `RCTFabricComponentsPlugins.h` re-vend exception noted above). ## Changelog: [IOS] [CHANGED] - Remove the Clang VFS overlay from prebuilt React Native Core; resolve headers via React.xcframework + a new headers-only ReactNativeHeaders.xcframework Pull Request resolved: #57285 Test Plan: - [x] rn-tester builds against the prebuilt `React-Core-prebuilt` pod (Debug + Release) with no `-ivfsoverlay` flags present in the generated xcconfigs. - [x] rn-tester builds against React native source code (without any prebuilt artifacts) - [x] `#import <React/...>`, `import React;`, and the relocated namespaces (`<react/...>`, `<yoga/...>`, `folly`/`glog`) all resolve. - [x] Prebuilt tarball contains both `React.xcframework` and `ReactNativeHeaders.xcframework`; pod install flattens headers into `React-Core-prebuilt/Headers`. - [x] Switch RN-tester between Debug/Release and verify that both `React.xcframework` and `ReactNativeHeaders.xcframework` are changed between debug and release correctly. - [x] `headers-verify.js` gate green on Debug and Release composes (`-Werror=non-modular-include-in-framework-module` never trips in consumer builds). - [x] `private/helloworld` builds against the prebuilt core (CocoaPods path). - [x] Expo SDK compiles against the prebuilt artifacts (private headers, `React_RCTAppDelegate` umbrella probe, Fabric quoted imports). Reviewed By: fabriziocucci Differential Revision: D111448598 Pulled By: cipolleschi fbshipit-source-id: 72bc2f37765ad425722161a038e410ba976858a5
…Frameworks (#57305) Summary: **Depends on #57285 In source builds, `React-Core` ships non-header resources — its privacy manifest (`PrivacyInfo.xcprivacy`) and its localized strings (RCTI18nStrings) — via the podspec's resource_bundles. In the prebuilt path those source pods aren't installed (CocoaPods facades) or aren't present at all (SwiftPM), so these resources were silently dropped: prebuilt/SwiftPM apps shipped no React Native privacy manifest, and localized strings were unavailable. This embeds them in React.framework at prebuild time so they ship uniformly across CocoaPods-prebuilt and SwiftPM, with source builds unchanged: - **Privacy manifest** — the PrivacyInfo.xcprivacy of the pods baked into React.framework are merged into one manifest at the framework root. React.framework is a dynamic framework, so Xcode's privacy-report aggregation picks it up automatically (no runtime involvement). - **RCTI18nStrings** — rebuilt as RCTI18nStrings.bundle inside React.framework. RCTLocalizedString.mm now resolves the bundle from its own framework first (bundleForClass:), falling back to the app's main bundle for static source builds. It also fixes a latent bug in `React-Core.podspec`: a later resource_bundles = was overwriting the earlier resource_bundle =, so source builds had stopped shipping RCTI18nStrings. Both bundles are now declared together. With the artifact owning these resources, the prebuilt RNCore facade no longer carries them. ## Changelog: [IOS][FIXED] - Ship React-Core's privacy manifest and localized strings (RCTI18nStrings) inside the prebuilt React.xcframework, so CocoaPods-prebuilt and SwiftPM apps include them Pull Request resolved: #57305 Test Plan: - ✅ yarn jest packages/react-native/scripts/ios-prebuild — unit + integration tests for the merge/discovery/bundle-build logic (red/green). - ✅ Built React.xcframework from this branch and confirmed each slice's React.framework carries the merged PrivacyInfo.xcprivacy and RCTI18nStrings.bundle (37 locales). - ✅ Cold-built rn-tester in prebuilt mode and verified the app bundle contains Frameworks/React.framework/{PrivacyInfo.xcprivacy, RCTI18nStrings.bundle}. - ✅ Confirmed React-Core.podspec now reports both resource bundles (RCTI18nStrings, React-Core_privacy) for source builds. Reviewed By: fabriziocucci Differential Revision: D111448862 Pulled By: cipolleschi fbshipit-source-id: a95360f51cc1131510ef03f7e48c689575d1c22d
Summary
The prebuilt
React.xcframeworkpreviously relied on a Clang VFS overlay (React-VFS.yaml) to make headers importable, because the headers were laid out in CocoaPods-style namespaced folders rather than standard framework conventions. The overlay had to be generated at build time, re-resolved at pod-install time per slice, and injected as-ivfsoverlayflags into every Obj-C, C++, and Swift compile (including aggregate and third-party pod targets).This is fragile, hard to reason about, and incompatible with SwiftPM consumption.
This PR removes the VFS overlay entirely and resolves headers through standard framework/header-search-path mechanics instead.
Headers are now emitted into the artifact according to an explicit, executable spec:
React.xcframework— each slice'sReact.frameworkcarries every<React/...>header plus a framework module map, so#import <React/...>and@import Reactresolve throughFRAMEWORK_SEARCH_PATHSautomatically.ReactNativeHeaders.xcframework(new, headers-only) — carries every other namespace (<react/...>,<yoga/...>,folly,glog, …), shipped alongside in the prebuilt tarball and exposed via a single header search path.ReactNativeDependenciesbinary-only.No clang VFS overlay, no per-target
-ivfsoverlayflags. The layout is driven by a single source of truth (headers-spec.js, rules R1–R11) that both the prebuild compose step and downstream SwiftPM tooling derive from, so the shipped header set cannot drift from the spec.Source headers are byte-identical to the repo — the only consumer-facing change needed is bare-form angle includes (
#import <RCTAppDelegate.h>→#import <React/RCTAppDelegate.h>).Consumer surfaces the flattened layout initially dropped are restored (validated against Expo and community Fabric modules):
RCTBridge+Private.h+ the FabricRCTComponentView*family) are exposed in theReactmodule map — modular where safe,textualwhere they reach C++ — so frameworks like Expo compile unchanged, incl. Swift access toRCTBridge.moduleRegistry.React_RCTAppDelegate-umbrella.his re-emitted (derived from the live header set) for consumers probing it via__has_include.React/X.h+ legacyCoreModules/…,RCTImage/…, bare aliases — 116 today) keep content at ONE module-owned spelling; other spellings become generated redirect shims, so-fmodulesconsumers cannot hit duplicate declarations.React-RCTFabricfacade re-vendsRCTFabricComponentsPlugins.hatheader_dir "React", keeping community Fabric modules' quoted#import "RCTFabricComponentsPlugins.h"working as with source pods.The layout is verified at generator time (
headers-verify.js, runs in the prebuild compose CI job): unresolvable includes ratchet against a committed baseline, composed module maps/umbrellas must byte-match the spec render, and consumer-shaped compile smokes must pass (theReactmodule, every namespace module, an Expo-shaped ObjC++ fixture, and a SwiftmoduleRegistryfixture). Fail-closed guards cover header collisions, allowlist drift, and missing OR undeclared third-party deps namespaces (the latter surfacedSocketRocket, which is deliberately NOT relocated — the real pod vends it, and textual copies collide underuse_frameworks; the gate asserts its absence).Key changes
headers-spec.js(the executable layout contract, R1–R11),headers-compose.js(emitter for both xcframeworks),headers-inventory.js(podspec-driven header classifier feeding the spec + a diagnostic manifest),headers-verify.js(generator-time gate + CI step),__docs__/headers-rules.md(rules + rationale).vfs.js, VFS types intypes.js, and the VFS processing/flag-injection paths inrncore.rbandxcframework.js.React-Core-prebuilt.podspec(vends both xcframeworks, flattensReactNativeHeadersheaders intoHeaders/viaprepare_command, fails closed on incomplete tarballs),rncore.rb/react_native_pods.rb(header search path instead of overlay flags),prebuild-ios-core.yml(core tarball ships both xcframeworks; compose job verifies the composed headers), README (VFS docs replaced with the new model).RCTFabricComponentsPlugins.hre-vend exception noted above).Changelog:
[IOS] [CHANGED] - Remove the Clang VFS overlay from prebuilt React Native Core; resolve headers via React.xcframework + a new headers-only ReactNativeHeaders.xcframework
Test Plan
React-Core-prebuiltpod (Debug + Release) with no-ivfsoverlayflags present in the generated xcconfigs.#import <React/...>,@import React;, and the relocated namespaces (<react/...>,<yoga/...>,folly/glog) all resolve.React.xcframeworkandReactNativeHeaders.xcframework; pod install flattens headers intoReact-Core-prebuilt/Headers.React.xcframeworkandReactNativeHeaders.xcframeworkare changed between debug and release correctly.headers-verify.jsgate green on Debug and Release composes (-Werror=non-modular-include-in-framework-modulenever trips in consumer builds).private/helloworldbuilds against the prebuilt core (CocoaPods path).React_RCTAppDelegateumbrella probe, Fabric quoted imports).