Skip to content

Make C++ stable API guards textual in the ReactNativeHeaders module map (#59068) - #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:

[Internal]

Test Plan:
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.

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

This was also tested locally on rn-tester.

Differential Revision: D125444181

Pulled By: coado

@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.

…ap (react#59068)

Summary:
Apps that use React Native through Swift Package Manager and define `RN_STRICT_API` (the C++ stable API opt-in, see react#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:
[Internal]


Test Plan:
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.

RNTester through SwiftPM, with react#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](https://claude.com/claude-code)


This was also tested locally on rn-tester.

Differential Revision: D125444181

Pulled By: coado
@meta-codesync meta-codesync Bot changed the title Make C++ stable API guards textual in the ReactNativeHeaders module map Make C++ stable API guards textual in the ReactNativeHeaders module map (#59068) Oct 19, 2026
@coado
coado force-pushed the fix-spm-textual-guard-headers branch from 84a4668 to e96a2d5 Compare October 19, 2026 14:01
@meta-codesync

meta-codesync Bot commented Oct 19, 2026

Copy link
Copy Markdown

@coado has exported this pull request. If you are a Meta employee, you can view the originating Diff in D125444181.

meta-codesync Bot pushed a commit that referenced this pull request Oct 20, 2026
Summary:
SwiftPM artifact setup currently fails an entire CI lane when a Maven response stalls mid-body. This change:

- aborts a download after 60 seconds without body progress;
- retries transient transport errors and HTTP 408/429/5xx responses up to three total attempts with short backoff;
- keeps deterministic HTTP client errors fail-fast; and
- removes partial `.download` files before retrying, so a retry cannot reuse truncated content.

## Changelog:

[INTERNAL] [FIXED] - Retry stalled SwiftPM artifact downloads

## Diagnosis

`main` commit `f1f516e8cb2f6c085bcc08838066b5b49c08da3b` failed `test_ios_spm_rntester / test (Debug)` in job https://github.com/react/react-native/actions/runs/39353507614/job/118412160607. The Hermes release artifact stopped at 8.0 MB of 24.2 MB and 0 KB/s for about ten minutes before Node fetch reported `terminated`.

This is a transport flake rather than a deterministic source regression:

- the Release sibling on the same SHA completed successfully;
- both SwiftPM RNTester lanes passed on each of the 19 preceding `main` workflow runs (38/38 checks);
- the tip commit only changes Fantom SelectableParagraph rendering; and
- the exact failed artifact downloaded successfully on a subsequent probe (25,372,145 bytes in 0.29 seconds).

Because the failure is nondeterministic and independent of the commit contents, a code bisect cannot identify a meaningful introducing commit. Searches of open PRs and prior CI-monitor runs found no existing fix for this stalled-download signature. PR #59068 mentions the downloader only in its diagnosis and changes different prebuild files.

Pull Request resolved: #59214

Test Plan:
- `node --check packages/react-native/scripts/spm/download-spm-artifacts.js`
- `npx -y prettier@3.9.4 --write packages/react-native/scripts/spm/download-spm-artifacts.js packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js` (no changes)
- `jest --runInBand packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js` — 76/76 tests passed, including transient stream retry, idle-body retry, partial-file cleanup, and fail-fast HTTP 404 coverage
- Live curl probe of the exact Hermes artifact — HTTP 200, 25,372,145 bytes

## Risk and limitations

A genuinely slow transfer that makes no progress for 60 seconds is restarted, which may add up to roughly two minutes plus backoff before the final failure. The retry is bounded and final errors are still surfaced; deterministic 4xx responses are not retried. Full RNTester/Xcode CI was not reproduced locally because the failure is in remote transport rather than compilation.

Reviewed By: cipolleschi

Differential Revision: D126318143

Pulled By: cortinico

fbshipit-source-id: c413b2278c4ca735e69a1d275f33e74fcea679ca

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. meta-exported 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