Uh oh!
There was an error while loading. Please reload this page.
SPM: allow overriding the autolinking config command - #57662
Closed
chrfalch wants to merge 2 commits into
Closed
Conversation
The SwiftPM autolinking flow hardcodes `@react-native-community/cli config` to generate autolinking.json. Apps that replace community autolinking — most notably Expo, which ships expo-modules-autolinking instead of @rncli — have no injection point, so the command fails, autolinking.json is never written, and the Autolinked SwiftPM package is emitted empty (external native modules can't be imported). CocoaPods already solves this: `use_native_modules!(config_command)` takes the command as a parameter, letting an Expo Podfile pass its own. This adds the equivalent hook to the SPM path: - `--config-command '<json argv array>'` CLI flag, and - the `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` env var (same JSON-array format), which the injected Xcode build phase can read even when it can't rewrite argv. Both go through one validator; precedence is flag > env > default. The value is the command to run (its stdout is captured as the config JSON), mirroring CocoaPods — not a precomputed result. ## Changelog: [IOS] [ADDED] - Allow overriding the SwiftPM autolinking config command via --config-command / RCT_SPM_AUTOLINKING_CONFIG_COMMAND Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chrfalch
requested review from
cipolleschi and Copilot
and removed request for
CopilotJuly 24, 2026 09:32
setup-apple-spm previously swallowed a config-command failure as a warning and continued, emitting an empty Autolinked package that only surfaced much later as an inscrutable `unable to resolve module dependency` at build time — with nothing pointing at the config command. Extract the policy into generateAutolinkingConfigOrFailClosed: on a config-command error (non-zero exit, unparseable output, or a config missing project.ios.sourceDir) it logs an actionable message naming RCT_SPM_AUTOLINKING_CONFIG_COMMAND / --config-command, sets process.exitCode = 2 (a hard Xcode build-phase error, matching the RemoteVersionError path), and stops instead of proceeding. The guard is narrow by construction: a genuinely native-module-free app never reaches the error path — its command exits 0 with valid, empty-dependency JSON, so the generator returns normally and the legitimate empty-package path stays valid. ## Changelog: [IOS] [CHANGED] - Fail closed with an actionable error when the SwiftPM autolinking config command fails, instead of silently emitting an empty Autolinked package Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cipolleschi
approved these changes
Jul 24, 2026
@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D113554857. |
@cipolleschi merged this pull request in a7ba4ce. |
fabriziocucci pushed a commit
that referenced
this pull request
Aug 3, 2026
Summary:
The SwiftPM autolinking flow hardcodes `react-native-community/cli config` to generate `autolinking.json` (`generate-spm-autolinking-config.js`). Apps that replace community autolinking — most notably **Expo**, which ships `expo-modules-autolinking` instead of `react-native-community/cli` — had no way to override that command, and any failure was swallowed. The result: the config command fails, `autolinking.json` is never written, the `Autolinked` SwiftPM package comes out empty, and `import Expo` (and every Expo module) fails to resolve — surfacing much later as an inscrutable `unable to resolve module dependency: 'Expo'`.
CocoaPods already solves the injection half: `use_native_modules!(config_command = $default_command)` accepts the command as a parameter, so an Expo `Podfile` passes `expo-modules-autolinking react-native-config` in place of the `rncli` default. This PR adds the equivalent hook to the SwiftPM path **and** closes the silent-failure trap.
### 1. Allow overriding the config command
`generateAutolinkingConfig` already accepted a `configCommand` option internally; it was just never reachable. Two ways to supply it, mirroring the CocoaPods hook:
- **`--config-command '<json>'`** — CLI flag taking a JSON array of the argv.
- **`RCT_SPM_AUTOLINKING_CONFIG_COMMAND`** — env var in the same JSON-array format. This is the vehicle for the injected Xcode build phase, which usually can't rewrite the script's argv but can read env.
Both go through one `parseConfigCommandJson` validator (rejects non-JSON, non-arrays, empty arrays, and non-string / empty-string elements, with a `source`-named error). Precedence: **`--config-command` > `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` > default** (local `rncli` → `npx --no-install` fallback, unchanged). JSON (rather than whitespace-splitting) because real commands contain dashed flags and quoted script strings.
The value is the **command to execute** — its stdout is captured as the config JSON and written verbatim — exactly matching CocoaPods, not a precomputed result. An Expo app feeds the same argv it already builds for `use_native_modules!`:
```jsonc
RCT_SPM_AUTOLINKING_CONFIG_COMMAND='["node","--no-warnings","--eval","require('expo/bin/autolinking')","expo-modules-autolinking","react-native-config","--json","--platform","ios","--source-dir","/abs/path"]'
```
(Use `--platform ios`: the generator requires `project.ios.sourceDir` and everything downstream is iOS-only.)
### 2. Fail closed when the config command errors
Previously `main()` swallowed a config-command failure as a warning and continued, which is what let the empty package be produced silently. That policy is now extracted into `generateAutolinkingConfigOrFailClosed`: on a config-command error (non-zero exit, unparseable output, or a config missing `project.ios.sourceDir`) it logs an actionable message naming `RCT_SPM_AUTOLINKING_CONFIG_COMMAND` / `--config-command`, sets `process.exitCode = 2` (a hard Xcode build-phase error, matching the existing `RemoteVersionError` path), and stops.
The guard is deliberately narrow: a **genuinely native-module-free app never reaches the error path** — its command exits 0 with valid, empty-dependency JSON, so the generator returns normally and the legitimate empty-package path stays valid. Only an *erroring* command fails the build.
## Changelog:
[IOS] [ADDED] - Allow overriding the SwiftPM autolinking config command via `--config-command` / `RCT_SPM_AUTOLINKING_CONFIG_COMMAND`
[IOS] [CHANGED] - Fail closed with an actionable error when the SwiftPM autolinking config command fails, instead of silently emitting an empty Autolinked package
Pull Request resolved: #57662
Test Plan:
New unit tests, developed red → green:
- `generate-spm-autolinking-config-test.js` — env var honored; explicit `configCommand` beats env; invalid-JSON and invalid-shape (`[]`, `[1,2]`) throw with the source name; env unset falls back to the default command; env state saved/restored per test.
- `setup-apple-spm-test.js` — `parseArgs` parses `--config-command` into an argv array, defaults to `null` when omitted, and throws on an invalid value; `generateAutolinkingConfigOrFailClosed` returns the result on success (exit code untouched), passes `projectRoot`/`configCommand` through, and on a config-command error returns `null`, sets exit 2, and logs an actionable error that names the env var and preserves the underlying cause.
```
$ yarn jest --no-cache -i \
packages/react-native/scripts/spm/__tests__/generate-spm-autolinking-config-test.js \
packages/react-native/scripts/spm/__tests__/setup-apple-spm-test.js
Test Suites: 2 passed, 2 total
Tests: 39 passed, 39 total
```
`prettier` and `eslint` clean on all changed files.
Reviewed By: zeyap
Differential Revision: D113554857
Pulled By: cipolleschi
fbshipit-source-id: d0baeeefc91aed144cf9e405e198531ff88088a7
(cherry picked from commit a7ba4ce)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
The SwiftPM autolinking flow hardcodes
@react-native-community/cli configto generateautolinking.json(generate-spm-autolinking-config.js). Apps that replace community autolinking — most notably Expo, which shipsexpo-modules-autolinkinginstead of@react-native-community/cli— had no way to override that command, and any failure was swallowed. The result: the config command fails,autolinking.jsonis never written, theAutolinkedSwiftPM package comes out empty, andimport Expo(and every Expo module) fails to resolve — surfacing much later as an inscrutableunable to resolve module dependency: 'Expo'.CocoaPods already solves the injection half:
use_native_modules!(config_command = $default_command)accepts the command as a parameter, so an ExpoPodfilepassesexpo-modules-autolinking react-native-configin place of the@rnclidefault. This PR adds the equivalent hook to the SwiftPM path and closes the silent-failure trap.1. Allow overriding the config command
generateAutolinkingConfigalready accepted aconfigCommandoption internally; it was just never reachable. Two ways to supply it, mirroring the CocoaPods hook:--config-command '<json>'— CLI flag taking a JSON array of the argv.RCT_SPM_AUTOLINKING_CONFIG_COMMAND— env var in the same JSON-array format. This is the vehicle for the injected Xcode build phase, which usually can't rewrite the script's argv but can read env.Both go through one
parseConfigCommandJsonvalidator (rejects non-JSON, non-arrays, empty arrays, and non-string / empty-string elements, with asource-named error). Precedence:--config-command>RCT_SPM_AUTOLINKING_CONFIG_COMMAND> default (local@rncli→npx --no-installfallback, unchanged). JSON (rather than whitespace-splitting) because real commands contain dashed flags and quoted script strings.The value is the command to execute — its stdout is captured as the config JSON and written verbatim — exactly matching CocoaPods, not a precomputed result. An Expo app feeds the same argv it already builds for
use_native_modules!:(Use
--platform ios: the generator requiresproject.ios.sourceDirand everything downstream is iOS-only.)2. Fail closed when the config command errors
Previously
main()swallowed a config-command failure as a warning and continued, which is what let the empty package be produced silently. That policy is now extracted intogenerateAutolinkingConfigOrFailClosed: on a config-command error (non-zero exit, unparseable output, or a config missingproject.ios.sourceDir) it logs an actionable message namingRCT_SPM_AUTOLINKING_CONFIG_COMMAND/--config-command, setsprocess.exitCode = 2(a hard Xcode build-phase error, matching the existingRemoteVersionErrorpath), and stops.The guard is deliberately narrow: a genuinely native-module-free app never reaches the error path — its command exits 0 with valid, empty-dependency JSON, so the generator returns normally and the legitimate empty-package path stays valid. Only an erroring command fails the build.
Changelog:
[IOS] [ADDED] - Allow overriding the SwiftPM autolinking config command via
--config-command/RCT_SPM_AUTOLINKING_CONFIG_COMMAND[IOS] [CHANGED] - Fail closed with an actionable error when the SwiftPM autolinking config command fails, instead of silently emitting an empty Autolinked package
Test Plan:
New unit tests, developed red → green:
generate-spm-autolinking-config-test.js— env var honored; explicitconfigCommandbeats env; invalid-JSON and invalid-shape ([],[1,2]) throw with the source name; env unset falls back to the default command; env state saved/restored per test.setup-apple-spm-test.js—parseArgsparses--config-commandinto an argv array, defaults tonullwhen omitted, and throws on an invalid value;generateAutolinkingConfigOrFailClosedreturns the result on success (exit code untouched), passesprojectRoot/configCommandthrough, and on a config-command error returnsnull, sets exit 2, and logs an actionable error that names the env var and preserves the underlying cause.prettierandeslintclean on all changed files.