Skip to content

Commit dd369dd

Browse files
committed
docs(swiftpm): correct SwiftPM docs to match current scripts
Fix statements in the SwiftPM and ios-prebuild __docs__ that no longer match the code: CLI options, exit code 2 causes, what sync does and when it re-runs, which command writes each file, header products, remote mode, and header sidecar layout. Add an environment variables table.
1 parent 1449daa commit dd369dd

6 files changed

Lines changed: 273 additions & 173 deletions

File tree

‎packages/react-native/scripts/ios-prebuild/__docs__/README.md‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,14 @@ overlay**. The layout contract is defined and validated in code:
126126
the `React/` and bare-aliased headers into every slice's
127127
`React.framework/Headers`, and `buildReactNativeHeadersXcframework()`
128128
assembles the headers-only `ReactNativeHeaders.xcframework` carrying every
129-
other namespace (incl. `react/`) plus the third-party dependency namespaces
130-
(`folly`, `glog`, `boost`, `fmt`, `double-conversion`, `fast_float`). The
131-
Hermes public headers (`<hermes/...>`) are folded in only on the SwiftPM
132-
consumer side (`ensureHeadersLayout`); the published prebuild artifact does
133-
not yet carry them (TODO in `xcframework.js`).
129+
other React Native namespace (incl. `react/`). The third-party dependency
130+
namespaces (`folly`, `glog`, `boost`, `fmt`, `double-conversion`,
131+
`fast_float`, `SocketRocket`) ship in the separate headers-only
132+
`ReactNativeDependenciesHeaders.xcframework` sidecar, built by the
133+
dependencies prebuild. The Hermes public headers (`<hermes/...>`) are folded
134+
into the published `ReactNativeHeaders` when the hermes-ios headers are
135+
staged; without them the compose ships no `hermes/`, unless `--require-hermes`
136+
is set, which makes it fail closed.
134137

135138
### Artifacts
136139

@@ -145,9 +148,9 @@ The prebuild (`xcframework.js`) always produces:
145148
`ReactHeadersTarget/include/React` and rewrites `framework module React` to a
146149
plain `module React`, vended as the `ReactHeaders` target (see
147150
`spm-header-paths-contract.md` in the SwiftPM docs).
148-
- `ReactNativeHeaders.xcframework` — headers-only; carries every other
149-
namespace. Consumed by SwiftPM as a `binaryTarget` and by CocoaPods via the
150-
`React-Core-prebuilt` pod (headers flattened onto the header search path).
151+
- `ReactNativeHeaders.xcframework` — headers-only; carries every other React
152+
Native namespace. Consumed by SwiftPM as a `binaryTarget` and by CocoaPods via
153+
the `React-Core-prebuilt` pod (headers flattened onto the header search path).
151154

152155
### CocoaPods consumption
153156

‎packages/react-native/scripts/ios-prebuild/__docs__/headers-rules.md‎

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,14 @@ resolving `<react/...>` through `React.framework` requires case-folding
120120
`react.framework` → `React.framework`, which only works on case-insensitive
121121
filesystems. The header-search-path route (R2) is exact everywhere.
122122

123-
**R2 — every other namespace ships in ONE headers-only xcframework,
123+
**R2 — every other React Native namespace ships in ONE headers-only xcframework,
124124
`ReactNativeHeaders`.** Namespace dirs at its `Headers/` root: `react/`,
125-
`yoga/`, `jsi/`, `cxxreact/`, `React_RCTAppDelegate/`, … **including the
126-
third-party deps namespaces** (folly/glog/boost/fmt/double-conversion/
127-
fast_float, copied out of the ReactNativeDependencies artifact — which thereby
128-
becomes binary-only). SPM/Xcode auto-serve a binaryTarget's `Headers/` on the
129-
consumer's search path, so everything resolves with zero flags.
125+
`yoga/`, `jsi/`, `cxxreact/`, `React_RCTAppDelegate/`, … The third-party deps
126+
namespaces (folly, glog, boost, fmt, double-conversion, fast_float,
127+
SocketRocket) are **not** here: they ship in the headers-only
128+
`ReactNativeDependenciesHeaders` sidecar built by the deps prebuild. SPM/Xcode
129+
auto-serve a binaryTarget's `Headers/` on the consumer's search path, so
130+
everything resolves with zero flags.
130131

131132
**R3 — NO include rewriting, anywhere.** Shipped headers are byte-identical to
132133
the repo. If a header's includes don't work in the packaged layout, the fix is
@@ -271,11 +272,11 @@ module.
271272

272273
### `buildReactNativeHeadersXcframework`
273274

274-
1. Stage all R2 entries, then copy the six deps namespaces from
275-
`third-party/ReactNativeDependencies.xcframework/Headers`. A declared deps
276-
namespace that is missing is a **hard error** — previously a warn-and-ship,
277-
which once produced a silently deps-less artifact (1.6 MB instead of 11 MB).
278-
2. Optionally fold in the `hermes/` public headers (consumer-side compose path).
275+
1. Stage all R2 entries. The deps namespaces are not copied here; they ship in
276+
the `ReactNativeDependenciesHeaders` sidecar, whose namespace guards live in
277+
`buildDepsHeadersXcframework` (`headers-xcframework.js`).
278+
2. Fold in the `hermes/` public headers when they are staged (both the prebuild
279+
compose and the consumer-side compose path).
279280
3. Write the R10 umbrella files, then the R5 module map.
280281
4. Compile a stub static archive per slice (headers-only artifacts still need a
281282
library for `xcodebuild -create-xcframework`) and compose the xcframework.
@@ -296,13 +297,16 @@ mtime + hermes presence).
296297
| `#import <React/RCTMountingManager.h>` | same, **textual** via the R9 entry | R9 |
297298
| `#import <react/renderer/...>` (C++) | `ReactNativeHeaders/Headers` search path, textual | R2 |
298299
| `#import <yoga/Yoga.h>` | same, **modular** via the yoga R5 module | R2, R5 |
299-
| `#import <folly/dynamic.h>` | same (deps namespaces relocated here) | R2 |
300+
| `#import <folly/dynamic.h>` | SwiftPM: the `ReactNativeDependenciesHeaders` sidecar's `Headers/` (deps namespaces are not in `ReactNativeHeaders`) | R2 |
300301
| `<React_RCTAppDelegate/React_RCTAppDelegate-umbrella.h>` | same, modular | R10 |
301302
| `#import "RCTFabricComponentsPlugins.h"` (quoted, community Fabric pods) | CocoaPods only: re-vended by the `React-RCTFabric` facade into the pod header map (`rncore_facades.rb`, `FACADE_REEXPOSED_HEADERS`) | — |
302303

303-
- **SwiftPM**: both xcframeworks are plain `.binaryTarget`s; Xcode auto-serves
304-
`React.framework`'s `Headers/`+`Modules/` and `ReactNativeHeaders`' `Headers/`
305-
(incl. its `module.modulemap`) to dependents. Zero flags.
304+
- **SwiftPM**: `React.xcframework` is not in the package graph. Its headers are
305+
copied into `ReactHeadersTarget/include/React` with the module map rewritten
306+
to a plain `module React`, vended as the `ReactHeaders` target. The only
307+
`.binaryTarget`s are `ReactNativeHeaders` and
308+
`ReactNativeDependenciesHeaders`; Xcode auto-serves their `Headers/` (incl.
309+
`module.modulemap`) to dependents. Zero flags.
306310
- **CocoaPods**: `React-Core-prebuilt`'s `prepare_command` flattens
307311
`ReactNativeHeaders`' Headers (incl. the module map) into the pod, and the
308312
React-core pods are installed as dependency-only **facades**
@@ -316,8 +320,8 @@ mtime + hermes presence).
316320
| R9 allowlist validation | private header removed/renamed, or bucket drifted | `validatePrivateReactHeaders` |
317321
| R10 umbrella check | a probed namespace loses all modular headers | `planFromInventory` |
318322
| R5 exemption assert | an invalid-module-identifier namespace gains a modular-candidate header | `planFromInventory` |
319-
| Deps namespace guard (missing) | folly/glog/… not staged at compose time | `buildReactNativeHeadersXcframework` |
320-
| Deps namespace guard (undeclared) | the deps artifact ships a namespace not in `DEPS_NAMESPACES` (new third-party dep) | `buildReactNativeHeadersXcframework` |
323+
| Deps namespace guard (missing) | folly/glog/… not staged at compose time | `buildDepsHeadersXcframework` |
324+
| Deps namespace guard (undeclared) | the deps artifact ships a namespace not in `DEPS_NAMESPACES` (new third-party dep) | `buildDepsHeadersXcframework` |
321325
| Include-health ratchet | a shipped header gains a `notShipped`/`unresolved`/quoted-unresolvable include not in the committed baseline | `headers-verify.js` |
322326
| Structural gate | composed module maps/umbrellas differ from the spec render; R9 headers or deps dirs absent | `headers-verify.js` |
323327
| Compile gates | the React module, any R5 namespace module, the R10 umbrella, the R9 textual (Expo-shape) surface, or Swift `RCTBridge.moduleRegistry` fails to compile | `headers-verify.js` (CI: prebuild compose job) |

‎packages/react-native/scripts/spm/__docs__/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Three documents cover the design, each owning one area:
5252
| Document | Covers |
5353
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5454
| [spm-scripts.md](./spm-scripts.md) | The tool itself: CLI surface, the six-step pipeline, [every file it creates or modifies](./spm-scripts.md#files-the-tool-touches), the two auto-sync hooks, and how Debug/Release flavor selection works. |
55-
| [spm-header-paths-contract.md](./spm-header-paths-contract.md) | How headers and package references resolve. The contract is **zero-`-I`**: no header search paths and no `unsafeFlags` in any generated manifest. Also covers remote mode. |
55+
| [spm-header-paths-contract.md](./spm-header-paths-contract.md) | How headers and package references resolve. The contract is **zero-`-I`**: no generated manifest carries a search path into React Native's own headers. Also covers remote mode. |
5656
| [spm-autolinking-plugins.md](./spm-autolinking-plugins.md) | The extension seam for frameworks with their own module system (Expo is the first consumer): discovery, the full context/return contract including `flavoredFrameworks`, `watchPaths` and `scriptPhases`, and failure behavior. |
5757

5858
Two ideas explain most of the architecture:
@@ -83,7 +83,8 @@ Two ideas explain most of the architecture:
8383
local `React-GeneratedCode` package rather than a Pod.
8484
- **`@react-native-community/cli config`** — supplies the autolinking metadata
8585
(`autolinking.json`) that the SwiftPM autolinker turns into a `Package.swift`.
86-
Overridable via `--configCommand`.
86+
Overridable via the `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` environment variable,
87+
or `--config-command` when running `setup-apple-spm.js` directly.
8788

8889
### Uses this
8990

‎packages/react-native/scripts/spm/__docs__/spm-autolinking-plugins.md‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ allowlist is required. The deprecated `spm.autolinkingPlugin` in
4848
`react-native.config.js` is still read — see
4949
[Migrating from react-native.config.js](spm-scripts.md#where-swiftpm-settings-live).
5050

51+
A dependency that declares a plugin owns its native contribution. React Native
52+
does not build it as an autolinked target, so it needs no `Package.swift` of its
53+
own.
54+
5155
**Opt-out escape hatch.** An app can exclude a plugin from its own package.json:
5256

5357
```json
@@ -149,6 +153,9 @@ with a warning (never fatal), and each non-string / empty / **relative** entry
149153
is dropped with a warning. Absolute-only, because the generated phase tests
150154
these paths with no cwd context. The kept paths are folded into
151155
`<outputDir>/.spm-sync-watch-paths` alongside RN's own, then deduped and sorted.
156+
Only paths that exist when the sync runs are written; a missing path is dropped
157+
silently. So the **vanished** check above applies only to paths that existed at
158+
the last sync.
152159

153160
### `scriptPhases` — build-time shell phases on the app target
154161

@@ -185,9 +192,8 @@ agree nothing is rewritten, which is what keeps an unchanged declaration
185192
re-syncing to a byte-identical project. The consequence worth knowing: a phase
186193
you **drag somewhere else in Xcode is moved back** to its declared position on
187194
the next sync, because the plugin's declaration is the source of truth. Only the
188-
`id` behaves differently — it is a key, not a label, so renaming it is a remove
189-
190-
- add.
195+
`id` behaves differently — it is a key, not a label, so renaming it is a
196+
remove + add.
191197

192198
Phases are injected by `spm add` / `spm update` **only**. The build-time `sync`
193199
rewrites the sidecar but never touches the `.xcodeproj`, so a newly declared
@@ -292,8 +298,9 @@ wiring, it stays correct across repackaging.
292298

293299
The plugin returns **data** — it never writes into React Native's generated
294300
tree. RN owns the merge, so a re-sync reproduces the same `Package.swift`
295-
byte-for-byte (idempotent). Package and product contributions are **deduped by
296-
name** across plugins.
301+
byte-for-byte (idempotent). Across plugins, package contributions are **deduped
302+
by name** and product contributions by **package and name** — the first
303+
contribution wins.
297304

298305
## Lifecycle
299306

@@ -317,6 +324,12 @@ function, throws, or returns a malformed contribution aborts the run with a
317324
message identifying the framework. A framework silently dropping its modules (a
318325
green build missing native code) is worse than a loud stop.
319326

327+
A plugin's host dependency is also a hard error when another library lists it in
328+
`swiftpmConfig.dependencies` and that library has no `Package.swift` of its own
329+
(shipped or scaffolded). React Native builds no target for the host, so there is
330+
nothing to depend on; remove the entry — the plugin already links its products
331+
into the app.
332+
320333
## Status & open items (Preview)
321334

322335
- **Implemented & tested:** discovery (transitive + deny-list), invocation,

0 commit comments

Comments
 (0)