Skip to content

[iOS][swiftpm] Resolve SwiftPM manifest naming collisions - #58044

Closed
chrfalch wants to merge 1 commit into
mainfrom
spm/reserved-name-guard
Closed

[iOS][swiftpm] Resolve SwiftPM manifest naming collisions#58044
chrfalch wants to merge 1 commit into
mainfrom
spm/reserved-name-guard

Conversation

@chrfalch

@chrfalch chrfalch commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

A dependency's Swift name is derived from its npm package name with the scope dropped, which makes two collisions unavoidable: @powersync/react-native derives ReactNative, one of React Native's own names, and @a/foo and @b/foo both derive Foo.

Either was emitted into the package graph as-is, and SwiftPM then failed deep inside dependency resolution with a duplicate-name error that named neither the library nor the react-native.config.js that caused it.

This was seen in the PR here powersync-ja/powersync-js#1076 - and was hard to fix.

This PR fixes this by adding support for prefixing the name with the scope if the name without scope crashes.

In addition the same logic is added when two packages have names that collide with each other.

If the name given by the SwiftPM scaffolder doesn't work for you - you can use the spm.name field in the react-native.config.js file to set a specific name (see SwiftPM docs).

Changelog:

[IOS] [FIXED] - Resolve Swift manifest naming collisions

Test Plan:

✅ Unit tests green

@chrfalch
chrfalch requested a review from cipolleschi August 21, 2026 09:31
@chrfalch chrfalch changed the title [iOS][swiftpm] Give a library whose Swift name collides its npm scope back [iOS][swiftpm] Resolve SwiftPM manifest naming collisions Aug 21, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 21, 2026
@chrfalch
chrfalch force-pushed the spm/name-constants branch from 380b803 to c2fb471 Compare August 21, 2026 09:40
@chrfalch
chrfalch force-pushed the spm/reserved-name-guard branch from 232df94 to c9aa6ae Compare August 21, 2026 09:42
@chrfalch
chrfalch force-pushed the spm/reserved-name-guard branch from c9aa6ae to b8cc821 Compare August 24, 2026 09:08
@chrfalch
chrfalch changed the base branch from spm/name-constants to main August 24, 2026 09:08
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Aug 24, 2026
@meta-codesync

meta-codesync Bot commented Aug 24, 2026

Copy link
Copy Markdown

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D117178818.

@chrfalch
chrfalch force-pushed the spm/reserved-name-guard branch from b8cc821 to 881411a Compare August 24, 2026 13:45
A dependency's Swift name is derived from its npm package name with the scope
dropped, which makes two collisions unavoidable: `@powersync/react-native`
derives `ReactNative`, one of React Native's own names, and `@a/foo` and
`@b/foo` both derive `Foo`. Either was emitted into the package graph as-is, and
SwiftPM then failed deep inside dependency resolution with a duplicate-name
error that named neither the library nor the react-native.config.js that caused
it.

The scope that was dropped is the fix. A scoped dep whose derived name is
reserved gets the TitleCased scope prepended (`PowersyncReactNative`), and deps
that resolved to the same name get it prepended too (`AFoo`, `BFoo`), so the
library author has nothing to do. Every disambiguation logs one line naming the
package, the name it would have taken and the name it got. Nothing can regress
on this — both collisions fail SwiftPM resolution today, so no working library
carries such a name and no consumer imports headers under it.

The two cases need different shapes. A reserved name is decidable per dep, so it
resolves in `resolveSwiftName`. A collision with another dep is not visible from
there, so it is a pass over the resolved set: group by name, and prepend the
scope to every scoped member of a group larger than one. Every member moves
rather than one arbitrary winner staying put, since there is no non-arbitrary
winner. Two members never move: a name the author set with `spm.name` (their
choice wins, and the others move around it) and an unscoped one (no scope to
borrow).

The pass runs once, and then the whole set is validated — this is the part that
has to be right. A borrowed scope can land on a name another dep already holds
(`@a/foo` → `AFoo`, next to a package `a-foo`) or on a reserved one, and two
libraries silently sharing a name is worse than the error this replaces. So both
existing checks now run over the final set, and anything a scope could not
resolve still fails with the message it did before: two unscoped deps deriving
the same name, an explicit `spm.name` that is reserved, a group whose only
scoped member's new name is taken. Retrying instead of failing would trade a
diagnosable error for a name nobody can predict.

Reserving the names React Native puts in a manifest is what makes the first case
diagnosable at all. Matching is case-insensitive throughout: a name that differs
from another only in case is not distinct enough for the build to keep the two
apart. The reserved check runs before the dep-vs-dep one, so the more specific
diagnosis wins, and it runs for a library that ships an autolinking plugin too:
`spm scaffold` knows nothing about plugins, so an exemption there would leave
the two commands disagreeing about the same library.

`SpmNameCollisionError` distinguishes a misconfiguration from a resolution
failure, so `scaffoldAll` — which degrades to the direct deps when a transitive
dep can't be found — still surfaces it instead of scaffolding manifests SPM
will reject. Its remote package config moves out of the same try for the same
reason.

Every Swift name reaching a manifest now comes from that one resolved set: the
autolinker's two `toSwiftName` fallbacks would have re-derived the
pre-disambiguation name and emitted a reference nothing matches, so they are
replaced by a required lookup that fails loudly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chrfalch
chrfalch force-pushed the spm/reserved-name-guard branch from 881411a to 3f937fb Compare August 25, 2026 09:59

@cortinico cortinico left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

@meta-codesync meta-codesync Bot closed this in 962aeec Aug 25, 2026
meta-codesync Bot pushed a commit that referenced this pull request Aug 25, 2026
)

Summary:
An app declares extra native modules through spm.modules in its react-native.config.js. Those names went into the generated package graph unvalidated, and two of the ways they can go wrong fail silently.

This PR fixes this by using the same Swift name collision detection/resolving as we introduced in #58044

> **NOTE:** #58044 must be merged before this one so that we can change the base branch for this one to `main`

## Changelog:
[IOS] [FIXED] - Reject colliding or invalid spm.modules names instead of silently dropping a module from the build

Pull Request resolved: #58060

Test Plan: ✅ Unit tests/CI

Reviewed By: mdvacca

Differential Revision: D117360298

Pulled By: cipolleschi

fbshipit-source-id: d2531d51b6d371cf6bc98dc85c9071ee81fb7a37
@meta-codesync meta-codesync Bot added the Merged This PR has been merged. label Aug 25, 2026
@meta-codesync

meta-codesync Bot commented Aug 25, 2026

Copy link
Copy Markdown

@cipolleschi merged this pull request in 962aeec.

cipolleschi pushed a commit that referenced this pull request Aug 26, 2026
Summary:
A dependency's Swift name is derived from its npm package name with the scope dropped, which makes two collisions unavoidable: `powersync/react-native` derives `ReactNative`, one of React Native's own names, and `a/foo` and `b/foo` both derive `Foo`.

Either was emitted into the package graph as-is, and SwiftPM then failed deep inside dependency resolution with a duplicate-name error that named neither the library nor the react-native.config.js that caused it.

This was seen in the PR here powersync-ja/powersync-js#1076 - and was hard to fix.

This PR fixes this by adding support for prefixing the name with the scope if the name without scope crashes.

In addition the same logic is added when two packages have names that collide with each other.

If the name given by the SwiftPM scaffolder doesn't work for you - you can use the `spm.name` field in the `react-native.config.js` file to set a specific name (see SwiftPM docs).

[IOS] [FIXED] - Resolve Swift manifest naming collisions

Pull Request resolved: #58044

Test Plan: ✅ Unit tests green

Reviewed By: mdvacca, cortinico

Differential Revision: D117178818

Pulled By: cipolleschi

fbshipit-source-id: 23269f4d959b84ac484be1911e8a863c74008ff9
(cherry picked from commit 962aeec)
cipolleschi pushed a commit that referenced this pull request Aug 26, 2026
)

Summary:
An app declares extra native modules through spm.modules in its react-native.config.js. Those names went into the generated package graph unvalidated, and two of the ways they can go wrong fail silently.

This PR fixes this by using the same Swift name collision detection/resolving as we introduced in #58044

> **NOTE:** #58044 must be merged before this one so that we can change the base branch for this one to `main`

## Changelog:
[IOS] [FIXED] - Reject colliding or invalid spm.modules names instead of silently dropping a module from the build

Pull Request resolved: #58060

Test Plan: ✅ Unit tests/CI

Reviewed By: mdvacca

Differential Revision: D117360298

Pulled By: cipolleschi

fbshipit-source-id: d2531d51b6d371cf6bc98dc85c9071ee81fb7a37
(cherry picked from commit 39cd1df)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. p: Expo Partner: Expo Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants