Skip to content

Make C++ stable API guards textual in the ReactNativeHeaders module map - #59068

Draft
coado wants to merge 1 commit into
react:mainfrom
coado:fix-spm-textual-guard-headers
Draft

coado wants to merge 1 commit into
react:mainfrom
coado:fix-spm-textual-guard-headers

Conversation

@coado

@coado coado commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Summary:

Apps that use React Native through Swift Package Manager and define RN_STRICT_API (the C++ stable API opt-in, see #59043) cannot build import React.

The prebuilt ReactNativeHeaders.xcframework ships a module map with one module per namespace. The prebuild makes every header that reaches no C++ a member of that module, and the react/cxxstableapi guards qualify because they are pure preprocessor code. So ReactNativeHeaders_react lists FrameworksGuard.h, PrivateGuard.h and UmbrellaGuard.h themselves, plus react/debug/flags.h and the JSRuntimeFactory headers that include them.

Clang compiles module members on their own, using the importing target's -D macros, with neither RN_BUILDING nor RN_UMBRELLA_CONTEXT set. With RN_STRICT_API defined, PrivateGuard.h and UmbrellaGuard.h hit their #error while building the module. import React needs that module (RCTHermesInstanceFactory.h imports <react/runtime/JSRuntimeFactoryCAPI.h>), so every Swift file importing React, and every Objective-C++ file importing <React/...> with modules on, fails.

This change declares the guards, and every module member that reaches one through unguarded includes, as textual header (new rule R13 in headers-spec.js). They still belong to the module, so React.framework may keep including them without a non-modular include error, but they are expanded at each include site, where the guard sees its real includer. On main this covers 8 headers across ReactNativeHeaders_react, ReactCommon and reactperflogger.

Other changes:

  • headers-verify.js runs the Swift import React gate a second time with RN_STRICT_API defined. Its module import fixture now picks a non-textual member of each module.
  • The R5 comment said react/ gets no module. It does, named ReactNativeHeaders_react.

Changelog:

[IOS] [FIXED] - Fix import React failing to build in Swift Package Manager apps that define RN_STRICT_API

Test Plan:

Unit tests, lint and types:

yarn test packages/react-native/scripts/ios-prebuild   # 95 passed, 5 of them new
npx eslint --max-warnings 0 <changed files>             # clean
npx prettier --check <changed files>                    # clean
yarn flow-check                                         # no errors in the changed files

Prebuild end to end (Debug, iOS simulator slice), from packages/react-native:

RN_DEP_VERSION=nightly node scripts/ios-prebuild.js -s -f Debug
node scripts/ios-prebuild.js -b -f Debug -p ios-simulator
node scripts/ios-prebuild.js -c -f Debug -p ios-simulator
node scripts/ios-prebuild/headers-verify.js --flavor Debug
  • The composed module map has the 8 textual header entries, and headers-verify.js reports ALL GATES PASSED, including the new strict gate.
  • Negative control: composing with only headers-spec.js reverted, every gate still passes except the new strict gate, which fails on PrivateGuard.h.

RNTester through SwiftPM, with #59043 applied and the artifact above packaged as CI does (ReactCoreDebug.xcframework.tar.gz):

RN_CORE_TARBALL_PATH=/path/to/ReactCoreDebug.xcframework.tar.gz npx react-native spm download --artifacts /tmp/spm-artifacts
npx react-native spm scaffold --artifacts /tmp/spm-artifacts --download skip || true
RN_STRICT_API=1 npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip
xcodebuild -project RNTesterPods.xcodeproj -scheme RNTester -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build
Scenario Before After
RN_STRICT_API on Fails: ReactNativeHeaders_react won't precompile, Swift target fails Build succeeds, app links
RN_STRICT_API on, private include in app code — error
RN_STRICT_API on, direct public include — error
RN_STRICT_API on, "for frameworks" include — warning
RN_STRICT_API on, umbrella include — builds
RN_STRICT_API off (spm update without it) Builds Builds, no guard diagnostics

The "Before" row was built against the published nightly. Swift with C++ interop also builds with RN_STRICT_API once main's RCTCallInvoker.h (which now includes <React/CallInvoker.h>) is in the artifact.

Not run: Release flavor, device and Mac Catalyst slices. React Native's dependencies and Hermes came from the nightly rather than being built locally.

🤖 Generated with Claude Code

The prebuild declares every ReactNativeHeaders header that reaches no C++
as a member of its namespace module. That includes the react/cxxstableapi
guards and the headers that include them (react/debug/flags.h,
react/runtime/JSRuntimeFactory*.h, ...). Clang compiles module members on
their own, with the importer's -D macros and without RN_BUILDING or
RN_UMBRELLA_CONTEXT, so when an app defines RN_STRICT_API the guards fire
while building ReactNativeHeaders_react and `import React` fails.

Declare the guards, and every member that reaches one through unguarded
includes, as `textual header`. They still belong to the module, so
React.framework may include them, but they are expanded at each include
site, where the guard sees its real includer. headers-verify now also
type-checks the Swift `import React` fixture with RN_STRICT_API defined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 14, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner p: Facebook Partner: Facebook labels Oct 14, 2026
@coado
coado marked this pull request as draft October 14, 2026 12:20
@meta-codesync

meta-codesync Bot commented Oct 15, 2026

Copy link
Copy Markdown

@coado has imported this pull request. If you are a Meta employee, you can view this in D125444181.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Facebook Partner: Facebook p: Software Mansion Partner: Software Mansion Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant