Skip to content

Commit 9df65a4

Browse files
chrfalchclaude
andcommitted
Honor a library's spm.name when scaffolding its Package.swift
The scaffolder derived every target name with `toSwiftName(dep.name)`, ignoring the `spm.name` override in the library's own react-native.config.js. A library setting that override got a manifest whose product name differed from the name the autolinker registers it under, and SPM failed resolution on `.product(name: "X", package: "X")` — the exact mismatch the override exists to prevent. Use the name the autolinker already resolved (`dep.swiftName`), falling back to `toSwiftName` when a caller runs the translation without it. Sibling references resolve the same way, through the new `SpmScaffoldSpec.siblingSwiftNames`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 54696dd commit 9df65a4

3 files changed

Lines changed: 97 additions & 12 deletions

File tree

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,37 @@ describe('translatePodspecToSpmTarget', () => {
140140
expect(spec.coreReactNative).toBe(true);
141141
});
142142

143+
it('uses the resolved swiftName (spm.name override) so the manifest matches what the autolinker registers', () => {
144+
const model = podspec({name: 'react-native-worklets'});
145+
const spec = translatePodspecToSpmTarget(
146+
model,
147+
autolinkedDep({name: 'react-native-worklets', swiftName: 'worklets'}),
148+
);
149+
expect(spec.swiftName).toBe('worklets');
150+
});
151+
152+
it('falls back to toSwiftName when the dep carries no resolved name', () => {
153+
const spec = translatePodspecToSpmTarget(
154+
podspec(),
155+
autolinkedDep({name: 'react-native-foo'}),
156+
);
157+
expect(spec.swiftName).toBe('ReactNativeFoo');
158+
});
159+
160+
it('resolves each sibling through the same overrides, not through toSwiftName', () => {
161+
const model = podspec({dependencies: ['RNWorklets']});
162+
const spec = translatePodspecToSpmTarget(
163+
model,
164+
autolinkedDep({name: 'react-native-reanimated', swiftName: 'reanimated'}),
165+
new Map([['RNWorklets', 'react-native-worklets']]),
166+
new Map([['react-native-worklets', 'worklets']]),
167+
);
168+
expect(spec.siblingNames).toEqual(['react-native-worklets']);
169+
expect(spec.siblingSwiftNames).toEqual({
170+
'react-native-worklets': 'worklets',
171+
});
172+
});
173+
143174
it('does not self-wire when a pod dependency maps back to the dep itself', () => {
144175
const model = podspec({dependencies: ['RNReanimated']});
145176
const spec = translatePodspecToSpmTarget(
@@ -604,6 +635,18 @@ describe('emitScaffoldedPackageSwift', () => {
604635
);
605636
});
606637

638+
it('emits the sibling override name for both the package and the product', () => {
639+
const out = emitScaffoldedPackageSwift(
640+
baseSpec({
641+
siblingNames: ['react-native-worklets'],
642+
siblingSwiftNames: {'react-native-worklets': 'worklets'},
643+
}),
644+
);
645+
expect(out).toContain('.package(name: "worklets", path: "../worklets")');
646+
expect(out).toContain('.product(name: "worklets", package: "worklets")');
647+
expect(out).not.toContain('ReactNativeWorklets');
648+
});
649+
607650
it('-includes the ObjC prefix header in c/cxx settings when needsObjCPrefix is set', () => {
608651
const withPrefix = emitScaffoldedPackageSwift(
609652
baseSpec({needsObjCPrefix: true}),
@@ -745,6 +788,21 @@ end
745788
};
746789
}
747790

791+
it('names the scaffolded package with the spm.name override the autolinker resolved', () => {
792+
makePodspec();
793+
const result = scaffoldPackageSwiftForDep(
794+
makeDep({swiftName: 'foo'}),
795+
makeCtx(),
796+
);
797+
expect(result.status).toBe('written');
798+
const content = fs.readFileSync(
799+
path.join(depRoot, 'Package.swift'),
800+
'utf8',
801+
);
802+
expect(content).toContain('name: "foo"');
803+
expect(content).not.toContain('ReactNativeFoo');
804+
});
805+
748806
it('writes Package.swift into the dep root on the happy path', () => {
749807
makePodspec();
750808
const result = scaffoldPackageSwiftForDep(makeDep(), makeCtx());

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

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,18 @@ function translatePodspecToSpmTarget(
235235
// `s.dependency "RNWorklets"` — a pod-style name the `react-native-*`
236236
// heuristic can't recognize — to the right sibling package. Empty by default.
237237
podToNpm /*: Map<string, string> */ = new Map(),
238+
// npm name → resolved Swift name for every autolinked dep, so a sibling
239+
// reference honors that sibling's `spm.name` instead of re-deriving it.
240+
swiftNameByNpm /*: Map<string, string> */ = new Map(),
238241
) /*: SpmScaffoldSpec */ {
239242
const warnings = [...model.warnings];
240243

241-
// Swift target name: ALWAYS toSwiftName(npm-name). The autolinker
242-
// registers each autolinked dep under that name in its aggregator (and in
243-
// any sibling spm.dependencies refs), so the scaffolded Package.swift's
244-
// product/library name must match — otherwise SPM resolution fails with
245-
// a name mismatch on `.product(name: "X", package: "X")`.
244+
// Swift target name: whatever the autolinker resolved for this dep — its
245+
// `spm.name` override when set, else toSwiftName(npm-name). The autolinker
246+
// registers the dep under that name in its aggregator (and in any sibling
247+
// spm.dependencies refs), so the scaffolded Package.swift's product/library
248+
// name must match it exactly — otherwise SPM resolution fails with a name
249+
// mismatch on `.product(name: "X", package: "X")`.
246250
//
247251
// The podspec's `header_dir` is captured separately: when it changes the
248252
// include surface (e.g. `<reanimated/...>` instead of `<ReactNativeReanimated/...>`),
@@ -251,10 +255,8 @@ function translatePodspecToSpmTarget(
251255
// `<react/renderer/components/safeareacontext/...>` resolve through
252256
// `-I common/cpp/`). Module-style includes that NEED the target name to
253257
// match (e.g. reanimated's `<reanimated/X.h>` via SwiftPM's auto-generated
254-
// module map) require an explicit `spm.name` override in
255-
// react-native.config.js — handled by the existing autolinker flow, not
256-
// here.
257-
const swiftName = toSwiftName(dep.name);
258+
// module map) are what `spm.name` is for.
259+
const swiftName = dep.swiftName ?? toSwiftName(dep.name);
258260

259261
// Header search paths — substitute Xcode build-setting tokens against the
260262
// dep root. Anything we can't substitute is dropped + warned (avoids
@@ -512,6 +514,14 @@ function translatePodspecToSpmTarget(
512514
);
513515
}
514516

517+
const siblingSwiftNames /*: {[npmName: string]: string} */ = {};
518+
for (const npmName of siblingNames) {
519+
const resolved = swiftNameByNpm.get(npmName);
520+
if (resolved != null) {
521+
siblingSwiftNames[npmName] = resolved;
522+
}
523+
}
524+
515525
return {
516526
swiftName,
517527
sources: expandedSources,
@@ -520,6 +530,7 @@ function translatePodspecToSpmTarget(
520530
needsObjCPrefix,
521531
coreReactNative,
522532
siblingNames,
533+
siblingSwiftNames,
523534
extraFrameworks: model.frameworks,
524535
weakFrameworks: model.weakFrameworks,
525536
compilerFlags: model.compilerFlags,
@@ -687,7 +698,8 @@ function emitScaffoldedPackageSwift(
687698
);
688699
}
689700
for (const siblingName of spec.siblingNames) {
690-
const swiftSibling = toSwiftName(siblingName);
701+
const swiftSibling =
702+
spec.siblingSwiftNames?.[siblingName] ?? toSwiftName(siblingName);
691703
// The autolinker references each self-managed (scaffolded) dep through a
692704
// `libs/<SwiftName>` symlink, and SPM resolves a manifest's relative
693705
// package paths against that symlink location — so a sibling lives at
@@ -787,6 +799,9 @@ type ScaffoldContext = {
787799
// podspec-name → npm-name index over all autolinked deps, so pod-style
788800
// `s.dependency` names (e.g. "RNWorklets") wire to the right sibling.
789801
podToNpm?: Map<string, string>,
802+
// npm-name → resolved Swift name over all autolinked deps, so sibling
803+
// references honor each sibling's `spm.name`.
804+
swiftNameByNpm?: Map<string, string>,
790805
};
791806
*/
792807

@@ -945,6 +960,7 @@ function scaffoldPackageSwiftForDep(
945960
model,
946961
dep,
947962
ctx.podToNpm ?? new Map(),
963+
ctx.swiftNameByNpm ?? new Map(),
948964
);
949965

950966
// Mixed-language fail-closed: SPM can't compile Swift + C-family in one
@@ -1167,6 +1183,13 @@ function scaffoldAll(
11671183
}
11681184
}
11691185

1186+
const swiftNameByNpm /*: Map<string, string> */ = new Map();
1187+
for (const dep of allDeps) {
1188+
if (dep.swiftName != null) {
1189+
swiftNameByNpm.set(dep.name, dep.swiftName);
1190+
}
1191+
}
1192+
11701193
const ctx /*: ScaffoldContext */ = {
11711194
appRoot,
11721195
projectRoot,
@@ -1175,6 +1198,7 @@ function scaffoldAll(
11751198
dryRun: opts.dryRun === true,
11761199
cacheSlotLabel: opts.cacheSlotLabel ?? null,
11771200
podToNpm,
1201+
swiftNameByNpm,
11781202
};
11791203
const skipSet /*: Set<string> */ = new Set(opts.skipDeps ?? []);
11801204

packages/react-native/scripts/spm/spm-types.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,10 +482,13 @@ export type SpmScaffoldSpec = {
482482
// Bucketed dependency references — pre-computed by the translation layer.
483483
// `coreReactNative` is true when ANY React-* / RCT* / RCT-Folly / glog
484484
// dep is present (so we add React's invariant header products).
485-
// `siblingNames` are npm names that match other autolinked deps — resolved
486-
// to Swift names by the scaffold orchestrator before emit.
485+
// `siblingNames` are npm names that match other autolinked deps.
486+
// `siblingSwiftNames` carries each one's resolved Swift name (honoring the
487+
// sibling's `spm.name`); a sibling absent from it falls back to
488+
// toSwiftName(npmName) at emit time.
487489
coreReactNative: boolean,
488490
siblingNames: Array<string>,
491+
siblingSwiftNames?: {[npmName: string]: string},
489492
// Extra frameworks beyond the autolinker's default UIKit/Foundation/CoreGraphics
490493
// set. Merged with the defaults at emit time.
491494
extraFrameworks: Array<string>,

0 commit comments

Comments
 (0)