Skip to content

feat(iOS): serve third-party deps from the prebuilt ReactNativeDependencies pod via dependency-only facades#57440

Open
chrfalch wants to merge 4 commits into
chrfalch/prebuilt-resourcesfrom
chrfalch/prebuilt-deps-facades
Open

feat(iOS): serve third-party deps from the prebuilt ReactNativeDependencies pod via dependency-only facades#57440
chrfalch wants to merge 4 commits into
chrfalch/prebuilt-resourcesfrom
chrfalch/prebuilt-deps-facades

Conversation

@chrfalch

@chrfalch chrfalch commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Step 1 of making the prebuilt ReactNativeDependencies pod the single header authority for the third-party C/C++ deps (RCT-Folly, glog, boost, DoubleConversion, fmt, fast_float, SocketRocket) in prebuilt-deps mode.

Today the deps binary replaces the source pods' code, but the pod still s.dependency's the real source pods and borrows their headers via $(PODS_ROOT)/<pod> search paths. That split header authority is the dual-copy bug class behind the 2026-07-03 SocketRocket regression (duplicate @interface under use_frameworks! — SocketRocket's ObjC headers have no include guards).

Three commits:

  1. fix(cocoapods): harden prebuilt-deps header search paths and artifact handlingrndependencies.rb's ||= [] << path only added the deps header search path when HEADER_SEARCH_PATHS was unset (silently dropped otherwise); normalize and always append, and point at the pod-local flattened Headers/. ReactNativeDependencies.podspec prepare_command now fails closed (exit 1) instead of silently producing a no-link pod. reactNativeDependencies.js no longer deletes + re-downloads a locally staged artifact that lacks a version marker.

  2. feat(cocoapods): dependency-only facades for third-party pods in prebuilt-deps mode — in prebuilt-deps mode the real source pods are not declared, so a community podspec's hardcoded s.dependency "RCT-Folly" would resolve from trunk and compile from source next to the prebuilt binary. RNDepsFacades generates dependency-only local facade podspecs (no sources, no headers, a single dependency on ReactNativeDependencies); versions/subspecs are derived from the real podspecs in third-party-podspecs/ (SocketRocket synthesized fail-closed from socket_rocket_config). Full contract documented in scripts/cocoapods/__docs__/prebuilt-deps.md.

  3. feat(ios-prebuild): SocketRocket privacy manifest + Xcode 26 header layout — embed an RN-authored, accurate-empty PrivacyInfo.xcprivacy for SocketRocket (upstream ships none), and stage flat public headers into include/ so Xcode 26's SwiftPM accepts the header layout.

Stacked on #57305 (base: chrfalch/prebuilt-resources); the SwiftPM preview (#57332) rebases on top of this. Follow-up (separate PR): headers-only ReactNativeDependenciesHeaders.xcframework sidecar so SPM auto-serves the deps namespaces and ReactNativeHeaders goes pure-RN.

Changelog:

[IOS][CHANGED] - Prebuilt-deps mode: serve third-party headers from the ReactNativeDependencies pod itself and resolve community s.dependency on RCT-Folly/glog/boost/etc. via dependency-only facade pods

Test Plan

E2E matrix (2026-07-06, locally built deps artifact via prepare-ios-prebuilds.js):

  • rn-tester, prebuilt core + prebuilt deps, static linkage — builds
  • rn-tester, prebuilt core + prebuilt deps, USE_FRAMEWORKS=dynamic — builds (the SocketRocket-regression config)
  • helloworld (private), prebuilt core + prebuilt deps, static — builds
  • source-mode control: no facades generated, Podfile.lock identical to baseline

🤖 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-resources, which is not main or a -stable branch.
Are you sure you want to target this branch?

chrfalch and others added 4 commits July 6, 2026 18:36
… handling

- rndependencies.rb: the previous "||= [] << path" only added the deps
  header search path when HEADER_SEARCH_PATHS was unset — with an existing
  value the path was silently dropped. Normalize string values and always
  append. Also point the search path at the pod-local flattened Headers/
  (single header home) instead of the artifact root.
- ReactNativeDependencies.podspec: prepare_command exited 0 when
  XCFRAMEWORK_PATH/HEADERS_PATH resolved empty, producing a silent
  no-link pod; fail closed with exit 1.
- reactNativeDependencies.js: an artifact without a version marker logged
  "we are going to use it anyway" but then fell through to rmSync +
  re-download, destroying locally staged deps builds; honor the message
  and use it as-is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uilt-deps mode

In prebuilt-deps mode the real source pods (RCT-Folly, glog, boost,
DoubleConversion, fmt, fast_float, SocketRocket) are not declared in the
Podfile, so a community podspec's hardcoded 's.dependency "RCT-Folly"'
would resolve from the CocoaPods trunk and compile from source next to
the prebuilt binary — the dual-copy bug class behind the 2026-07-03
SocketRocket regression.

RNDepsFacades generates dependency-only local facade podspecs
(build/rndeps-facades/<Name>/): no sources, no headers, a single
dependency on ReactNativeDependencies. Versions and subspecs are derived
from the real podspecs in third-party-podspecs/ (RCT-Folly keeps
/Default + /Fabric with default_subspecs = ["Default"]; SocketRocket is
synthesized from socket_rocket_config, fail-closed if absent). Declared
as :path pods so Podfile-local resolution beats trunk and nothing is
fetched. ReactNativeDependencies remains the single header authority.

Docs: scripts/cocoapods/__docs__/prebuilt-deps.md describes the full
prebuilt-deps header/facade contract and the mode-by-mode supplier table.

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

- Embed an RN-authored, accurate-empty PrivacyInfo.xcprivacy for
  SocketRocket in the deps prebuild (upstream ships no privacy manifest;
  the deps artifact historically carried resource bundles only for
  boost/folly/glog). SocketRocket uses no Required Reason APIs.
- Xcode 26's SwiftPM rejects a public-headers directory where the
  umbrella header has sibling directories ("target 'SocketRocket' has
  invalid header layout"): stage the flat public headers into include/
  via prepareScript and point publicHeaderFiles there. Xcode 16 accepts
  both layouts; the artifact's Headers/SocketRocket namespace is
  unaffected.
- Rename the dependency 'socket-rocket' -> 'SocketRocket' so the bundle
  and header-namespace naming line up
  (ReactNativeDependencies_SocketRocket.bundle).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure formatting — no content change. Fixes the format-check lint lane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-resources branch from 8158f41 to 562985d Compare July 6, 2026 16:37
@chrfalch chrfalch force-pushed the chrfalch/prebuilt-deps-facades branch from 069c350 to 017ca51 Compare July 6, 2026 16:37
# Dependency-only facade podspecs for the third-party deps in prebuilt-deps
# mode (deps-side analogue of RNCoreFacades). Design + rationale:
# scripts/cocoapods/__docs__/prebuilt-deps.md
module RNDepsFacades

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.

Major (test coverage): This entire new module ships without tests, and several branchy behaviors are unit-testable without a full pod install: derive_subspecs filtering, the two synthesized_version fail-closed raise paths, derived_spec version/subspec derivation, and load_real_spec's missing-vs-unparseable errors. These error contracts are load-bearing (a silently-empty facade would hide drift) and deserve a few RSpec/minitest cases.

subspecs = derive_subspecs(real)
unless subspecs.empty?
spec["subspecs"] = subspecs.map do |ss|
{ "name" => ss, "dependencies" => { DEPS_POD => [] } }

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.

Minor: Every derived subspec is emitted depending only on ReactNativeDependencies, so the facade RCT-Folly/Fabric loses the real subspec's dependency on RCT-Folly/Default. Low impact in practice (all six deps are declared explicitly in react_native_pods.rb), but the facade graph isn't truly 'graph-equivalent to the source pod' as the header comment claims — either preserve intra-pod subspec deps or soften the comment.

// Honor the message above: an artifact without a version marker is a
// locally-staged one (e.g. a freshly composed deps build) — falling
// through here rmSync'd it and re-downloaded. Use it as-is.
return true;

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.

Minor: This early return true runs before the version.txt write below, so a locally-staged artifact never gets a version marker. Every later run re-hits this branch and returns true, meaning a subsequent version change won't invalidate the staged artifact until it's manually removed. Matches the stated intent, but worth a one-line comment since downstream code assuming version.txt exists would be surprised.

def self.synthesized_version(name)
case name
when "SocketRocket"
unless defined?(Helpers::Constants) && Helpers::Constants.respond_to?(:socket_rocket_config)

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.

Minor: This references Helpers::Constants but the file only requires json and fileutils; it works solely because react_native_pods.rb loads helpers.rb first. The defined? guard keeps it fail-closed, but a require_relative './helpers.rb' would make the module self-contained against require reordering.

In prebuilt-deps mode the `ReactNativeDependencies` POD (CocoaPods) is the
single authority for the third-party deps: compiled code lives in its
xcframework binary, and the artifact's own
`Headers/{folly,glog,boost,fmt,double-conversion,fast_float, SocketRocket}` are

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.

Nit: Stray space before SocketRocket in the Headers/{...} list.

end
# Artifact headers are flattened into the pod-local Headers/ by the podspec
# prepare_command (see __docs__/prebuilt-deps.md).
header_search_paths << "$(PODS_ROOT)/ReactNativeDependencies/Headers"

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.

Nit: This appends the search path unconditionally; if add_rn_third_party_dependencies ran twice on the same spec the entry would duplicate. Low risk (one call per spec) and now symmetric with the from-source branch — just noting.

if [ -z "$XCFRAMEWORK_PATH" ]; then
echo "ERROR: XCFRAMEWORK_PATH is empty."
exit 0
exit 1

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.

Good catch — this is a real bug fix. The old prepare_command printed 'ERROR' then exited 0, silently producing a broken install with no flattened headers; failing loudly is correct.

s.dependency "ReactNativeDependencies"
current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] ||= [] << "$(PODS_ROOT)/ReactNativeDependencies"

header_search_paths = current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] || []

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.

Nice — this mirrors the from-source branch's String-vs-Array handling and fixes the prior ||= [] << ... idiom that never appended when the key already existed.

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.

2 participants