Skip to content

Commit e99f329

Browse files
committed
Add kotlin-tooling-cocoapods-to-spm-migration skill
1 parent 445594d commit e99f329

File tree

6 files changed

+1106
-0
lines changed

6 files changed

+1106
-0
lines changed
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
---
2+
name: kotlin-tooling-cocoapods-to-spm-migration
3+
description: >
4+
Migrate Kotlin Multiplatform (KMP) projects from CocoaPods integration to
5+
Swift Package Manager import. Covers swiftPMDependencies adoption,
6+
CocoaPods cleanup, Kotlin import rewrites, Xcode reconfiguration, and common
7+
interoperability issues such as bundled klibs and Firebase or Google SDK
8+
migration. Use when replacing kotlin("native.cocoapods"), migrating pod()
9+
declarations, or updating imports from cocoapods.* to swiftPMImport.*.
10+
license: Apache-2.0
11+
metadata:
12+
author: JetBrains
13+
version: "1.0.0"
14+
---
15+
16+
# CocoaPods to SwiftPM Migration for KMP
17+
18+
Migrate Kotlin Multiplatform projects from `kotlin("native.cocoapods")` to
19+
`swiftPMDependencies {}`.
20+
21+
## Requirements
22+
23+
- **Kotlin**: Version with Swift Import support (for example `2.4.0-Beta1` or later)
24+
- **Xcode**: `16.4` or `26.0+`
25+
- **iOS deployment target**: `16.0+` recommended
26+
27+
## Migration Overview
28+
29+
**Important:** Keep the `cocoapods {}` block and plugin active until Phase 6.
30+
The migration adds `swiftPMDependencies {}` alongside the existing CocoaPods
31+
setup first, reconfigures Xcode, and only then removes CocoaPods.
32+
33+
| Phase | Action |
34+
| --- | --- |
35+
| 1 | Analyze the existing CocoaPods configuration |
36+
| 2 | Update Gradle configuration (repositories, Kotlin version) |
37+
| 3 | Add `swiftPMDependencies {}` alongside `cocoapods {}` |
38+
| 4 | Transform Kotlin imports |
39+
| 5 | Reconfigure the iOS project and deintegrate CocoaPods |
40+
| 6 | Remove CocoaPods plugin and Gradle configuration |
41+
| 7 | Verify Gradle and Xcode builds |
42+
| 8 | Write `MIGRATION_REPORT.md` |
43+
44+
## Phase 1: Pre-Migration Analysis
45+
46+
### 1.0 Verify the project builds
47+
48+
Before changing anything, identify the module that uses CocoaPods and confirm it
49+
builds successfully.
50+
51+
1. Find `build.gradle.kts` files containing `cocoapods`.
52+
2. Extract the KMP module name from the path.
53+
3. Build only that module with `./gradlew :moduleName:build`.
54+
4. If the targeted build fails, ask the user either:
55+
- to provide the correct build command, or
56+
- to confirm the module is in a working state and it is safe to proceed.
57+
58+
If the user confirms without a successful pre-migration build, record that
59+
verification could not be completed and call it out again in Phase 7.
60+
61+
### 1.0a Confirm Kotlin version with Swift Import support
62+
63+
Ask the user whether the project already uses a Kotlin version with
64+
`swiftPMDependencies` support.
65+
66+
- If **yes**, read the current Kotlin version and skip Phase 2.2.
67+
- If **no**, ask for:
68+
- the target Kotlin version, and
69+
- whether that version requires a custom Maven repository.
70+
71+
Compare the current and target **major.minor** Kotlin versions. If the change is
72+
large, warn that the version jump may introduce unrelated breaking changes and
73+
recommend updating Kotlin first, verifying the build, and then re-running the
74+
migration.
75+
76+
### 1.1 Check for deprecated CocoaPods workaround property
77+
78+
Search `gradle.properties` for:
79+
80+
```properties
81+
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
82+
```
83+
84+
Record whether it exists. After migration, this property is no longer needed and
85+
must be removed in Phase 6.
86+
87+
### 1.2 Check for EmbedAndSign disablers
88+
89+
Search all `build.gradle.kts` files for logic that disables `EmbedAndSign`
90+
tasks, such as `taskGraph.whenReady` filters or `tasks.matching` blocks.
91+
These CocoaPods-era workarounds will break `integrateEmbedAndSign` and must be
92+
removed before Phase 5 succeeds.
93+
94+
See [references/troubleshooting.md](references/troubleshooting.md).
95+
96+
### 1.3 Check for third-party KMP libraries with bundled cinterop klibs
97+
98+
Some KMP libraries ship pre-built cinterop klibs that use the
99+
`cocoapods.*` namespace. Those imports must be preserved after migration,
100+
because the bindings come from the library's bundled klib rather than actual
101+
CocoaPods infrastructure.
102+
103+
Known example:
104+
105+
| Library | Maven artifact | Bundled namespace |
106+
| --- | --- | --- |
107+
| KMPNotifier | `io.github.mirzemehdi:kmpnotifier` | `cocoapods.FirebaseMessaging` |
108+
109+
To detect this:
110+
111+
1. Search Gradle dependencies for known KMP wrapper libraries.
112+
2. Find all `import cocoapods.*` statements in Kotlin sources.
113+
3. Cross-reference the imports against the bundled namespaces provided by those
114+
libraries.
115+
4. Mark the imports that must stay unchanged in Phase 4.
116+
117+
If needed, inspect downloaded klibs with `klib dump-metadata-signatures`.
118+
119+
### Record these findings
120+
121+
1. CocoaPods configuration blocks.
122+
2. Pod names, versions, and `linkOnly` flags.
123+
3. `cocoapods.framework {}` settings such as `baseName`, `isStatic`, and
124+
deployment target.
125+
4. All `cocoapods.*` imports and whether they should be migrated or preserved.
126+
5. The iOS project directory containing `Podfile` and `.xcworkspace`.
127+
6. Whether the project uses non-KMP CocoaPods in the iOS app.
128+
7. Whether the Xcode project has commented-out or broken `embedAndSign` phases.
129+
8. Whether Crashlytics dSYM upload scripts point at CocoaPods paths.
130+
9. Any extra CocoaPods-specific build logic. See
131+
[references/cocoapods-extras-patterns.md](references/cocoapods-extras-patterns.md).
132+
133+
## Phase 2: Gradle Configuration
134+
135+
Do **not** upgrade Gradle, KSP, or unrelated dependencies during this migration.
136+
Only make the changes needed for SwiftPM import support.
137+
138+
### 2.1 Add custom Maven repository when required
139+
140+
If the chosen Kotlin version needs a custom repository, add it to
141+
`settings.gradle.kts` in both `pluginManagement.repositories` and
142+
`dependencyResolutionManagement.repositories`.
143+
144+
### 2.2 Update Kotlin version when required
145+
146+
If the project is not already on a Kotlin version with Swift Import support,
147+
update the Kotlin version in the version catalog or build scripts to the value
148+
confirmed in Phase 1.0a.
149+
150+
### 2.3 Add buildscript constraint if `swiftPMDependencies` is unresolved
151+
152+
```kotlin
153+
buildscript {
154+
dependencies.constraints {
155+
"classpath"("org.jetbrains.kotlin:kotlin-gradle-plugin:<kotlin-version>!!")
156+
}
157+
}
158+
```
159+
160+
## Phase 3: Add `swiftPMDependencies` While Keeping CocoaPods
161+
162+
Do **not** remove the `cocoapods {}` block or `kotlin("native.cocoapods")`
163+
plugin yet.
164+
165+
### 3.1 Add `group`
166+
167+
Ensure the module sets a stable `group`, because the generated Kotlin import
168+
namespace depends on it.
169+
170+
```kotlin
171+
group = "org.example.myproject"
172+
```
173+
174+
### 3.2 Add `swiftPMDependencies`
175+
176+
Add a `swiftPMDependencies {}` block that mirrors the existing pod usage.
177+
Use [references/common-pods-mapping.md](references/common-pods-mapping.md) to
178+
map each pod to:
179+
180+
- the Swift package repository URL,
181+
- the SPM products to link, and
182+
- the `importedModules` list when the Clang module name differs from the
183+
product name.
184+
185+
Key rules:
186+
187+
- `products` control linking.
188+
- `importedModules` control cinterop generation when
189+
`discoverModulesImplicitly = false`.
190+
- Do not split a shared library suite across CocoaPods and SPM. Migrate the
191+
full suite together, especially Firebase.
192+
193+
### 3.3 Move framework configuration out of `cocoapods`
194+
195+
If the project configures `framework {}` inside `cocoapods`, move that
196+
configuration to `binaries.framework {}` on the iOS targets.
197+
198+
Prefer `isStatic = true`. It is strongly recommended in general and required for
199+
some CocoaPods-era KMP wrapper libraries such as `dev.gitlive:firebase-*`.
200+
201+
### 3.4 Handle wrapper libraries such as `dev.gitlive:firebase-*`
202+
203+
If the project uses wrapper libraries with CocoaPods-era linker metadata:
204+
205+
1. Switch the framework to `isStatic = true`.
206+
2. Add framework search paths or `linkerOpts` as needed.
207+
3. Re-run `integrateLinkagePackage` after product changes.
208+
209+
See [references/common-pods-mapping.md](references/common-pods-mapping.md) and
210+
[references/troubleshooting.md](references/troubleshooting.md).
211+
212+
### 3.5 Add language settings
213+
214+
```kotlin
215+
sourceSets.configureEach {
216+
languageSettings {
217+
optIn("kotlinx.cinterop.ExperimentalForeignApi")
218+
}
219+
}
220+
```
221+
222+
For DSL details, see [references/dsl-reference.md](references/dsl-reference.md).
223+
224+
## Phase 4: Kotlin Source Updates
225+
226+
### Import namespace formula
227+
228+
```
229+
swiftPMImport.<group>.<module>.<ClassName>
230+
```
231+
232+
Where:
233+
234+
- `group` comes from `build.gradle.kts`, with `-` converted to `.`
235+
- `module` comes from the Gradle module name, with `-` converted to `.`
236+
- `ClassName` is the Objective-C class name
237+
238+
### Example
239+
240+
```kotlin
241+
// BEFORE
242+
import cocoapods.FirebaseAnalytics.FIRAnalytics
243+
244+
// AFTER
245+
import swiftPMImport.org.example.myproject.shared.FIRAnalytics
246+
```
247+
248+
### Preserve bundled klib imports
249+
250+
Do **not** replace `cocoapods.*` imports that come from bundled third-party
251+
klibs identified in Phase 1.3. Those imports should stay unchanged.
252+
253+
### Bulk replacement approach
254+
255+
Use a scoped regex replacement across Kotlin source files, excluding preserved
256+
imports:
257+
258+
```text
259+
Find: cocoapods\.\w+\.
260+
Replace: swiftPMImport.<group>.<module>.
261+
```
262+
263+
Then manually restore any imports that should remain `cocoapods.*`.
264+
265+
## Phase 5: iOS Project Reconfiguration
266+
267+
### 5.1 Run the integration tasks
268+
269+
Build the iOS workspace to capture the Gradle integration command. Then run the
270+
project-specific `integrateEmbedAndSign` and `integrateLinkagePackage` tasks.
271+
272+
After running them:
273+
274+
1. Verify `embedAndSignAppleFrameworkForXcode` is active in the Xcode project.
275+
2. Disable `ENABLE_USER_SCRIPT_SANDBOXING`.
276+
3. Restart the Gradle daemon with `./gradlew --stop`.
277+
278+
If `integrateEmbedAndSign` is skipped or the build phase remains commented out,
279+
check again for EmbedAndSign disablers and see
280+
[references/troubleshooting.md](references/troubleshooting.md).
281+
282+
### 5.2 Update Crashlytics scripts when needed
283+
284+
If the iOS app uses Crashlytics, update dSYM upload scripts from CocoaPods paths
285+
to the SPM checkout path.
286+
287+
### 5.3 Deintegrate CocoaPods
288+
289+
Choose one of these paths:
290+
291+
- **Option A:** Full deintegration when CocoaPods was only used for the KMP
292+
framework.
293+
- **Option B:** Partial removal when non-KMP CocoaPods remain in the iOS app.
294+
295+
Do not guess. Base the cleanup path on the findings from Phase 1.
296+
297+
### 5.4 Manual fallback
298+
299+
If automatic integration fails, use the manual steps from
300+
[references/troubleshooting.md](references/troubleshooting.md).
301+
302+
## Phase 6: Remove CocoaPods from Gradle
303+
304+
Only after Xcode has been reconfigured successfully:
305+
306+
1. Remove `kotlin("native.cocoapods")` from `plugins`.
307+
2. Delete the `cocoapods {}` block.
308+
3. Remove deprecated properties from `gradle.properties`.
309+
4. Remove obsolete CocoaPods-specific tasks and build logic.
310+
311+
For cleanup patterns, see
312+
[references/cocoapods-extras-patterns.md](references/cocoapods-extras-patterns.md).
313+
314+
## Phase 7: Verification
315+
316+
Run the migrated module build:
317+
318+
```bash
319+
./gradlew :moduleName:build
320+
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm64
321+
```
322+
323+
Then build the iOS app with `xcodebuild`, using:
324+
325+
- `.xcodeproj` if all CocoaPods were removed, or
326+
- `.xcworkspace` if non-KMP CocoaPods remain.
327+
328+
If the pre-migration build was not verified, explicitly warn that current build
329+
failures may include pre-existing issues.
330+
331+
Do **not** revert the migration as a first response. Read the failure logs,
332+
re-check the migration phases, and use the troubleshooting guide.
333+
334+
## Phase 8: Migration Report
335+
336+
Write `MIGRATION_REPORT.md` in the project root using
337+
[references/migration-report-template.md](references/migration-report-template.md).
338+
339+
The report must cover:
340+
341+
1. Pre-migration state and project-specific anomalies.
342+
2. Exact changes made in each migration phase.
343+
3. Every import transformation, including preserved `cocoapods.*` imports.
344+
4. Errors encountered, root causes, and fixes.
345+
5. Non-trivial decisions such as `isStatic`, preserved imports, and framework
346+
search paths.
347+
6. A complete file list of created, modified, and deleted files.
348+
349+
## Additional Resources
350+
351+
- [references/dsl-reference.md](references/dsl-reference.md)
352+
- [references/common-pods-mapping.md](references/common-pods-mapping.md)
353+
- [references/cocoapods-extras-patterns.md](references/cocoapods-extras-patterns.md)
354+
- [references/troubleshooting.md](references/troubleshooting.md)
355+
- [references/migration-report-template.md](references/migration-report-template.md)

0 commit comments

Comments
 (0)