Skip to content

fix(iOS): keep prebuilt Headers/ in place on a Debug/Release swap - #57814

Open
chrfalch wants to merge 1 commit into
mainfrom
chrfalch/fix-rncore-swap-modulemap
Open

fix(iOS): keep prebuilt Headers/ in place on a Debug/Release swap#57814
chrfalch wants to merge 1 commit into
mainfrom
chrfalch/fix-rncore-swap-modulemap

Conversation

@chrfalch

@chrfalch chrfalch commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #57803. An iOS Release build can fail in PrecompileModule React with seven include of non-modular header inside framework module errors — but only when the build follows a Debug/Release configuration switch, and only when the project contains a pod target that does not depend on React.

replace-rncore-version.js deleted and recreated Pods/React-Core-prebuilt/Headers/ on a swap. That directory holds module.modulemap, which rncore.rb activates on every target through -fmodule-map-file.

A pod that depends on React has a target-dependency edge that orders its module scan behind React-Core-prebuilt's script phase. A pod with no React dependency has no such edge, so its scan is free to run before the swap. The React module is then precompiled without the module map, and <yoga/...>, <react/...> and <RCTDeprecation/...> resolve textually and trip -Wnon-modular-include-in-framework-module.

Those headers never needed replacing. The prebuild compose job emits one set of ReactNativeHeaders for both configurations, so they are identical in the Debug and Release tarballs — only the compiled framework differs. This replaces React.xcframework and nothing else, so the module map stays put for the whole build and the ordering stops mattering.

Changelog:

[IOS] [FIXED] - Keep the prebuilt Headers/ in place on a Debug/Release configuration switch so the React explicit module still resolves its module map

Test Plan

The premise, on the published 0.87.0-rc.3 artifacts (ios-arm64_x86_64-simulator):

compared between the Debug and Release tarballs result
ReactNativeHeaders…/Headers/module.modulemap identical
React.framework/Modules/module.modulemap identical
ReactNativeHeaders…/Headers tree (diff -rq) 0 differences
React.framework/Headers tree (diff -rq) 0 differences

The reproducer from #57803, on Xcode 26.3 with CocoaPods 1.15.2:

build result
0.87.0-rc.3 FAIL — exit 65, 7 errors
0.87.0-rc.3 + this PR PASS** BUILD SUCCEEDED **, 0 errors

The swap still does its job in the passing build — it logs Replacing React-Core-prebuilt/React.xcframework, and the installed binary is the Release one:

installed:   55225ccbc283c57c614ff4caf263cb63bad3828240e62cee8893e7001774bd6c
rc3 release: 55225ccbc283c57c614ff4caf263cb63bad3828240e62cee8893e7001774bd6c
rc3 debug:   516215801a6f8a86640aae13c2f2de1bbdb95189edf124e208f528b1497c7e4c

A Release→Debug swap was verified the same way. Across a swap, Headers/module.modulemap keeps its inode while React.xcframework gets a new one.

Isolating the precondition

On stock 0.87.0-rc.3, changing only the reproducer's IndependentPod, and reading back which targets' ScanDependencies ran before the swap:

IndependentPod.podspec scans before the swap result
no dependencies (as reported) IndependentPod FAIL 2/2
+ spec.dependency 'React-Core' none PASS 2/2
pod removed from the Podfile none PASS 2/2

Adding the dependency was the only change — same Podfile declaration, same local :path pod, same source file importing only Foundation.

Note this is an ordering race, not a structural guarantee: a project with no React-independent pod is very likely safe but not provably so, since a different job count or machine load could reorder scans. This PR removes the race rather than the ordering, so the precondition stops mattering either way.

Adds a unit test for the script (3 cases: correct framework installed, module map untouched, fail on a tarball with no React.xcframework). The script needed a require.main === module guard and one export to be importable.

Note for the release crew

This is a candidate for 0.87.0-rc.4. #57803 reproduces on rc.2 and rc.3 and is not fixed by anything currently on 0.87-stable; I verified #57742 in particular does not address it.

Exposure: a stock template app has no React-independent pod and passed here. Apps that pull a native SDK pod directly (Firebase, Sentry, analytics or networking libraries) or vendor a local utility pod are the exposed shape, including via transitive pods of an RN wrapper.

Not fixed here: React.xcframework is still replaced mid-build, and Xcode's xcframework-extraction task is not ordered against this script phase either. No failure was observed from that, and closing it properly means installing both configurations side by side instead of mutating pod content during a build — too large for a release cut.

🤖 Generated with Claude Code

An iOS Release build can fail in `PrecompileModule React` with seven
`include of non-modular header inside framework module` errors, but only
when the build follows a Debug/Release configuration switch (#57803).

`replace-rncore-version.js` deleted and recreated
`Pods/React-Core-prebuilt/Headers/` on a swap. That directory holds
`module.modulemap`, which `rncore.rb` activates on every target through
`-fmodule-map-file`. Nothing orders an unrelated target's dependency scan
against this script phase, so a scan can run while the module map is
missing; the React module is then precompiled without it and `<yoga/...>`,
`<react/...>` and `<RCTDeprecation/...>` resolve non-modularly.

Those headers never needed replacing. The prebuild compose job emits one
set of ReactNativeHeaders for both configurations, so they are identical
in the Debug and Release tarballs — only the compiled framework differs.
Replace `React.xcframework` and nothing else.

## Changelog:

[IOS] [FIXED] - Keep the prebuilt `Headers/` in place on a Debug/Release configuration switch so the React explicit module still resolves its module map

Co-Authored-By: Claude Opus 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 Aug 4, 2026
@facebook-github-tools facebook-github-tools Bot added p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Aug 4, 2026
@meta-codesync

meta-codesync Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

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: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS prebuilt core: React explicit-module build fails after a Debug-to-Release swap

1 participant