Skip to content

fix(iOS): stamp the React Native version in the prebuild compose job#57603

Closed
chrfalch wants to merge 2 commits into
mainfrom
chrfalch/prebuild-stamp-compose
Closed

fix(iOS): stamp the React Native version in the prebuild compose job#57603
chrfalch wants to merge 2 commits into
mainfrom
chrfalch/prebuild-stamp-compose

Conversation

@chrfalch

@chrfalch chrfalch commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

prebuild-ios-core.yml builds the prebuilt iOS core in two jobs: build-rn-slice compiles the React binary per platform slice, and compose-xcframework runs afterwards in its own fresh checkout, downloads the slice artifacts, and composes the shipped React.xcframework + ReactNativeHeaders.xcframework — re-deriving the shipped headers from that checkout.

The "Set React Native version" step runs only in build-rn-slice. So the compiled binary is stamped, but the composed headers are not: the artifact ships ReactNativeVersion.h with the 1000.0.0 dev sentinel, internally inconsistent with its own binary (and with the npm package, which is stamped in its own publish job).

This was latent for as long as the compose job has existed — it only started breaking consumers now because the header-facades / VFS-overlay-removal work changed which file libraries actually read. Previously header resolution (the VFS overlay, and the Pods/Headers/Public symlinks of the source pods) redirected reads to the stamped npm copy, so the sentinel bytes in the tarball were never consumed. With real materialized headers, the prebuilt copy now wins the search path, and any library gating on REACT_NATIVE_VERSION_MAJOR/MINOR compiles the wrong branch. That breaks [ios] react-native-unistyles in nightly-tests: its #if REACT_NATIVE_VERSION_MINOR >= 81 sees MINOR 0 and picks a removed pre-0.81 path (shadowNodeFromValue).

Fix — built-headers overlay (no re-stamp/revert). The build-rn-slice job already stamps its checkout before building and uploads .build/headers, and the compose job already downloads it — it was just unused. stageEntries now takes an overlay dir and, for ReactNativeVersion.h only, prefers the built copy (stamped in the slice job) over the source sentinel. Header layout is still spec-derived from source (podspec inventory, collision detection, classification), and every other header still copies from source — so a stale build tree can never ship wrong header content, only the one build-generated version header is overlaid. No stamping of the compose checkout, no git revert, no cache-key desync.

The compose cache key is bumped so pre-fix (unstamped) composed artifacts cached under the old key are not served.

Guard. headers-verify.js gains --require-stamped-version (passed when a version-type is set) that hard-fails the compose verification if any composed ReactNativeVersion.h still contains the sentinel — turning any future regression into a red prebuild instead of silently broken community libraries.

Changelog:

[IOS] [FIXED] - Ship a version-stamped ReactNativeVersion.h in the prebuilt iOS core artifacts instead of the 1000.0.0 dev sentinel

Test Plan

  • Local compose with a stamped ReactNativeVersion.h seeded into .build/headers and the source tree left at the 1000 sentinel: all composed ReactNativeVersion.h copies come out stamped (overlay won), while a deliberately stale .build/headers/…/TraceRecordingState.h is correctly ignored — the composed TraceRecordingState.h/HostTracingProfile.h come from source, and other headers + module maps/umbrellas are unaffected (structural gate passes).
  • Guard: composed layout with a sentinel header → verifyVersionStamp throws listing the offending files; stamped → version stamp OK (N copies checked); no effect without the flag.
  • Verified against a fresh RN-nightly app + react-native-unistyles@3.3.0 on Xcode 26.3 (prebuilt mode) and in the Expo prebuilt harness (26.3 + 26.6): stock prebuilt headers reproduce the shadowNodeFromValue error; the stamped prebuilt header resolves it.
  • End-to-end proof is the next nightly run: the composed tarball must carry a stamped header.

🤖 Generated with Claude Code

@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 Jul 19, 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 Jul 19, 2026
@chrfalch
chrfalch requested a review from cipolleschi July 19, 2026 20:47

@cipolleschi cipolleschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this workaround, as we set the version and then we revert it.
It would be better if we can create the headers folder in one of the slice jobs and restore it here, probably.

chrfalch and others added 2 commits July 20, 2026 12:31
…ders

The prebuilt iOS core artifacts shipped ReactNativeVersion.h with the
1000.0.0 dev sentinel: the compose-xcframework job re-derives the shipped
headers from its own fresh (unstamped) checkout, while only build-rn-slice
runs set-rn-artifacts-version.js. Since the header-facades change,
libraries resolve the prebuilt header instead of the stamped npm copy, so
any library gating on REACT_NATIVE_VERSION_MAJOR/MINOR compiles the wrong
branch (react-native-unistyles in nightly-tests).

Rather than re-stamp (and then revert) the compose checkout, source just
that one build-stamped header's CONTENT from the built header tree the
compose job already downloads (.build/headers): stageEntries now takes an
overlay dir and, for ReactNativeVersion.h only, prefers the built copy
(stamped in the slice job) over the source sentinel. Header LAYOUT is
still spec-derived from source, and every other header still copies from
source, so a stale build tree can never ship wrong header content. Bump
the compose cache key so pre-fix unstamped composed artifacts are not
served.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on sentinel

Enforce the stamping above: headers-verify.js gains a
--require-stamped-version gate that fails the compose verification when
any ReactNativeVersion.h in the composed artifacts still contains the
1000.0.0 dev sentinel, and the workflow passes the flag whenever a
version-type is set. Turns any future regression of the stamping into a
red prebuild instead of silently broken community libraries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chrfalch
chrfalch force-pushed the chrfalch/prebuild-stamp-compose branch from 7af1793 to a1830a7 Compare July 20, 2026 10:32
@chrfalch

Copy link
Copy Markdown
Collaborator Author

@cipolleschi good call — dropped the set-then-revert. Reworked it to do what you were pointing at: the compose job now consumes the stamped ReactNativeVersion.h from the .build/headers the slice job already produces and uploads (it was downloaded but unused). stageEntries takes an overlay dir and prefers the built copy for that one file only.

I scoped it strictly to ReactNativeVersion.h on purpose: it's the only header that's generated/stamped at build time, so it's the only one whose content legitimately differs between the source tree and the built tree. Everything else still copies from source (layout stays spec-derived), so a stale/partial .build/headers can never leak wrong header content — a local repro with a deliberately stale built tree confirmed only the version header is taken from the overlay and the rest come from source. No more stamping the compose checkout, no git revert.

@chrfalch
chrfalch requested a review from cipolleschi July 20, 2026 10:49

@cipolleschi cipolleschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for acting on the feedback

@meta-codesync

meta-codesync Bot commented Jul 21, 2026

Copy link
Copy Markdown

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

@meta-codesync meta-codesync Bot closed this in 95df500 Jul 21, 2026
@meta-codesync meta-codesync Bot added the Merged This PR has been merged. label Jul 21, 2026
@meta-codesync

meta-codesync Bot commented Jul 21, 2026

Copy link
Copy Markdown

@cipolleschi merged this pull request in 95df500.

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. Merged This PR has been merged. 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.

2 participants