Skip to content

Commit 5177226

Browse files
chrfalchclaude
andcommitted
fix(spm): scaffold app-package paths relative to the libs/<Name> symlink
A scaffolded community-library Package.swift referenced the app's codegen and xcframeworks packages with paths computed relative to the dep's real node_modules dir (relFromDep). But the autolinker references each library through a symlink at build/generated/autolinking/libs/<SwiftName>, and on a fresh SwiftPM resolve the manifest's relative `.package(path:)` entries are interpreted against that SYMLINK location (SwiftPM does not canonicalize the symlink first). So `../../ios/build/generated/ios` resolved to the doubled, nonexistent `…/autolinking/ios/build/generated/ios` → "Could not resolve package dependencies". This bit any fresh resolve (only a stale Package.resolved hid it). Compute the paths relative to the symlink dir instead: codegen → `../../../ios`, xcframeworks → `../../../../xcframeworks`. SCAFFOLDER_VERSION bumped 7→8 so existing manifests auto-regenerate on the next scaffold/sync. Verified E2E on /tmp/SpmE2E and ~/spikes/Spm1 (remote mode): re-scaffold via the v8 tooling, fresh resolve (clean DerivedData + Package.resolved) BUILD SUCCEEDED. Known follow-up: the sibling-dep path (emitScaffoldedPackageSwift `../${siblingName}`) is still dep.root-relative and uses the npm name rather than `libs/<swiftSibling>` — same class, only hit when a scaffolded lib depends on another RN lib (reanimated→worklets). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a924ec4 commit 5177226

2 files changed

Lines changed: 51 additions & 7 deletions

File tree

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,30 @@ end
459459
expect(content).toContain(SCAFFOLDER_MARKER);
460460
});
461461

462+
it('computes app paths relative to the libs/<SwiftName> symlink, not dep.root (fresh-resolve correctness)', () => {
463+
makePodspec();
464+
const result = scaffoldPackageSwiftForDep(makeDep(), makeCtx());
465+
expect(result.status).toBe('written');
466+
const content = fs.readFileSync(
467+
path.join(depRoot, 'Package.swift'),
468+
'utf8',
469+
);
470+
// swiftName = ReactNativeFoo; the autolinker references the dep via
471+
// build/generated/autolinking/libs/ReactNativeFoo. SwiftPM resolves the
472+
// manifest's relative paths against THAT location, so:
473+
// build/generated/ios -> ../../../ios
474+
// build/xcframeworks -> ../../../../xcframeworks
475+
expect(content).toContain(
476+
'.package(name: "React-GeneratedCode", path: "../../../ios")',
477+
);
478+
expect(content).toContain(
479+
'.package(name: "ReactNative", path: "../../../../xcframeworks")',
480+
);
481+
// The old dep.root-relative form (doubled to …/autolinking/ios/build/...
482+
// through the symlink) must NOT be emitted.
483+
expect(content).not.toContain('../../ios/build/generated/ios');
484+
});
485+
462486
it('reports previouslyExisted=false for first-time scaffolds (so the CLI can prompt)', () => {
463487
makePodspec();
464488
const result = scaffoldPackageSwiftForDep(makeDep(), makeCtx());

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ const {log} = makeLogger('scaffold-package-swift');
8080
// the scaffolder regenerates regardless of --force (the bump implies the
8181
// existing file is broken under current tooling). A file with the marker
8282
// but no version line is treated as v1.
83-
const SCAFFOLDER_VERSION = 7;
83+
// v8: relative app paths (codegen / xcframeworks) are now computed from the
84+
// autolinker's libs/<SwiftName> symlink location instead of the real dep.root,
85+
// fixing a doubled-path resolution failure on fresh SwiftPM resolves.
86+
const SCAFFOLDER_VERSION = 8;
8487
const SCAFFOLDER_VERSION_LINE_RE = /^\/\/ AUTO-SCAFFOLDED-VERSION: (\d+)$/m;
8588

8689
const AUTOGEN_MARKER =
@@ -635,18 +638,35 @@ function scaffoldPackageSwiftForDep(
635638
}
636639

637640
const spec = translatePodspecToSpmTarget(model, dep);
638-
// Relative paths from the dep's package dir (where Package.swift lands)
639-
// into the app — posix separators, as SPM expects.
640-
const relFromDep = (...segments /*: Array<string> */) =>
641+
// Relative paths into the app, embedded in the scaffolded Package.swift.
642+
//
643+
// The manifest is written to <dep.root>/Package.swift, but the autolinker
644+
// references the dep through a symlink at
645+
// <appRoot>/build/generated/autolinking/libs/<SwiftName>. On a fresh resolve
646+
// SwiftPM interprets a manifest's relative `.package(path:)` entries against
647+
// that SYMLINK location (it does not canonicalize the symlink first), so the
648+
// paths must be relative to the symlink dir — NOT the real dep.root.
649+
// Computing from dep.root produced a doubled path
650+
// (…/autolinking/ios/build/generated/ios) → opaque "package manifest cannot
651+
// be accessed" resolution failure. posix separators, as SPM expects.
652+
const symlinkDir = path.join(
653+
ctx.appRoot,
654+
'build',
655+
'generated',
656+
'autolinking',
657+
'libs',
658+
spec.swiftName,
659+
);
660+
const relFromManifest = (...segments /*: Array<string> */) =>
641661
path
642-
.relative(dep.root, path.join(ctx.appRoot, ...segments))
662+
.relative(symlinkDir, path.join(ctx.appRoot, ...segments))
643663
.split(path.sep)
644664
.join('/');
645665
const content = emitScaffoldedPackageSwift(spec, {
646666
cacheSlotLabel: ctx.cacheSlotLabel,
647667
remote: remotePackageConfig(ctx.appRoot),
648-
codegenPackageDir: relFromDep('build', 'generated', 'ios'),
649-
localXcfwPackageDir: relFromDep('build', 'xcframeworks'),
668+
codegenPackageDir: relFromManifest('build', 'generated', 'ios'),
669+
localXcfwPackageDir: relFromManifest('build', 'xcframeworks'),
650670
});
651671

652672
// Distinguish "first-time scaffold" (no file at all) from "regenerate"

0 commit comments

Comments
 (0)