Skip to content

Mark SwiftPM remote-package mode as planned in the docs - #59631

Merged
meta-codesync[bot] merged 5 commits into
mainfrom
spm-docs-remote-mode-planned
Nov 3, 2026
Merged

meta-codesync[bot] merged 5 commits into
mainfrom
spm-docs-remote-mode-planned

Conversation

@chrfalch

@chrfalch chrfalch commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The SwiftPM docs describe remote-package mode as a feature that works today. In remote mode, React Native is fetched by URL (RN_SPM_REMOTE_URL). This mode is planned and not supported. The text made library authors think that their Package.swift needs a remote form. It also made them think that relative paths to the app's folders are a workaround.

This PR changes only docs:

  1. The "Remote-package mode" section is now "Remote-package mode (planned)". A note at the top says that the mode is not supported. The design text stays below the note.
  2. The path table no longer lists remote alternatives.
  3. Rule 1 of the hand-authored library contract now shows the supported local form. A library refers to ../../../../xcframeworks (React Native) and ../../../ios (the app's React-GeneratedCode package) from its link folder build/generated/autolinking/libs/<Name>.
  4. The docs say that these relative paths are the intended design and that the folders they point to are stable.
  5. A new rule 2 says that each library target defines RCT_NEW_ARCH_ENABLED=1 in cSettings and cxxSettings. SwiftPM mode supports only the New Architecture, and nothing else sets this define for a SwiftPM target. Libraries keep their #ifdef gates, because the same sources still build with CocoaPods.
  6. The old rule 2 (the library ships its own generated code) is now rule 3, and it is optional.
  7. spm-scripts.md, spm-autolinking-plugins.md and the docs index mark the remote environment variables, the remote build error and the remote packageRef shape as planned.

The RN_SPM_REMOTE_URL code does not change.

Changelog:

[INTERNAL] [CHANGED] - Mark SwiftPM remote-package mode as planned in the SwiftPM docs

Test Plan:

  • npx prettier --check packages/react-native/scripts/spm/__docs__/*.md prints "All matched files use Prettier code style!".
  • git grep remote-package-mode finds only links to the new #remote-package-mode-planned anchor.
  • I compared the package names, products and relative paths in rule 1 with scripts/spm/spm-utils.js, generate-spm-autolinking.js and scaffold-package-swift.js.

The SwiftPM docs described remote-package mode (React Native fetched by
URL through RN_SPM_REMOTE_URL) as a current feature. It is planned and not
supported. Mark it as planned, and describe the hand-authored library
contract in the supported local form.
@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 Oct 29, 2026
@chrfalch
chrfalch requested a review from cipolleschi October 29, 2026 17:10
@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 Oct 29, 2026
no per-app codegen headers from the consuming app.
`@chrfalch/react-native-calculator` (a hand-authored Fabric/TurboModule library)
proves this.
These relative paths are the intended design. The folders they reach,

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.

I'm not sure we should call these folders stable. build/xcframeworks and build/generated/ios live under ios/build/, which should be gitignored. They're generated output, recreated on each machine by spm, not something we version or promise to keep in place.

These paths also only resolve when the library is reached through the libs/<Name> symlink, so the library can't be built on its own (e.g. swift build in its own repo).

Could we describe this as how it works today, rather than a contract libraries can rely on?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. Reworded in b32bad9: the docs now say these folders are generated and gitignored, and that the paths resolve only through the libs/<SwiftName> link folder, so a library cannot build on its own. The word "stable" is gone. What remains is that libraries depend on this layout today, so we will not change it without notice.

```

- The `packageRef` keys signal local or remote mode: `path` xor `url`+`version`.
- `packageRef` has the local `path` shape. The `url`+`version` shape appears

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.

Since context.react exists to keep plugins working when this changes, could we say something like "today packageRef always has the path shape; handle url+version too, since remote mode is planned"? Otherwise plugin authors may only handle path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done in b32bad9: today packageRef always has the path shape, and plugins should handle url+version too, since remote mode is planned.


1. Depend on the React Native package and the app's codegen package by relative
path. SwiftPM reaches the library's manifest through React Native's
per-library link folder, `build/generated/autolinking/libs/<Name>`. The paths

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.

Nit: the link folder is named after the library's Swift name, not its npm name. It doesn't change the two paths here, but it matters when a library depends on another one (../<SwiftName>). Maybe use libs/<SwiftName>?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Changed to libs/<SwiftName> in b32bad9, with a note that the folder takes the Swift name, not the npm name.

## Remote-package mode
## Remote-package mode (planned)

> **Planned, not supported.** The `RN_SPM_REMOTE_URL` code path exists, but it

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.

Not for this PR, but since RN_SPM_REMOTE_URL is now "do not use", should the code print a warning when it's set? Today it's still honored without any message.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, a warning makes sense. I will do it in a separate follow-up PR, so this one stays docs-only.

…contract

- Say the build folders are generated and gitignored, and resolve only
  through the libs/<SwiftName> link folder.
- Name the link folder after the library's Swift name.
- Tell plugin authors to handle the url+version packageRef shape too.
@chrfalch
chrfalch requested a review from cipolleschi October 31, 2026 08:47
SwiftPM mode is New Architecture only, and nothing else sets the define for a SwiftPM target. Libraries keep their #ifdef gates for CocoaPods builds.

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

Thanks for clarifying the remote-mode status. I checked the docs against the code at this commit. Package names, products and relative paths all match. I left comments on a few places where the contract is incomplete or claims more than the code does. The most important are the missing DEBUG/NDEBUG defines, the Swift form of RCT_NEW_ARCH_ENABLED, and the packageRef "always" wording in the plugins doc.

`.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative")`,
and `.product(name: "ReactAppHeaders", package: "React-GeneratedCode")`. A
scaffolded manifest depends on the same products.
2. Define `RCT_NEW_ARCH_ENABLED=1` in both `cSettings` and `cxxSettings` of each

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.

Rule 2 lists RCT_NEW_ARCH_ENABLED as the only define, but every other Fabric C++ target in the graph also gets the config-gated defines from REACT_CXX_CONFIG_DEFINES (generate-spm-autolinking.js):

.define("DEBUG", .when(configuration: .debug)),
.define("NDEBUG", .when(configuration: .release)),

The comment there explains why: without NDEBUG in Release, DebugStringConvertible's vtable and the ShadowNode layout diverge from the prebuilt React.framework and the link fails. A Fabric library that follows rules 1 and 2 exactly would hit that in Release. Could the contract list these too (and mention CXX_API_DEFINES if the app opts into any)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Rule 2 now also lists the DEBUG/NDEBUG configuration defines from REACT_CXX_CONFIG_DEFINES, with the Release link failure as the reason, and shows them in a short manifest snippet. Fixed in 7cd0824.

and `.product(name: "ReactAppHeaders", package: "React-GeneratedCode")`. A
scaffolded manifest depends on the same products.
2. Define `RCT_NEW_ARCH_ENABLED=1` in both `cSettings` and `cxxSettings` of each
target: `.define("RCT_NEW_ARCH_ENABLED", to: "1")`. SwiftPM mode supports

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.

CocoaPods also passes the define to Swift through OTHER_SWIFT_FLAGS (new_architecture.rb:124). A library with Swift sources behind #if RCT_NEW_ARCH_ENABLED would quietly compile the old-arch branch under SwiftPM if it follows this rule as written. Can we add swiftSettings: [.define("RCT_NEW_ARCH_ENABLED")] here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added swiftSettings: [.define("RCT_NEW_ARCH_ENABLED")] to rule 2 and to the snippet, with a note that CocoaPods passes it through OTHER_SWIFT_FLAGS. Fixed in 7cd0824.

scaffolded manifest depends on the same products.
2. Define `RCT_NEW_ARCH_ENABLED=1` in both `cSettings` and `cxxSettings` of each
target: `.define("RCT_NEW_ARCH_ENABLED", to: "1")`. SwiftPM mode supports
only the New Architecture, and nothing else sets this define for a SwiftPM

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.

"nothing else sets this define for a SwiftPM target" isn't quite right for scaffolded libraries. install_modules_dependencies puts -DRCT_NEW_ARCH_ENABLED=1 into the podspec's compiler_flags (new_architecture.rb:112-113). The scaffolder then forwards compiler_flags as .unsafeFlags in cxxSettings only (scaffold-package-swift.js:596, 636-640).

So scaffolded libs get the define for C++/ObjC++, but plain .m files don't. That looks like a scaffolder gap, and this sentence hides it. Could you reword this to scope it to hand-authored manifests, and fix the .m gap (or open an issue for it)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reworded: the sentence now covers hand-authored manifests only. The scaffolder gap for plain .m files is fixed in #59632, which emits the define in cSettings as well as cxxSettings. Fixed in 7cd0824.

they are gitignored. The paths resolve only when SwiftPM reaches the library
through its `libs/<SwiftName>` link folder, so the library cannot build on its
own (for example, `swift build` in its own repository). Libraries depend on this
layout today, so React Native will not change it without notice.

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.

I'm not sure we should promise this layout won't change. generate-spm-autolinking.js accepts --output and --xcframeworks-path, which move these folders, and the relative paths have changed across scaffolder versions. A library that hardcodes ../../../../xcframeworks breaks when those flags are used. Could this say the paths assume the default layout, without the stability commitment?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. The stability wording is gone. The doc now says the relative paths assume the default layout, and that --output and --xcframeworks-path move these folders, so the hardcoded paths do not resolve when either is used. Fixed in 7cd0824.

target. Without it, the preprocessor drops all code behind
`#ifdef RCT_NEW_ARCH_ENABLED`. Keep those `#ifdef` gates in the sources: the
same sources still build under CocoaPods.
3. Optionally, ship its own generated code. This suits a library that wants to

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.

Rule 3 is described as letting a library be "self-contained", but rule 1 now requires every hand-authored library to depend on the app's React-GeneratedCode by relative path, and line 147 says the library can't build on its own. Does a rule-3 library still need the ReactAppHeaders dependency? If so, "self-contained" is misleading. If not, rule 1 should say it's optional for rule-3 libraries. Also, does @chrfalch/react-native-calculator follow the new rule 1?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Both fixed. Rule 3 no longer says "self-contained" (7cd0824). The calculator reference is removed (9ad602d), because it is not in this repo and it predates rules 1 and 2. Rule 1 still requires ReactAppHeaders for rule-3 libraries. That package also carries other autolinked libraries' headers, and we have not proved that a rule-3 library can build without it.

scaffolder or autolinker marker). That library needs two things, and **no
discovery code**. A third thing is optional:

1. Depend on the React Native package and the app's codegen package by relative

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.

Worth noting that React-GeneratedCode declares platforms: [.iOS(.v15), ...] (codegen/templates/Package.swift.spm-template:31). A library that declares a lower floor (e.g. .iOS(.v13), common for libs supporting older RN) will fail resolution when it adds the ReactAppHeaders product. Could the contract say libraries need .iOS(.v15) or higher?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added: libraries that depend on ReactAppHeaders must declare .iOS(.v15) or higher, because React-GeneratedCode declares .iOS(.v15). Fixed in 7cd0824.

```

- The `packageRef` keys signal local or remote mode: `path` xor `url`+`version`.
- Today `packageRef` always has the local `path` shape. Handle the

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.

"always" isn't accurate. reactDescriptor (generate-spm-autolinking.js:159-185) returns the {name, url, version} shape whenever remoteCfg is set, which happens if RN_SPM_REMOTE_URL or a persisted spm-remote.json url is present. It also returns null for the whole context.react when no xcframeworks path resolves. A plugin that trusts this sentence would emit .package(path: "undefined") or throw. Could this say plugins must handle both shapes and a null context.react today?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Right. The doc now says packageRef has the local path shape by default and the url + version shape when RN_SPM_REMOTE_URL or a persisted spm-remote.json url is present, that plugins must handle both, and that context.react can be null and must be checked. Fixed in 7cd0824.

## Remote-package mode
## Remote-package mode (planned)

> **Planned, not supported.** The `RN_SPM_REMOTE_URL` code path exists, but it

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.

The docs say not to use remote mode, but remotePackageConfig in spm-utils.js still turns it on silently whenever RN_SPM_REMOTE_URL or a leftover spm-remote.json url is present. The failure path also tells users to set RN_SPM_REMOTE_VERSION, which leads them further into the unsupported path. Could you add a runtime warning where remote mode is read, so the code backs up the docs? Fine as a follow-up PR if you prefer.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. As promised in the last round, the runtime warning comes in a separate PR. It will also stop pointing users at RN_SPM_REMOTE_VERSION.

2. Define `RCT_NEW_ARCH_ENABLED=1` in both `cSettings` and `cxxSettings` of each
target: `.define("RCT_NEW_ARCH_ENABLED", to: "1")`. SwiftPM mode supports
only the New Architecture, and nothing else sets this define for a SwiftPM
target. Without it, the preprocessor drops all code behind

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.

Broader question: this rule makes every third-party library author responsible for a tooling gap. Existing hand-authored SwiftPM libraries that predate the rule will silently compile their new-arch code out, and nothing points at the cause. Since SwiftPM mode is New-Arch-only, could the define come from React Native instead, for example from a React-owned header in the ReactHeaders product that libraries already import? Happy for that to be a follow-up you own, but I'd like to know if it's been considered.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point, and worth doing. We will look at it as a follow-up. We first need to check how far a React-owned header can reach: files that test the define before they include a React header, and Swift #if checks, may not see it. If a header covers only part of it, a warning from the sync step for hand-authored manifests without the define is another option.

…eRef shapes

- Rule 2 adds the DEBUG/NDEBUG configuration defines and the Swift form of RCT_NEW_ARCH_ENABLED.
- The define note is scoped to hand-authored manifests.
- The link-folder paths assume the default layout; --output and --xcframeworks-path move them.
- Libraries that depend on ReactAppHeaders must declare iOS 15 or higher.
- Rule 3 no longer calls the library self-contained.
- The plugins doc says packageRef has two shapes and context.react can be null.
@meta-codesync

meta-codesync Bot commented Nov 3, 2026

Copy link
Copy Markdown

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

@meta-codesync
meta-codesync Bot merged commit cf3704f into main Nov 3, 2026
55 checks passed
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @chrfalch in cf3704f

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Nov 3, 2026
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