Skip to content

feat(iOS): ReactNativeDependenciesHeaders sidecar + pure-RN ReactNativeHeaders, published to Maven#57442

Open
chrfalch wants to merge 5 commits into
chrfalch/prebuilt-deps-facadesfrom
chrfalch/deps-headers-sidecar
Open

feat(iOS): ReactNativeDependenciesHeaders sidecar + pure-RN ReactNativeHeaders, published to Maven#57442
chrfalch wants to merge 5 commits into
chrfalch/prebuilt-deps-facadesfrom
chrfalch/deps-headers-sidecar

Conversation

@chrfalch

@chrfalch chrfalch commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Step 2 of the prebuilt-deps roadmap: ship the deps headers as a SwiftPM-ready, self-contained artifact and make every header namespace have exactly one physical home.

  1. New artifact: ReactNativeDependenciesHeaders.xcframework — the binary ReactNativeDependencies.xcframework is framework-type, so its root Headers/ is invisible to SwiftPM binaryTargets (HeadersPath is rejected on framework entries; verified empirically). The deps prebuild now emits a headers-only library-type sidecar (stub archives + per-slice Headers/ + HeadersPath — the exact ReactNativeHeaders recipe, factored into a shared headers-xcframework.js emitter) carrying all seven deps namespaces incl. SocketRocket, with slice parity derived from the binary artifact's Info.plist. Ships inside the deps tarball and standalone.

  2. ReactNativeHeaders goes pure-RN — the R2 relocation of deps namespaces (and the DEPS_NAMESPACES_NOT_RELOCATED SocketRocket exclusion list) is deleted. Relocated copies are what enabled the SocketRocket dual-copy regression (duplicate @interface / poisoned module graph under use_frameworks!); that bug class is now structurally impossible. Headers gate flipped: deps namespaces must be absent from RNH; the sidecar emitter enforces set-equality with DEPS_NAMESPACES fail-closed in both directions. On the CocoaPods side, a new ReactNativeDependenciesUtils.configure_aggregate_xcconfig injects the deps pod's Headers/ globally (aggregate + every pod target), mirroring the rncore injection — this replaces the folly/glog resolution pods previously got via the flattened React-Core-prebuilt/Headers.

  3. CI: prebuilt + dynamic-frameworks lane — the regression's exact config had no coverage (the test-ios-rntester action hard-coupled use-frameworks:true to source builds). New use-prebuilds input; test_ios_rntester's dynamic cells now consume the workflow-built prebuilt artifacts.

  4. Maven publishingReactNativeHeaders and ReactNativeDependenciesHeaders publish standalone on react-native-artifacts (classifiers reactnative-headers-*, reactnative-dependencies-headers-*); verifyArtifactsAreOnMaven now HEAD-checks every classifier tarball instead of only the POM.

Stacked on #57440. The SwiftPM preview (#57332) rebases on top and wires the sidecar as its 5th binaryTarget.

Changelog:

[IOS] [CHANGED] - Prebuilt artifacts: ReactNativeHeaders is pure-RN; third-party deps headers ship in the new ReactNativeDependenciesHeaders.xcframework sidecar (and the ReactNativeDependencies pod), published standalone to Maven

Test Plan

  • Headers gate: include-health, structural (deps absent from RNH, byte-matched module maps), and compile smokes (React module + 14 namespace modules + Expo-shape ObjC++/Swift fixtures vs the deps include path) — ALL PASSED
  • jest: 33/33 (scripts/ios-prebuild/__tests__, incl. new sidecar set-equality tests)
  • ESLint (--max-warnings 0), Prettier, Flow (yarn flow-check): clean
  • E2E (locally built artifacts): rn-tester prebuilt static ✅, prebuilt USE_FRAMEWORKS=dynamic ✅ (the regression config — verified React-Core-prebuilt/Headers contains no deps namespaces and the deps pod serves all seven), helloworld static ✅, source-core + prebuilt-deps ✅ (React compiled from source resolves folly via the deps pod), source-mode control with unchanged dependency graph ✅
  • Sidecar inspected: per-slice HeadersPath, 7 namespaces, slice parity with the binary
  • Publication validated end-to-end with publishReleasePublicationToMavenLocal: all 12 files + POM land with the expected classifier names

🤖 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 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Caution

Invalid Base Branch

The base branch for this PR is chrfalch/prebuilt-deps-facades, which is not main or a -stable branch.
Are you sure you want to target this branch?

@chrfalch chrfalch force-pushed the chrfalch/deps-headers-sidecar branch from 73226c2 to c01f55a Compare July 6, 2026 12:10
chrfalch and others added 5 commits July 6, 2026 18:36
…idecar

The binary ReactNativeDependencies.xcframework is FRAMEWORK-type: it
cannot carry a HeadersPath key, so its root Headers/ dir is invisible to
SwiftPM binaryTargets (verified empirically 2026-07-04 - xcodebuild
rejects -headers without -library, and SwiftPM hard-rejects HeadersPath
on framework entries). This adds a headers-only LIBRARY-type sidecar,
ReactNativeDependenciesHeaders.xcframework, that SwiftPM auto-serves
with zero flags: stub static archives paired with per-slice Headers/
holding the seven third-party namespaces (folly, glog, boost, fmt,
double-conversion, fast_float, SocketRocket).

- headers-xcframework.js (new): dependency-light shared emitter -
  composeHeadersOnlyXcframework (the stub-archive recipe factored out of
  buildReactNativeHeadersXcframework), stubSlicesFromXcframework (slice
  parity derived from the binary artifact's Info.plist, so the sidecar
  resolves for every platform the binary does), and
  buildDepsHeadersXcframework with a set-equality gate that fails closed
  in both directions (declared-but-missing and undeclared namespaces).
- compose-framework.js: the deps compose emits and signs the sidecar
  right after assembling the root Headers/ (slice-uniform content).
- prebuild-ios-dependencies.yml: the sidecar ships INSIDE the deps
  tarball (self-contained for CocoaPods) and as a standalone tarball;
  cache key bumped and now also hashes compose-framework.js.
- reactNativeDependencies.js: consumers extract the sidecar alongside
  the binary; pre-sidecar tarballs (pinned RN_DEP_VERSION) warn and
  continue - CocoaPods is unaffected, SwiftPM recomposes it locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every namespace now has exactly ONE physical home: RN namespaces in
ReactNativeHeaders, third-party deps namespaces in the
ReactNativeDependenciesHeaders sidecar / the ReactNativeDependencies
pod. Relocated deps copies are what made the 2026-07-03 SocketRocket
regression possible (duplicate @interface + poisoned module graph under
use_frameworks when a real pod coexisted with relocated copies) - that
bug class is now structurally impossible, and the SocketRocket
exclusion list is deleted rather than maintained.

- headers-spec.js: R2 rewritten; DEPS_NAMESPACES now includes
  SocketRocket (= the sidecar contents = the artifact's Headers/ dirs,
  set-equality enforced); DEPS_NAMESPACES_NOT_RELOCATED deleted.
- headers-compose.js: RNH stages RN headers only; ensureHeadersLayout
  also builds the sidecar from the cache slot's deps headers and
  returns it, so SwiftPM consumers need no published sidecar.
- headers-verify.js: structural gate flipped - deps namespaces must be
  ABSENT from RNH; compile gates take the deps artifact's Headers on
  the include path (exactly how consumers resolve them).
- CocoaPods: NEW ReactNativeDependenciesUtils.configure_aggregate_xcconfig
  injects $(PODS_ROOT)/ReactNativeDependencies/Headers into the
  aggregate and every pod target in prebuilt-deps mode, mirroring the
  rncore injection. Pods previously resolved folly/glog through the
  globally-injected React-Core-prebuilt/Headers flatten of RNH; after
  the flip the deps pod is the single global home, so the mirror
  injection is what keeps arbitrary pods compiling.

Verified: headers gate green (include-health, structural, compile
smokes vs the deps include path); rn-tester prebuilt static + dynamic
(the regression config), helloworld static, source-core+prebuilt-deps
(React compiled from source resolves folly via the deps pod), and a
source-mode control with an unchanged dependency graph.

BREAKING (prebuilt artifacts only): ReactNativeHeaders.xcframework no
longer contains folly/glog/boost/fmt/double-conversion/fast_float;
consumers reaching them through React-Core-prebuilt/Headers must use
the ReactNativeDependencies pod / ReactNativeDependenciesHeaders
sidecar paths instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prebuilt + use_frameworks is the exact configuration of the 2026-07-03
SocketRocket dual-copy regression, and no CI lane covered it: the
test-ios-rntester action hard-coupled use-frameworks:true to building
from source. Add a use-prebuilds input ('auto' keeps the historical
coupling for existing callers) and pass use-prebuilds:true from
test_ios_rntester, so its dynamic-frameworks matrix cells now consume
the prebuilt ReactCore + ReactNativeDependencies artifacts built in the
same workflow run. Source-built dynamic frameworks stay covered by
test_ios_rntester_dynamic_frameworks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eaders to Maven

Publish the two headers-only xcframeworks as standalone artifacts on
the react-native-artifacts coordinate so SwiftPM consumers can wire
them as separate binaryTargets (they also keep shipping inside the
ReactCore / ReactNativeDependencies tarballs for CocoaPods):

- external-artifacts/build.gradle.kts: four new PublishArtifacts with
  classifiers reactnative-headers-{debug,release} and
  reactnative-dependencies-headers-{debug,release}.
- prebuild-ios-core.yml: upload the standalone
  ReactNativeHeaders<flavor>.xcframework.tar.gz the compose step
  already produces (previously only bundled inside the ReactCore
  tarball); include it in the workflow cache.
- build-npm-package action: download ReactNativeHeaders* artifacts
  (ReactNativeDependenciesHeaders* already rides the existing
  ReactNativeDependencies* pattern).
- verifyArtifactsAreOnMaven.js: HEAD-check every classifier tarball,
  not just the POM - a release with a missing classifier artifact now
  fails verification instead of passing silently.

Validated locally end-to-end with publishReleasePublicationToMavenLocal:
all 12 files + POM land on com.facebook.react:react-native-artifacts
with the expected classifier names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The verifier now HEAD-checks the POM plus all eight classifier tarballs
instead of GETting only the POM; the mocks/assertions follow, and a new
case covers a missing classifier behind an existing POM (the exact gap
the classifier checks were added to catch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-deps-facades branch from 069c350 to 017ca51 Compare July 6, 2026 16:37
@chrfalch chrfalch force-pushed the chrfalch/deps-headers-sidecar branch from 90d8f50 to 0c44552 Compare July 6, 2026 16:37
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant