Repository navigation
Conversation
Converting an app to SwiftPM with `--deintegrate` now moves the Podfile to Podfile.bak instead of deleting React Native lines from it (that edit left invalid Ruby for multi-line calls). `spm deinit` restores the Podfile, undoes the .gitignore lines the conversion wrote, removes empty ReactNativeSPM folders, and says to run `pod install`. The conversion refuses, before changing anything, when the Podfile has pods that are not React Native or when Podfile.bak already exists.
The Podfile is moved to Podfile.bak, so no pod is lost and `spm deinit` restores it. An explicit `--deintegrate` now lists the other pods, says CocoaPods will not install them while the app uses SwiftPM unless SwiftPM provides them, and continues. RNTester needs this: its Podfile has local example pods that SwiftPM provides. The automatic conversion of a new app still skips such Podfiles.
b75a2b8 to
dda80a2
Compare
cipolleschi
left a comment
There was a problem hiding this comment.
Thanks for this!
I'd changes some bits, to simplify the feature... See my comments!
| ``` | ||
|
|
||
| On a fresh CocoaPods app, this converts the app to SwiftPM. It runs | ||
| `pod deintegrate` and moves the `Podfile` to `Podfile.bak`. A Podfile with pods |
There was a problem hiding this comment.
Podfile can remain Podfile. Why do we need to change it?
There was a problem hiding this comment.
Done. The Podfile now stays as it is.
| `pod deintegrate` and moves the `Podfile` to `Podfile.bak`. A Podfile with pods | ||
| that are not part of React Native is not converted automatically; | ||
| `spm add --deintegrate` converts it and warns that those pods are not installed. | ||
| Without a Podfile, the CLI needs `@react-native-community/cli` 21 or newer to |
There was a problem hiding this comment.
if we keep it, we can lift this requirement
There was a problem hiding this comment.
Done. The CLI 21 requirement is gone, because the Podfile stays in place.
| 2. moves the `Podfile` to `Podfile.bak`, unchanged. `spm deinit` moves it back. | ||
| If `Podfile.bak` already exists, the command stops before it changes | ||
| anything. |
There was a problem hiding this comment.
I would not do this step.
There was a problem hiding this comment.
Done. The step is removed.
| Non-RN pods can stay side-by-side. Your Podfile still lists them. Remove the | ||
| leftover React Native lines (see step 2 above), then re-integrate the pods with | ||
| a normal install: | ||
| `--deintegrate` converts an app whose Podfile has pods that are not part of |
There was a problem hiding this comment.
it might be confusing that we have two different behaviors for spm --deintegrate and spm.
I understand that we would like to remove a step (manually calling --deintegrate) but perhaps it is not a bad thing that the user has to call it themselves.
There was a problem hiding this comment.
Done. Plain spm no longer runs --deintegrate. On a CocoaPods app it stops and points to spm add --deintegrate.
| [local native module](#local-native-modules). The pods stay in `Podfile.bak`, | ||
| and `spm deinit` restores the Podfile. | ||
|
|
||
| To keep a CocoaPods-only pod next to SwiftPM, convert by hand: |
There was a problem hiding this comment.
are there Cocoapods-only pods? I believe that most Native iOS libraries nowadays supports Swift PM. For React Native packages, we should strive to auto-convert them or to fail. If the users have a pod that still needs to be converted to SwiftPM and that can't be converted automatically, I would probably fail and ask them to open a PR/Issue to convert the library.
There was a problem hiding this comment.
I kept this a warning, as in my top-level comment (point 4). The Podfile now stays unchanged, so spm deinit and then pod install bring those pods back. An error would also block RNTester: its Podfile has local pods that SwiftPM provides through spmModules.
| return true; | ||
| } | ||
|
|
||
| function restorePodfile(appRoot /*: string */) /*: void */ { |
There was a problem hiding this comment.
The move isn't recorded in the marker (unlike the .gitignore lines), so deinit renames any Podfile.bak to Podfile, including one the user made themselves. This goes away if we keep the Podfile in place.
There was a problem hiding this comment.
Gone. The Podfile.bak code is removed.
| return false; | ||
| } | ||
| if (podfileHasThirdPartyPods(appRoot)) { | ||
| if (podfileHasThirdPartyPods(appRoot) || hasPodfileBackup(appRoot)) { |
There was a problem hiding this comment.
When Podfile.bak exists, this skips without saying why. The fail-fast check then suggests spm add --deintegrate, which refuses for the same reason. Could we log the reason here?
If we get rid of the Podfile.bak, we can simplify the code significantly
There was a problem hiding this comment.
Gone. The Podfile.bak code is removed.
| function podfileHasThirdPartyPods(appRoot /*: string */) /*: boolean */ { | ||
| // The names of the pods the Podfile declares explicitly (`pod '...'` or | ||
| // `pod('...')`), deduplicated, in order. React Native itself comes in through | ||
| // use_react_native!, so every name here is a pod that is not part of it. |
There was a problem hiding this comment.
Older or brownfield Podfiles can list RN pods explicitly (e.g. pod 'React-Core', :path => '../node_modules/react-native/'), so this would report them as non-RN. Pods from test targets also end up in the list.
There was a problem hiding this comment.
Done. Pods with :path or :podspec into node_modules/react-native now count as React Native pods. Pods in test targets stay in the list: to find them, we would need to parse the Ruby target blocks.
| const podNames = podfilePodNames(appRoot); | ||
| if (podNames.length > 0) { | ||
| logError( | ||
| 'Your Podfile declares pods that are not part of React Native: ' + |
There was a problem hiding this comment.
Nit: this is a warning, but it goes through logError and the run continues. If we decide to fail here instead (see my comment in the docs), this is the place to do it.
There was a problem hiding this comment.
Done. It is now printed as a warning.
| function runDeintegrate(appRoot /*: string */) /*: void */ { | ||
| const podfilePath = path.join(appRoot, 'Podfile'); | ||
| const backupPath = path.join(appRoot, 'Podfile.bak'); | ||
| if (refuseToOverwritePodfileBackup(appRoot)) { |
There was a problem hiding this comment.
Nit: main already runs this check before we get here, so this one never fires on that path. Is it only meant for direct callers?
There was a problem hiding this comment.
Gone. This check was part of the Podfile.bak code.
|
Thanks for the review, Riccardo! I agree with most of it. My plan:
I will push these as new commits. |
Review feedback: `spm add --deintegrate` no longer moves the Podfile to Podfile.bak. It runs `pod deintegrate` and keeps the Podfile unchanged, so `spm deinit` and then `pod install` go back to CocoaPods. - Plain `spm` no longer converts a CocoaPods app; it points to `spm add --deintegrate`. - The React Native CocoaPods scripts stop `pod install` before any write when the .xcodeproj next to the Podfile has .spm-injected.json. Otherwise `pod install` rewrites the generated Package.swift with a wrong path and Xcode fails to resolve packages before any build phase runs. - The uncommitted-changes prompt comes before `pod deintegrate`. - .gitignore is written only after a successful injection. - Pods sourced from node_modules/react-native count as React Native pods, and the other-pods message is a warning. - The SwiftPM iOS workflows check for `Pods-` references in the pbxproj instead of the Podfile content.
spm deinit take an app back to CocoaPodsspm deinit can take an app back to CocoaPods
spm deinit can take an app back to CocoaPodsspm deinit take an app back to CocoaPods
|
Pushed the changes in 0640f08. One change from my earlier plan (point 1): a mistaken |
| for (const line of fs.readFileSync(podfilePath, 'utf8').split('\n')) { | ||
| // `#{` is Ruby string interpolation, not a comment. A line that ends in | ||
| // `,` continues on the next one. | ||
| const code = continued + line.split(/#(?!\{)/)[0].trimEnd(); |
There was a problem hiding this comment.
Small edge case: continued ends with a space, so if a blank or comment-only line sits in the middle of a multi-line pod call, code.endsWith(',') is false and the statement ends before its :path. For example:
pod 'React-Core',
# comment
:path => '../node_modules/react-native/'This gets reported as a non-RN pod. Trimming the whole code string should fix it.
There was a problem hiding this comment.
Good catch, fixed in dae2c23. The whole joined line is trimmed now, so a blank or comment line inside a pod call no longer ends it. I added a test with both cases.
| ); | ||
| } | ||
| if (fs.existsSync(path.join(appRoot, 'Podfile'))) { | ||
| log('To go back to CocoaPods, run `pod install`.'); |
There was a problem hiding this comment.
Nit: this is printed even when deinit found nothing to remove. Also, when the command was redirected from the JS root, it might help to say where to run it (e.g. cd ios && pod install, or displayPath(appRoot)).
There was a problem hiding this comment.
Fixed in dae2c23. The line is printed only when deinit removed the SwiftPM setup. When the command was run from the JS root, it says cd ios && pod install. Tests added for both.
- A multi-line `pod` call with a blank or comment line in the middle no longer ends early, so a pod sourced from node_modules/react-native is not reported as a non-React Native pod. - `spm deinit` says to run `pod install` only when it removed the SwiftPM setup, and says `cd ios && pod install` when run from the JS root.



Summary:
SwiftPM is planned to become the default iOS setup, with CocoaPods as the opt-out. An app that wants CocoaPods then runs
npx react-native spm deinit. This PR does not change the templates or the CLI. It makesspm deinitable to take an app fully back to CocoaPods, which the plan needs.Today an app that was converted to SwiftPM cannot fully go back to CocoaPods:
spm add --deintegrate, also run by plainnpx react-native spmon a new app) deletes each Podfile line that names a React Native call. A call that spans several lines, such asuse_react_native!(…), loses only its first line. The Podfile is then invalid Ruby.spm deinitremoves SwiftPM from the Xcode project, but it does not touch the Podfile.This PR keeps the Podfile unchanged, so
npx react-native spm deinitand thenpod installgo back to CocoaPods:spm add --deintegraterunspod deintegrateand does not change the Podfile. The line editing is removed.npx react-native spmno longer converts a CocoaPods app. It stops before it changes anything and points tospm add --deintegrate.pod installstops on an app that uses SwiftPM. The React Native CocoaPods scripts check for the SwiftPM marker file (.spm-injected.json) in the.xcodeprojnext to the Podfile. If it is there,pod installstops with an error before it writes any file. Without this check,pod installwould add CocoaPods back to the Xcode project and rewrite the generatedPackage.swiftwith a wrong path, and Xcode would fail to resolve packages..xcodeprojhas uncommitted changes, the conversion asks beforepod deintegrate. If the answer is no, nothing changes.react-nativepackage (:pathor:podspecintonode_modules/react-native) are not listed. The Podfile is kept, sospm deinitand thenpod installbring those pods back.spm deinitundoes the.gitignorelines the conversion wrote. The conversion writes.gitignoreonly after a successful injection and records the lines in the marker file, sodeinitremoves only them.spm deinitremoves theReactNativeSPMfolders that are empty after it deletes its files, and prints "To go back to CocoaPods, runpod install."The three SwiftPM iOS workflows checked that the Podfile has no
use_react_native!after the conversion. The Podfile is now kept, so they check that the.xcodeprojhas noPods-references instead.The docs in
scripts/spm/__docs__describe the way back and thepod installerror. They also say that the conversion removes the app target's ownREACT_NATIVE_PATH, anddeinitdoes not restore it.pod installsets it again throughreact_native_post_install. Only a Podfile without that call needs a manual check.Changelog:
[IOS] [CHANGED] -
react-native spm add --deintegratekeeps the Podfile, plainreact-native spmno longer converts a CocoaPods app, andpod installstops with an error on an app that uses SwiftPM.react-native spm deinitand thenpod installtake an app back to CocoaPods.Test Plan:
Unit tests:
25 suites, 1233 tests pass. ESLint and Prettier are clean on the changed files, and
ruby -cpasses on the three changed Ruby files.The Ruby scripts have no unit tests in this repo. I checked the
pod installerror on a real app instead (step 3 below).A new app outside the monorepo, on React Native 0.89.0-nightly-20261021-c7429b0e9 with
@react-native-community/cli21.0.0-alpha.1. The changed scripts were copied intonode_modules/react-native/scripts.npx react-native spm add --deintegrate --yesconverts the app. The Podfile is unchanged.pod installstops: "This app uses Swift Package Manager for React Native (SpmOptOut.xcodeproj has .spm-injected.json), sopod installwould break the Xcode project." NoPods/folder is created.Podfile.lock,Package.swiftand the git status ofios/are unchanged.npx react-native run-iosandnpx react-native build-iosbuild and run the app. The CLI sees the marker file and does not runpod install.npx react-native spm deinitremoves the marker. The Podfile is unchanged.cd ios && pod installsucceeds, and the CocoaPods build succeeds.Not checked:
.flowconfigrequires, so it did not type-check.run-ios --force-podson a converted app. In the CLI source, this flag runspod installanyway, which then stops with the error from step 3..xcworkspacestays (as before this PR). The CLI builds it, and the build succeeds.