Skip to content

[iOS][swiftpm] Derive an SPM library's Swift name from its podspec - #58290

Open
chrfalch wants to merge 2 commits into
mainfrom
spm/derive-name-from-podspec
Open

[iOS][swiftpm] Derive an SPM library's Swift name from its podspec#58290
chrfalch wants to merge 2 commits into
mainfrom
spm/derive-name-from-podspec

Conversation

@chrfalch

@chrfalch chrfalch commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary:

An autolinked library's SwiftPM target name is also its header import prefix, so deriving it from the npm package name was wrong for most of the ecosystem (react-native-svg publishes RNSVG, not ReactNativeSvg) and wrong silently — no error, just headers nobody can import under the expected name.

How:

This PR reads the podspec file on scaffolding, and will use the name from the podspec if available. In addition it deprecates the react-native.config.js spm section in favor of the library's package.json file.

Configuration

A package's SwiftPM settings now live in swiftpmConfig in its package.json, following codegenConfig's conventions: name, dependencies, autolinkingPlugin and scaffold for a library, modules and denyPlugins for an app. The spm block in react-native.config.js is deprecated — still read, so nothing breaks, but it warns once per file and package.json wins field by field.

Resolving

A name resolves from swiftpmConfig.name, then the deprecated spm.name, then the podspec's header_dir or name, then the npm name. The podspec is thereby transitional rather than permanent: spm scaffold records the name it derived as swiftpmConfig.name in the library's package.json, so the next run needs no podspec to name it. It never overwrites a name the library already declares, never records a name guessed from the npm name, and reports what it did.

Failsafety

A prefix Swift cannot spell is normalized to the identifier SwiftPM would compile it as, with a warning. A reserved name or two deps landing on one name is a hard error naming swiftpmConfig.name as the fix; the scope-borrowing that auto-corrected collisions is removed, since a name the build invents is a name no #import can predict. Collision checks key on SwiftPM's c99 name, so react-native-svg and react_native_svg no longer pass and then compile as one module.

Implementation

Name resolution reads the two podspec fields it needs with the regex parser, so it adds no pod ipc spec spawn, and the full read is memoized on the resolved path so one run reads a podspec once across name resolution, header search paths and scaffolding.

rn-tester and the Apple test library move to the new location.

Changelog:

[IOS] [FIXED] - Read SwiftPM name from podspec and store in package.json when scaffolding

Test Plan:

✅ Unit tests

@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 Sep 2, 2026
@facebook-github-tools facebook-github-tools Bot added 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. labels Sep 2, 2026
@chrfalch
chrfalch force-pushed the spm/derive-name-from-podspec branch 3 times, most recently from c66bafe to 9dda3a4 Compare September 2, 2026 14:18
@chrfalch
chrfalch requested a review from cipolleschi September 2, 2026 14:44

@cipolleschi cipolleschi 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.

Thank you for putting this together—the direction addresses a real gap. I’m requesting changes for the two concrete blockers called out inline: the current RNTester SwiftPM builds fail after the target-name change, and the fast podspec parsing can promote a subspec header_dir to the package name for external libraries. Once those are fixed with regression coverage, I’ll be happy to take another look.

Comment thread packages/react-native/scripts/spm/__docs__/spm-scripts.md Outdated
const derived = toSwiftName(npmName);
if (!reserved.has(derived.toLowerCase())) {
return derived;
const fromPodspec = podspecSwiftName(npmName, podspec);

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.

Thanks for tackling this naming mismatch. This currently changes the common test library from the npm-derived ReactNativeTestLibraryCommon to its podspec name, TestLibraryCommon, while TestLibraryApple.mm still imports <ReactNativeTestLibraryCommon/TestLibraryCommon.h>. Both test_ios_spm_rntester Debug and Release fail with that header not found. Could we update the fixture/import and add regression coverage so these end-to-end SPM builds pass?

: null;
};
const name = field('name');
const headerDir = field('header_dir');

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.

Thanks for adding the podspec fallback. This fast path also picks up a subspec's ss.header_dir as though it were the package-level value: the regex matches the trailing s.header_dir. For example, react-native-svg has s.name = 'RNSVG' and a nested ss.header_dir = 'rnsvg', so this resolves rnsvg even though the intended library name here is RNSVG; react-native-screens has the same shape. Could we make this scope-aware (or fall back to the pod name / explicit config) and cover these external-pod shapes in a regression test?

… config a home

An autolinked library's SwiftPM target name is also its header import prefix,
so deriving it from the npm package name was wrong for most of the ecosystem
(react-native-svg publishes RNSVG, not ReactNativeSvg) and wrong silently — no
error, just headers nobody can import under the expected name.

A package's SwiftPM settings now live in `swiftpmConfig` in its package.json,
following codegenConfig's conventions: `name`, `dependencies`,
`autolinkingPlugin` and `scaffold` for a library, `modules` and `denyPlugins`
for an app. The `spm` block in react-native.config.js is deprecated — still
read, so nothing breaks, but it warns once per file and package.json wins field
by field.

A name resolves from `swiftpmConfig.name`, then the deprecated `spm.name`, then
the podspec's `header_dir` or name, then the npm name. The podspec is thereby
transitional rather than permanent: `spm scaffold` records the name it derived
as `swiftpmConfig.name` in the library's package.json, so the next run needs no
podspec to name it. It never overwrites a name the library already declares,
never records a name guessed from the npm name, and reports what it did.

A prefix Swift cannot spell is normalized to the identifier SwiftPM would
compile it as, with a warning. A reserved name or two deps landing on one name
is a hard error naming swiftpmConfig.name as the fix; the scope-borrowing that
auto-corrected collisions is removed, since a name the build invents is a name
no #import can predict. Collision checks key on SwiftPM's c99 name, so
react-native-svg and react_native_svg no longer pass and then compile as one
module.

Name resolution reads the two podspec fields it needs with the regex parser, so
it adds no `pod ipc spec` spawn, and the full read is memoized on the resolved
path so one run reads a podspec once across name resolution, header search paths
and scaffolding.

rn-tester and the Apple test library move to the new location.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chrfalch
chrfalch force-pushed the spm/derive-name-from-podspec branch from 9dda3a4 to e19cd06 Compare September 2, 2026 16:15
A SwiftPM target name is also the prefix its consumers import it under, so
deriving it from the podspec has to match what CocoaPods already named the
module. CocoaPods resolves `module_name || c99(header_dir) || c99(name)`
(cocoapods-core specification.rb); this consulted only `header_dir` and the
pod name.

`react-native-maps` declares `s.name = "react-native-maps"` and
`s.module_name = "ReactNativeMaps"` with no `header_dir`, so it resolved to
`react-native-maps` — compiled as the module `react_native_maps` — breaking
every `import ReactNativeMaps` and `#import <ReactNativeMaps/...>`. The npm
transform it replaced happened to agree with CocoaPods here.

`header_dir` still wins, being the prefix a library sets when it differs from
its pod name; `module_name` comes next; the pod name is last. The regex fast
path learns `module_name` too, and declines when one is declared but not
literal — without both, the fast path answers first and the new tier never
runs.

Also:

- Both in-repo fixtures declare `swiftpmConfig.name`, so `spm scaffold` no
  longer writes into tracked files on every run, in CI and locally.
- A podspec that yields no name warns, naming `swiftpmConfig.name`: the
  fallback depends on whether CocoaPods is installed, so the same library
  could otherwise be named differently on two machines.
- Two errors named the deprecated config block; they now describe the
  dependency without naming a location, which is correct whichever the
  library uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chrfalch

chrfalch commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Thank you for putting this together—the direction addresses a real gap. I’m requesting changes for the two concrete blockers called out inline: the current RNTester SwiftPM builds fail after the target-name change, and the fast podspec parsing can promote a subspec header_dir to the package name for external libraries. Once those are fixed with regression coverage, I’ll be happy to take another look.

Fixed - Good catch.

The fast path is now anchored to the root spec. A subspec's ss.header_dir can no longer name the library. s.name = 'RNSVG' with a nested ss.header_dir = 'rnsvg' resolves RNSVG. react-native-screens behaves the same.

If a value is declared but not readable, the fast path declines. pod ipc spec then resolves it.

Tests: "does not take a subspec's header_dir as the library's" and "keeps the spec's own module_name, not a subspec's".

@chrfalch

chrfalch commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Both blockers are fixed. A later review found a third.

Podspec module_name was ignored. react-native-maps sets s.name = "react-native-maps" and s.module_name = "ReactNativeMaps", with no header_dir. So it resolved to react-native-maps, and the module became react_native_maps. Every import ReactNativeMaps broke.

The order is now header_dir → module_name → pod name. This matches CocoaPods: module_name || c99(header_dir) || c99(name).

No CI job would have caught it. RNTester has only the two fixtures.

Two smaller fixes. Both fixtures declare swiftpmConfig.name, so spm scaffold no longer writes to tracked files. A podspec with no readable name now warns, because the fallback depends on CocoaPods being installed.

929 tests, 20 suites.

@chrfalch
chrfalch requested a review from cipolleschi September 2, 2026 16:58
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. 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.

2 participants