Skip to content

Commit a94d74c

Browse files
chrfalchclaude
andcommitted
feat(spm): treat package.json codegenConfig as an implicit React-core dep
New-Architecture libraries (react-native-svg, …) declare their React-core dependency only through the `install_modules_dependencies(s)` podspec helper — which auto-adds React-Core / React-RCTFabric / React-Codegen at install time — rather than an explicit `s.dependency "React-Core"`. We strip that helper when evaluating the podspec, so the React-core dependency never surfaced in model.dependencies and `coreReactNative` stayed false. The scaffolded manifest then declared no dependencies at all, and the lib's Fabric sources failed with: RNSVGShadowNodes.cpp:1:10: fatal error: 'react/renderer/components/rnsvg/ShadowNodes.h' file not found That generated header lives in the per-app React-GeneratedCode package (the `ReactAppHeaders` target vends `react/renderer/components/<name>/…`), which the lib can only reach via an SPM dependency — SwiftPM forbids a headerSearchPath from escaping the package root, so cross-package headers must come through a dependency's publicHeadersPath. Fix: detect a `codegenConfig` block in the dep's package.json — RN's authoritative marker that a library participates in the New Architecture / codegen — and treat it as an implicit React-core dependency. That makes the scaffolder emit the existing `.package(name: "React-GeneratedCode", …)` + `.product(name: "ReactAppHeaders", …)` wiring, so the generated component headers resolve. Verified: react-native-svg now BUILD SUCCEEDED via the scaffolder on a real RN 0.87 app (previously stopped at the ShadowNodes.h codegen header). SCAFFOLDER_VERSION bumped to 17. 401 spm tests green (2 new). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d2c6e2e commit a94d74c

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

packages/react-native/scripts/spm/__tests__/scaffold-package-swift-test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,45 @@ describe('translatePodspecToSpmTarget', () => {
321321
expect(spec.siblingNames).toEqual(['react-native-worklets']);
322322
});
323323

324+
it('treats a package.json codegenConfig as an implicit React-core dep (New-Arch libs strip install_modules_dependencies — svg shape)', () => {
325+
// svg declares its React-core dep only via install_modules_dependencies(s),
326+
// which we strip — so model.dependencies has NO React-Core. The codegenConfig
327+
// marker is what tells us it still needs the React-GeneratedCode package.
328+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegen-dep-'));
329+
try {
330+
fs.writeFileSync(
331+
path.join(root, 'package.json'),
332+
JSON.stringify({name: 'react-native-svg', codegenConfig: {name: 'rnsvg'}}),
333+
);
334+
const model = podspec({dependencies: []}); // nothing explicit
335+
const spec = translatePodspecToSpmTarget(
336+
model,
337+
autolinkedDep({name: 'react-native-svg', root}),
338+
);
339+
expect(spec.coreReactNative).toBe(true);
340+
} finally {
341+
fs.rmSync(root, {recursive: true, force: true});
342+
}
343+
});
344+
345+
it('does NOT force coreReactNative for a non-codegen dep with no React deps', () => {
346+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'no-codegen-dep-'));
347+
try {
348+
fs.writeFileSync(
349+
path.join(root, 'package.json'),
350+
JSON.stringify({name: 'react-native-foo'}), // no codegenConfig
351+
);
352+
const model = podspec({dependencies: []});
353+
const spec = translatePodspecToSpmTarget(
354+
model,
355+
autolinkedDep({name: 'react-native-foo', root}),
356+
);
357+
expect(spec.coreReactNative).toBe(false);
358+
} finally {
359+
fs.rmSync(root, {recursive: true, force: true});
360+
}
361+
});
362+
324363
it('warns + drops unknown non-RN dependencies (MMKV, AFNetworking)', () => {
325364
const model = podspec({dependencies: ['MMKV', 'AFNetworking']});
326365
const spec = translatePodspecToSpmTarget(model, autolinkedDep());

packages/react-native/scripts/spm/scaffold-package-swift.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const {log} = makeLogger('scaffold-package-swift');
9393
// defines from pod_target_xcconfig emitted as `.define(...)`. v13: ObjC(++)
9494
// targets get an ambient-import prefix header (Foundation/UIKit) `-include`d,
9595
// replacing CocoaPods' generated prefix.pch.
96-
const SCAFFOLDER_VERSION = 16;
96+
const SCAFFOLDER_VERSION = 17;
9797
const SCAFFOLDER_VERSION_LINE_RE = /^\/\/ AUTO-SCAFFOLDED-VERSION: (\d+)$/m;
9898

9999
const AUTOGEN_MARKER =
@@ -147,6 +147,24 @@ function isReactCoreDep(name /*: string */) /*: boolean */ {
147147
return REACT_CORE_DEP_PREFIXES.some(p => name.startsWith(p));
148148
}
149149

150+
// True when the dep ships a `codegenConfig` in package.json — RN's standard
151+
// marker that the library participates in the New Architecture / codegen. Such
152+
// a lib's Fabric sources include the app-generated component headers
153+
// (`<react/renderer/components/<name>/ShadowNodes.h>` etc.), which are vended by
154+
// the per-app React-GeneratedCode package — so it implicitly depends on React
155+
// core even when its podspec only wires that via `install_modules_dependencies`
156+
// (which we strip). Safe/quiet: a missing or unparseable package.json → false.
157+
function depHasCodegenConfig(depRoot /*: string */) /*: boolean */ {
158+
try {
159+
const pkg = JSON.parse(
160+
fs.readFileSync(path.join(depRoot, 'package.json'), 'utf8'),
161+
);
162+
return pkg != null && pkg.codegenConfig != null;
163+
} catch {
164+
return false;
165+
}
166+
}
167+
150168
// Every subdirectory (relative to depRoot) under depRoot/base, recursively —
151169
// used to expand a CocoaPods `path/**` recursive header search path into the
152170
// concrete dirs SPM needs (SPM has no recursive search-path syntax). Skips
@@ -325,6 +343,23 @@ function translatePodspecToSpmTarget(
325343
}
326344
}
327345

346+
// New-Architecture libraries declare their React-core dependency via the
347+
// `install_modules_dependencies(s)` podspec helper (which auto-adds
348+
// React-Core / React-RCTFabric / React-Codegen), NOT an explicit
349+
// `s.dependency "React-Core"`. We strip that helper when evaluating the
350+
// podspec, so the React-core dependency never surfaces in model.dependencies
351+
// and `coreReactNative` would stay false. The authoritative, RN-standard
352+
// marker for "this lib participates in the New Architecture / codegen" is a
353+
// `codegenConfig` block in package.json — when present, the app's codegen has
354+
// generated `react/renderer/components/<name>/{ShadowNodes,Props,…}.h` that
355+
// the lib's Fabric sources include via angle brackets. Those generated
356+
// headers live in the per-app React-GeneratedCode package, so the lib must
357+
// depend on it (and, transitively, on React core). Treat codegenConfig as
358+
// an implicit React-core dependency.
359+
if (!coreReactNative && depHasCodegenConfig(dep.root)) {
360+
coreReactNative = true;
361+
}
362+
328363
// SPM's `sources:` field does NOT accept CocoaPods-style globs — it wants
329364
// a list of explicit file paths (relative to the target's `path:`).
330365
// Expand the podspec globs against the actual filesystem so the emitted

0 commit comments

Comments
 (0)