Skip to content

Commit 62cabbe

Browse files
committed
fix(ios-prebuild): do not relocate SocketRocket headers into ReactNativeHeaders
Declaring SocketRocket in DEPS_NAMESPACES (added to satisfy the deps set-equality guard) relocated textual copies of its headers into ReactNativeHeaders and thus into the flattened React-Core-prebuilt/Headers, which sits on every pod's HEADER_SEARCH_PATHS. The real SocketRocket pod still exists in consumer graphs and vends the same headers, so the copies collide: duplicate @interface definitions when the SocketRocket pod compiles (rn-tester, prebuilt + USE_FRAMEWORKS=dynamic), and a poisoned module graph in use_frameworks/explicit-modules apps that don't compile SocketRocket sources (Expo: ReactCodegen/RCTModulesConformingToProtocolsProvider.h "file not found"). Bisect confirmed removing the SocketRocket dir flips red->green and the R11 shims are not implicated. Add DEPS_NAMESPACES_NOT_RELOCATED: such namespaces count as declared for the set-equality guard (a genuinely new dep still fails closed), the include classifier still recognizes them, and the headers gate now asserts they stay ABSENT from the artifact. Note: no CI lane covers prebuilt + use_frameworks (the dynamic-frameworks lane builds from source), which is why CI stayed green.
1 parent 40be14d commit 62cabbe

5 files changed

Lines changed: 45 additions & 11 deletions

File tree

packages/react-native/scripts/ios-prebuild/__docs__/headers-rules.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,9 @@ These classes previously failed late (consumer CI lane) or not at all; the gate
356356
Proof the gate earns its keep — its FIRST run found two real shipping defects:
357357
the dual-identity redefinitions that became R11, and an undeclared
358358
`SocketRocket` namespace in the deps artifact (caught by the set-equality guard
359-
the moment it was added).
359+
the moment it was added). SocketRocket is deliberately NOT relocated
360+
(`DEPS_NAMESPACES_NOT_RELOCATED`): the real pod vends it, and textual copies
361+
collide with it under `use_frameworks` — the gate asserts its absence.
360362

361363
### D. Remaining silent gaps — allowlist maintenance (by design)
362364

packages/react-native/scripts/ios-prebuild/headers-compose.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
const {computeInventory} = require('./headers-inventory');
2121
const {
2222
DEPS_NAMESPACES,
23+
DEPS_NAMESPACES_NOT_RELOCATED,
2324
planFromInventory,
2425
renderNamespaceModuleMap,
2526
renderReactModuleMap,
@@ -189,22 +190,24 @@ function buildReactNativeHeadersXcframework(
189190
execSync(`/bin/cp -Rc "${src}" "${path.join(stage, ns)}"`);
190191
}
191192
// Set equality with the deps artifact: a namespace dir present in the
192-
// artifact but NOT declared in DEPS_NAMESPACES means a new third-party dep
193-
// was added upstream and would silently not be relocated — consumers'
194-
// `<newdep/...>` includes would break. Declare it once in headers-spec.js
195-
// (the include classifier derives from the same list).
193+
// artifact but neither declared for relocation (DEPS_NAMESPACES) nor
194+
// explicitly excluded (DEPS_NAMESPACES_NOT_RELOCATED — namespaces a real
195+
// consumer pod vends itself, e.g. SocketRocket) means a new third-party dep
196+
// was added upstream — fail closed so a decision is made deliberately.
196197
const foundDepsDirs = fs
197198
.readdirSync(depsHeaders, {withFileTypes: true})
198199
.filter(e => e.isDirectory())
199200
.map(e => String(e.name));
200201
const undeclared = foundDepsDirs.filter(
201-
d => !plan.depsNamespaces.includes(d),
202+
d =>
203+
!plan.depsNamespaces.includes(d) &&
204+
!DEPS_NAMESPACES_NOT_RELOCATED.includes(d),
202205
);
203206
if (undeclared.length > 0) {
204207
throw new Error(
205208
`headers-compose: deps artifact ships undeclared namespace(s): ` +
206-
`${undeclared.join(', ')}. Add them to DEPS_NAMESPACES in ` +
207-
`headers-spec.js so they are relocated into ReactNativeHeaders.`,
209+
`${undeclared.join(', ')}. Add them to DEPS_NAMESPACES (relocated) or ` +
210+
`DEPS_NAMESPACES_NOT_RELOCATED (vended by a real pod) in headers-spec.js.`,
208211
);
209212
}
210213
// Hermes public headers (separate source from the deps namespaces — they

packages/react-native/scripts/ios-prebuild/headers-inventory.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131

3232
const {getHeaderFilesFromPodspecs} = require('./headers');
3333
// headers-spec.js requires only fs/path, so this cannot cycle.
34-
const {DEPS_NAMESPACES} = require('./headers-spec');
34+
const {
35+
DEPS_NAMESPACES,
36+
DEPS_NAMESPACES_NOT_RELOCATED,
37+
} = require('./headers-spec');
3538
const fs = require('fs');
3639
const path = require('path');
3740

@@ -77,7 +80,10 @@ type HeaderEntry = {
7780
// of truth: the spec's DEPS_NAMESPACES (the namespaces relocated into
7881
// ReactNativeHeaders) — a new third-party dep is declared ONCE and both the
7982
// include classifier and the compose step follow.
80-
const THIRD_PARTY_LIBS /*: Set<string> */ = new Set(DEPS_NAMESPACES);
83+
const THIRD_PARTY_LIBS /*: Set<string> */ = new Set([
84+
...DEPS_NAMESPACES,
85+
...DEPS_NAMESPACES_NOT_RELOCATED,
86+
]);
8187

8288
// Apple SDK / platform include roots (first path segment). Includes resolving
8389
// here are "system": always modular or always available, never our problem.

packages/react-native/scripts/ios-prebuild/headers-spec.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,20 @@ const DEPS_NAMESPACES = [
120120
'fmt',
121121
'double-conversion',
122122
'fast_float',
123-
'SocketRocket',
124123
];
125124

125+
// Namespaces the deps artifact ships but which must NOT be relocated into
126+
// ReactNativeHeaders: a REAL CocoaPods pod of the same name exists in consumer
127+
// graphs and vends these headers itself (with its own module). Relocating
128+
// textual copies puts them on every pod's HEADER_SEARCH_PATHS via the
129+
// flattened React-Core-prebuilt/Headers and collides with the pod's own
130+
// headers — duplicate @interface definitions compiling the SocketRocket pod,
131+
// and a poisoned module graph in use_frameworks/explicit-modules apps
132+
// (Expo regression, 2026-07-03). The compose set-equality guard treats these
133+
// as declared (so only genuinely NEW namespaces fail closed), and the headers
134+
// gate asserts they stay ABSENT from the artifact.
135+
const DEPS_NAMESPACES_NOT_RELOCATED = ['SocketRocket'];
136+
126137
// R4/R5 umbrella exclusion: C extern-inline definitions.
127138
const EXTERN_INLINE_RE /*: RegExp */ =
128139
/\b(RCT_EXTERN\s+inline|extern\s+inline)\b/;
@@ -458,6 +469,7 @@ function renderNamespaceModuleMap(
458469

459470
module.exports = {
460471
planFromInventory,
472+
DEPS_NAMESPACES_NOT_RELOCATED,
461473
renderReactModuleMap,
462474
renderUmbrellaHeader,
463475
renderNamespaceModuleMap,

packages/react-native/scripts/ios-prebuild/headers-verify.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
const {computeInventory} = require('./headers-inventory');
4141
const {
42+
DEPS_NAMESPACES_NOT_RELOCATED,
4243
planFromInventory,
4344
renderNamespaceModuleMap,
4445
renderReactModuleMap,
@@ -231,6 +232,16 @@ function verifyStructural(
231232
throw new Error(`deps namespace missing from artifact: ${ns}`);
232233
}
233234
}
235+
// Excluded namespaces must stay ABSENT: relocating them collides with the
236+
// real pod's own headers (SocketRocket / Expo use_frameworks regression).
237+
for (const ns of DEPS_NAMESPACES_NOT_RELOCATED) {
238+
if (fs.existsSync(path.join(rnhHeaders, ns))) {
239+
throw new Error(
240+
`excluded deps namespace '${ns}' found in the artifact — it must NOT ` +
241+
`be relocated (a real pod vends it; textual copies collide).`,
242+
);
243+
}
244+
}
234245
log('structural: OK (module maps + umbrellas byte-match the spec render).');
235246
return {reactSlice, rnhHeaders};
236247
}

0 commit comments

Comments
 (0)