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