feat(iOS): serve third-party deps from the prebuilt ReactNativeDependencies pod via dependency-only facades#57440
Conversation
|
Caution Invalid Base Branch The base branch for this PR is |
… 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>
8158f41 to
562985d
Compare
069c350 to
017ca51
Compare
| # 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 |
There was a problem hiding this comment.
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 => [] } } |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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"] || [] |
There was a problem hiding this comment.
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.
Summary
Step 1 of making the prebuilt
ReactNativeDependenciespod 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 @interfaceunderuse_frameworks!— SocketRocket's ObjC headers have no include guards).Three commits:
fix(cocoapods): harden prebuilt-deps header search paths and artifact handling —
rndependencies.rb's||= [] << pathonly added the deps header search path whenHEADER_SEARCH_PATHSwas unset (silently dropped otherwise); normalize and always append, and point at the pod-local flattenedHeaders/.ReactNativeDependencies.podspecprepare_commandnow fails closed (exit 1) instead of silently producing a no-link pod.reactNativeDependencies.jsno longer deletes + re-downloads a locally staged artifact that lacks a version marker.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.RNDepsFacadesgenerates dependency-only local facade podspecs (no sources, no headers, a single dependency onReactNativeDependencies); versions/subspecs are derived from the real podspecs inthird-party-podspecs/(SocketRocket synthesized fail-closed fromsocket_rocket_config). Full contract documented inscripts/cocoapods/__docs__/prebuilt-deps.md.feat(ios-prebuild): SocketRocket privacy manifest + Xcode 26 header layout — embed an RN-authored, accurate-empty
PrivacyInfo.xcprivacyfor SocketRocket (upstream ships none), and stage flat public headers intoinclude/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-onlyReactNativeDependenciesHeaders.xcframeworksidecar so SPM auto-serves the deps namespaces andReactNativeHeadersgoes pure-RN.Changelog:
[IOS][CHANGED] - Prebuilt-deps mode: serve third-party headers from the ReactNativeDependencies pod itself and resolve community
s.dependencyon RCT-Folly/glog/boost/etc. via dependency-only facade podsTest Plan
E2E matrix (2026-07-06, locally built deps artifact via
prepare-ios-prebuilds.js):USE_FRAMEWORKS=dynamic— builds (the SocketRocket-regression config)Podfile.lockidentical to baseline🤖 Generated with Claude Code