Kotlin Tooling Cocoapods Spm Migration
JetBrainsOfficialCreated by JetBrainsDistributed by JetBrains
- Skill
- Workflow
- Software development
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, multi-phase procedure for migrating Kotlin Multiplatform (KMP) projects from the legacy CocoaPods dependency manager to the modern Swift Package Manager (SwiftPM). It guides users through analyzing existing configurations, updating Gradle files, mapping dependencies, transforming Kotlin imports, reconfiguring the Xcode project, and cleaning up legacy CocoaPods artifacts.
What it does
- Analyzes existing CocoaPods configurations and identifies necessary migration steps.
- Updates Gradle build files to include the swiftPMDependencies DSL.
- Maps CocoaPods dependencies to their SwiftPM equivalents using a provided reference.
- Automates the transformation of Kotlin import statements from cocoapods.* to swiftPMImport.*.
- Reconfigures Xcode projects to support SwiftPM and removes legacy CocoaPods integration.
- Generates a migration report to track changes and verify build integrity.
When to use it
- When moving a KMP project away from the deprecated native.cocoapods plugin.
- When adopting SwiftPM for better integration with modern Xcode versions.
- When needing to resolve dependency conflicts caused by mixing CocoaPods and SwiftPM.
- When upgrading KMP projects to leverage newer Kotlin versions with Swift Import support.
Advantages
- Reduces reliance on the deprecated CocoaPods plugin in KMP.
- Provides a clear, phase-based roadmap to minimize build breakage.
- Includes specific guidance for complex dependencies like the Firebase suite.
- Ensures clean project state by removing legacy build artifacts and workarounds.
Usage examples
- “I need to migrate my KMP shared module from CocoaPods to SwiftPM. Where do I start?”
- The agent will initiate Phase 1, guiding you to identify your module, verify its current build status, and confirm your Kotlin version supports SwiftPM.
- “How do I handle my Firebase dependencies during the migration?”
- The agent will reference the common-pods-mapping to ensure all Firebase products are moved to SwiftPM simultaneously to avoid runtime dyld crashes.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/jetbrains/kotlin-tooling-cocoapods-spm-migration/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip kotlin-tooling-cocoapods-spm-migration.zip -d .agents/skills
Documentation by JetBrains
CocoaPods to SwiftPM Migration for KMP
Migrate Kotlin Multiplatform projects from kotlin("native.cocoapods") to swiftPMDependencies {} DSL.
Requirements
Kotlin: Version with Swift Import support (e.g., 2.4.0-Beta1 or later)
Xcode: 16.4 or 26.0+
iOS Deployment Target: 16.0+ recommended
Migration Overview
IMPORTANT: Keep the cocoapods {} block and plugin active until Phase 6. The migration adds swiftPMDependencies {} alongside the existing CocoaPods setup first, reconfigures Xcode, and only then removes CocoaPods.
| Phase | Action |
|---|---|
| 1 | Analyze existing CocoaPods configuration |
| 2 | Update Gradle configuration (repos, Kotlin version) |
| 3 | Add swiftPMDependencies {} alongside existing cocoapods {} |
| 4 | Transform Kotlin imports |
| 5 | Reconfigure iOS project and deintegrate CocoaPods |
| 6 | Remove CocoaPods plugin from Gradle |
| 7 | Verify Gradle build and Xcode project build |
| 8 | Write MIGRATION_REPORT.md |
Phase 1: Pre-Migration Analysis
1.0 Verify the project builds
Before starting migration, identify the module to migrate and confirm it compiles successfully.
Find the module that uses CocoaPods — look for
build.gradle.ktsfiles containingcocoapods:grep -rl "cocoapods" --include="build.gradle.kts" .Extract the module name from the path (e.g.,
./shared/build.gradle.kts→ module name isshared).Build only that module (avoids building the entire multi-module project):
./gradlew :moduleName:buildReplace
moduleNamewith the directory name of the module (e.g.,:shared:build).If the targeted build fails, ask the user to either:
Provide the correct Gradle command to verify the module builds, or
Confirm the module is in a working state and it's safe to proceed
If the user confirms without providing a build command, record that the pre-migration build could not be verified and warn about this at the end of migration (Phase 7).
1.0a Confirm Kotlin version with Swift Import support
Ask the user:
Does your project already use a Kotlin version with Swift Import support (swiftPMDependencies DSL)?
If yes → read their current Kotlin version from gradle/libs.versions.toml (or build.gradle.kts), record it, and skip Phase 2.2 (no version change needed).
If no → ask:
Please provide the Kotlin version to use (e.g., "2.4.0", "2.4.0-Beta1", "2.4.0-dev-123").
Record the user-provided version. Then ask:
Does this Kotlin version require a custom Maven repository (e.g., JetBrains dev repo)?
Yes → ask for the repo URL (suggest
https://packages.jetbrains.team/maven/p/kt/devas default). Phase 2.1 will add it.No → Phase 2.1 is skipped (no custom repo needed).
Finally, check the project's current Kotlin version. Compare major.minor against the target. If it differs significantly (e.g., 2.1.0 → 2.4.0), warn: "⚠️ Kotlin version jump — upgrading across minor versions can introduce breaking changes unrelated to this migration. Recommended: update first, verify it builds, then re-run." If the user confirms despite the mismatch, proceed.
1.1 Check for deprecated CocoaPods workaround property
Search gradle.properties for the deprecated property:
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=trueThis property was a workaround (see KT-64096) for projects using embedAndSign alongside CocoaPods dependencies. It suppresses an error about unsupported configurations that can cause runtime crashes or symbol duplication. After migrating to SwiftPM import, this property is no longer needed and must be removed in Phase 6. Record its presence if found.
1.2 Check for EmbedAndSign disablers
Search all build.gradle.kts files for code that disables EmbedAndSign tasks (e.g., TaskGraph.whenReady filters, tasks.matching blocks). This is a CocoaPods-era workaround that breaks the migration because integrateEmbedAndSign (needed in Phase 5) gets disabled too. Record any such code — it must be removed in Phase 6, and may need to be removed earlier. See troubleshooting.md § "integrateEmbedAndSign Skipped" for patterns.
1.3 Check for third-party KMP libraries with bundled cinterop klibs
Some KMP libraries ship pre-built cinterop klibs with cocoapods.* package namespaces. After migration, the swiftPMDependencies cinterop generator detects these existing bindings and skips generating new bindings for those Clang modules to avoid duplicates. This means cocoapods.* imports for those modules must be kept as-is — they resolve to the third-party library's bundled klib, not to actual CocoaPods.
Known libraries with bundled cocoapods.* klibs:
| Library | Maven artifact | Bundled klib namespace | Classes provided |
|---|---|---|---|
| KMPNotifier | io.github.mirzemehdi:kmpnotifier | cocoapods.FirebaseMessaging | FIRMessaging, FIRMessagingAPNSTokenType, etc. |
How to detect: Search Gradle dependency declarations for known libraries, then cross-reference their bundled namespaces against the import cocoapods.* statements found in step 4. Mark any matches — these imports will NOT be transformed in Phase 4.
If unsure whether a third-party KMP library bundles cinterop klibs, check if it has a linkOnly = true pod dependency in the project — this is a strong indicator that the library provides its own klib for those classes.
To inspect klib contents and verify bundled bindings, see troubleshooting.md § "Third-Party KMP Libraries with Bundled Klibs".
Find and record:
CocoaPods configuration - Search for
cocoapodsinbuild.gradle.ktsfilesPod dependencies - Extract pod names, versions from
cocoapods {}blocksFramework configuration - Record
baseName,isStatic, deployment target fromcocoapods.framework {}linkOnly pods - Record pods declared with
linkOnly = true. These pods provide native linking only — cinterop bindings come from a KMP wrapper library (e.g.,dev.gitlive:firebase-*). See common-pods-mapping.md for implications.Kotlin imports - Find all
import cocoapods.*statements. Cross-reference with step 1.3 to identify which imports come from bundled klibs (and must be preserved) vs. which come from direct pod cinterop (and must be transformed).Map pods to SPM - See common-pods-mapping.md
Locate iOS project directory - Find the directory containing
Podfileand.xcworkspace:find . -name "Podfile" -type fRecord this path (e.g.,
iosApp/,ios/, or project root) - needed for Phase 5Check for non-KMP CocoaPods - Determine if the project uses CocoaPods for dependencies other than KMP. This affects cleanup strategy in Phase 5.
Check Xcode build phases - Open the
.xcodeproj'sproject.pbxprojand search for the Gradle build phase script. Check ifembedAndSignAppleFrameworkForXcodeis present but commented out (prefixed with#). If commented out, it must be uncommented during Phase 5 — theintegrateEmbedAndSigntask may or may not handle this automatically.Check for existing Crashlytics dSYM upload script - If using FirebaseCrashlytics, search
project.pbxprojfor a dSYM upload shell script phase. Record its current path (CocoaPods-era scripts reference${PODS_ROOT}/FirebaseCrashlytics/upload-symbols). This must be updated to the SPM path in Phase 5.Identify CocoaPods-related extras in build scripts - Search all
build.gradle.ktsfiles for CocoaPods workarounds beyond the standardcocoapods {}block (custom tasks hooking intopodInstall,Pods.xcodeprojpatching, podspec metadata,extraSpecAttributes,noPodspec(), etc.). See cocoapods-extras-patterns.md for the full pattern list. Record all findings — these will be handled in Phase 6.
Phase 2: Gradle Configuration
Important scope note: Do NOT upgrade the Gradle wrapper version, update KSP, or update any other dependencies during this migration. Those are separate concerns and out of scope. Only change what is listed below.
2.1 Add custom Maven repository (if needed)
Skip this step if the user indicated in Phase 1.0a that their Kotlin version does not require a custom Maven repository (i.e., it is an official release, Beta, or RC available from Maven Central).
For dev/custom builds, add the custom Maven repository (URL from Phase 1.0a) to settings.gradle.kts:
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
maven("<custom-repo-url>") // ADD
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
maven("<custom-repo-url>") // ADD
}
}2.2 Update Kotlin version
Skip this step if the user's project already uses a Kotlin version with Swift Import support (recorded in Phase 1.0a).
Update to the version recorded in Phase 1.0a:
# gradle/libs.versions.toml
[versions]
kotlin = "<kotlin-version>"2.3 Add buildscript constraint (if swiftPMDependencies not recognized)
// root build.gradle.kts
buildscript {
dependencies.constraints {
"classpath"("org.jetbrains.kotlin:kotlin-gradle-plugin:<kotlin-version>!!")
}
}Replace <kotlin-version> with the version recorded in Phase 1.0a. The !! suffix forces strict version resolution, ensuring no other dependency pulls in a different Kotlin Gradle plugin version.
Phase 3: Add swiftPMDependencies (Keep CocoaPods)
Do NOT remove the cocoapods {} block or kotlin("native.cocoapods") plugin yet. Add swiftPMDependencies {} alongside the existing CocoaPods configuration.
3.1 Add group property
group = "org.example.myproject" // Required for import namespace3.2 Add swiftPMDependencies block alongside cocoapods
For each pod dependency, add the equivalent SwiftPM package declaration. Use common-pods-mapping.md to map each pod to its SPM package URL, product name, and importedModules.
Key concepts: products = SPM product names (controls linking). importedModules = Clang module names for cinterop bindings (only when discoverModulesImplicitly = false). discoverModulesImplicitly defaults to true (bindings for all Clang modules); set false when transitive C/C++ modules fail cinterop (Firebase, gRPC), then list needed modules explicitly.
Important: SPM product names and Clang module names don't always match. Always consult common-pods-mapping.md for correct values.
Do not mix the same library suite across CocoaPods and SPM. Libraries that share a common repository (e.g., all Firebase products) share transitive dependencies. Having some products linked via CocoaPods and others via SPM causes duplicate/conflicting symbols and dyld crashes at runtime. When migrating such a suite, move all pods from that suite to SPM at once — including Swift-only pods that Kotlin doesn't use directly. Add Swift-only pods as products entries (no importedModules needed). After adding new products, re-run integrateLinkagePackage to regenerate the linkage Swift package.
kotlin {
// Keep existing targets
iosArm64()
iosSimulatorArm64()
iosX64()
swiftPMDependencies {
iosDeploymentVersion.set("16.0")
// If using KMP IntelliJ plugin, specify the .xcodeproj path:
// xcodeProjectPathForKmpIJPlugin.set(
// layout.projectDirectory.file("../iosApp/iosApp.xcodeproj")
// )
swiftPackage(
url = url("https://github.com/owner/repo.git"),
version = from("1.0.0"),
products = listOf(product("ProductName")),
)
}
cocoapods {
// ... keep existing cocoapods block for now
}
}3.3 Move framework configuration out of cocoapods block
If the cocoapods block contains a framework {} configuration, move it to the binaries API on each target. isStatic = true is recommended — dynamic frameworks have known edge cases with SwiftPM import that can cause linker errors, dyld crashes, or duplicate class warnings:
listOf(iosArm64(), iosSimulatorArm64(), iosX64()).forEach { iosTarget ->
iosTarget.binaries.framework { baseName = "Shared"; isStatic = true }
}3.4 Handle dev.gitlive/firebase-kotlin-sdk and similar CocoaPods-era KMP wrappers
If the project uses dev.gitlive:firebase-* or similar KMP wrapper libraries, two additional steps are required:
A. Switch to isStatic = true — dynamic frameworks + Firebase SPM = runtime dyld crash. After switching: re-run integrateLinkagePackage, remove any "Embed Frameworks" copy phase, move linker flags to OTHER_LDFLAGS.
B. Add framework search paths — add conditional -F linkerOpts in build.gradle.kts and matching FRAMEWORK_SEARCH_PATHS in the Xcode project.
See common-pods-mapping.md § dev.gitlive and troubleshooting.md for code snippets and the full product list.
3.5 Add opt-in for cinterop API
kotlin.compilerOptions {
optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
}For full DSL reference, see dsl-reference.md.
Phase 4: Kotlin Source Updates
Import Namespace Formula
swiftPMImport.<group>.<module>.<ClassName>
Where:
- group: build.gradle.kts `group` property, dashes (-) → dots (.)
- module: Gradle module name, dashes (-) → dots (.)
- ClassName: Objective-C class name (FIR* for Firebase, GMS* for Google Maps)Example Transformation
// group = "org.jetbrains.kotlin.firebase.sample", module = "kotlin-library"
// BEFORE:
import cocoapods.FirebaseAnalytics.FIRAnalytics
// AFTER:
import swiftPMImport.org.jetbrains.kotlin.firebase.sample.kotlin.library.FIRAnalyticsImport flattening: The Clang module name (e.g., FirebaseFirestoreInternal, FirebaseAuth) disappears from the import path — all classes are flattened under the same swiftPMImport.<group>.<module> prefix regardless of which library they come from. For example, both cocoapods.FirebaseAuth.FIRAuth and cocoapods.FirebaseFirestoreInternal.FIRFirestore become swiftPMImport.<group>.<module>.FIRAuth and swiftPMImport.<group>.<module>.FIRFirestore.
Preserving Bundled Klib Imports
CRITICAL: Do NOT replace
cocoapods.*imports that resolve to third-party KMP libraries' bundled cinterop klibs (identified in Phase 1 step 1.3). These imports must remain as-is — thecocoapodsprefix is the package namespace in the library's published klib, not an actual CocoaPods dependency. The swiftPMDependencies cinterop generator skips modules already provided by a dependency's klib, soswiftPMImport.*for those classes will fail with "Unresolved reference".
Example (project using KMPNotifier):
// KEEP — resolves to kmpnotifier's bundled cinterop klib
import cocoapods.FirebaseMessaging.FIRMessagingBulk Replacement
Use a regex find-and-replace across all Kotlin source files, excluding imports identified in Phase 1 step 1.3:
Find: cocoapods\.\w+\.
Replace: swiftPMImport.<your.group>.<your.module>.After bulk replacement, manually restore any cocoapods.* imports that should be preserved (from bundled klibs).
Finding correct import path: Run ./gradlew :moduleName:build - errors show available classes.
Phase 5: iOS Project Reconfiguration
5.1 Get migration command
Build the CocoaPods workspace to obtain the migration command:
cd /path/to/iosApp
xcodebuild -scheme "$(echo -n *.xcworkspace | python3 -c 'import sys, json; from subprocess import check_output; print(list(set(json.loads(check_output(["xcodebuild", "-workspace", sys.stdin.readline(), "-list", "-json"]))["workspace"]["schemes"]) - set(json.loads(check_output(["xcodebuild", "-project", "Pods/Pods.xcodeproj", "-list", "-json"]))["project"]["schemes"]))[0])')" -workspace *.xcworkspace -destination 'generic/platform=iOS Simulator' ARCHS=arm64 | grep -A5 'What went wrong'The build output will contain a command like:
XCODEPROJ_PATH='/path/to/project/iosApp.xcodeproj' GRADLE_PROJECT_PATH=':shared' '/path/to/project/gradlew' -p '/path/to/project' ':shared:integrateEmbedAndSign' ':shared:integrateLinkagePackage'Run this command. It modifies the .xcodeproj to trigger embedAndSignAppleFrameworkForXcode during the build. integrateLinkagePackage is a one-time setup — it does not need to be added as a build phase. If integrateEmbedAndSign is skipped, check for EmbedAndSign disablers (Phase 1 step 1.2) — remove them first, then re-run.
Verify embedAndSignAppleFrameworkForXcode is active: After running integration, check the build phase script in project.pbxproj. If embedAndSignAppleFrameworkForXcode is commented out (prefixed with #), uncomment it.
The integrateLinkagePackage task generates _internal_linkage_SwiftPMImport/ at <iosDir>/ — a local Swift package that mirrors your products list and ensures SPM libraries are linked into the final binary.
After running the integration tasks, disable User Script Sandboxing (ENABLE_USER_SCRIPT_SANDBOXING = NO) in the .xcodeproj. Xcode 16+ enables it by default, which prevents the Gradle build phase from writing to the project directory:
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' "$XCODEPROJ_PATH/project.pbxproj"If the setting is absent (Xcode defaults to YES), add ENABLE_USER_SCRIPT_SANDBOXING = NO; to the app target's buildSettings sections. Then restart the Gradle daemon: ./gradlew --stop
Alternative (if xcodebuild approach fails): See troubleshooting.md § "Manual Integration Command Discovery" for a fallback script to discover paths and run integration tasks directly.
5.2 Update Crashlytics dSYM upload script (if applicable)
If the project uses FirebaseCrashlytics and has a dSYM upload run script phase (identified in Phase 1 step 10), update the script path from ${PODS_ROOT}/FirebaseCrashlytics/upload-symbols to "${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run". See troubleshooting.md § "Firebase Crashlytics: dSYM Upload Script" and common-pods-mapping.md for the full script and input files list.
5.3 Deintegrate CocoaPods
Option A: Full deintegration (if CocoaPods was used ONLY for KMP dependencies):
Before deleting files, run git status --short and verify the paths. If unsure, move files to a backup location instead of deleting immediately.
cd /path/to/iosApp
pod deintegrate
rm -rf Podfile Podfile.lock Pods/
# Remove the workspace that matches your app xcodeproj name
XCODEPROJ_NAME=$(basename "$(find . -maxdepth 1 -name "*.xcodeproj" -type d | grep -v Pods | head -1)" .xcodeproj)
rm -rf "${XCODEPROJ_NAME}.xcworkspace"
# Return to project root
cd ..
# Remove the migrated module podspec only (for example, shared.podspec)
# If unknown, list candidates and remove the matching one explicitly:
ls -1 *.podspec
# rm -f shared.podspecThis cleanup snippet is self-contained and does not assume XCODEPROJ_PATH or GRADLE_PROJECT_PATH from the earlier one-off migration command are still available in your shell.
If pod deintegrate is not available, see troubleshooting.md § "Manual CocoaPods Deintegration from pbxproj" for the full list of references to remove. Also remove Pods/ from .gitignore and delete the .xcworkspace directory.
Option B: Partial removal (if other non-KMP CocoaPods dependencies remain):
Remove only the KMP pod line from the Podfile and re-run pod install:
target 'iosApp' do
# Remove this line:
pod 'shared', :path => '../shared'
# Keep other non-KMP pods
endcd /path/to/iosApp && pod installTip: Consider migrating remaining pods to SPM too — most popular iOS libraries support it natively. Add them in Xcode via File → Add Package Dependencies, then fully deintegrate CocoaPods once all pods are replaced.
5.4 Manual integration (if automatic fails)
See troubleshooting.md § "Manual Xcode Integration Steps" for the 5-step manual setup (build phase, sandboxing, linkage package).
Phase 6: Remove CocoaPods from Gradle
Now that the iOS project is reconfigured, remove the CocoaPods plugin and block:
6.1 Remove CocoaPods plugin
plugins {
// REMOVE: kotlin("native.cocoapods")
alias(libs.plugins.kotlinMultiplatform) // Keep
}6.2 Remove cocoapods block
Delete the entire cocoapods { ... } block from build.gradle.kts. The swiftPMDependencies {} block and binaries.framework {} configuration added in Phase 3 replace it.
6.3 Remove deprecated gradle.properties entries
If found in Phase 1.1, remove from gradle.properties:
# REMOVE — no longer needed after migrating away from CocoaPods (KT-64096)
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true6.4 Clean up CocoaPods-related extras
Review the extras identified in Phase 1 step 11. Podspec metadata, noPodspec(), CocoaPods task hooks, and Pods.xcodeproj patching code are safe to remove without user consultation. Non-standard pod configurations (extraOpts, moduleName), custom cinterop defFile setups, and CocoaPods-specific compiler/linker flags require analysis — consult the user if unsure whether SPM handles them automatically.
See cocoapods-extras-patterns.md for the full categorized list with examples.
Phase 7: Verification
7.1 Build Gradle project
Build the migrated module to verify the migration succeeded:
./gradlew :moduleName:build7.2 Link framework
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm647.3 Build iOS/macOS Xcode project
After the Gradle build succeeds, build the Xcode project. Use -project *.xcodeproj if all CocoaPods were removed (Option A), or -workspace *.xcworkspace if non-KMP CocoaPods remain (Option B):
cd /path/to/iosApp
# Discover schemes and build (replace -project/-workspace as needed; for macOS use -destination 'platform=macOS'):
xcodebuild -project *.xcodeproj -list -json 2>/dev/null | python3 -c "import sys,json; schemes=json.load(sys.stdin)['project']['schemes']; [print(s) for s in schemes]"
xcodebuild -project *.xcodeproj -scheme "<AppScheme>" -destination 'generic/platform=iOS Simulator' ARCHS=arm64 buildIf checkSandboxAndWriteProtection fails — sandboxing was not disabled in Phase 5.1. Go back and apply the sandboxing fix from Phase 5.1, then retry.
If the pre-migration build was not verified (Phase 1.0 fallback was used), warn the user:
Note: The pre-migration build could not be fully verified. If build errors appear now, some may be pre-existing issues unrelated to the migration. Compare errors against the pre-migration build output to distinguish migration issues from prior problems.
If the build fails
Do NOT revert the migration. Read the error log, re-check Phases 2-6, and consult troubleshooting.md. If unsure, present options to the user — do not silently undo migration work.
Phase 8: Migration Report
After migration (whether successful or not), write a comprehensive MIGRATION_REPORT.md in the project root. Use the template in migration-report-template.md.
The report must include:
Pre-Migration State — CocoaPods dependencies (name, version,
linkOnly), framework config,cocoapods.*imports, non-KMP pods, atypical configurationMigration Steps — exact changes per phase with before/after snippets for non-trivial changes
Import Transformations — table of every import change, clearly marking preserved
cocoapods.*imports and which bundled klib provides themErrors Encountered — structured
Error #Nentries: phase, exact symptom, root cause, fix, generalizable flagNon-Trivial Decisions —
isStaticchanges, preserved imports, framework search paths, trade-offsFiles Changed — complete list grouped by type (Gradle, Kotlin, Xcode, created, deleted)
Additional Resources
DSL Reference - Full swiftPMDependencies syntax
Common Pods Mapping - Pod to SPM mapping table
CocoaPods Extras Patterns - Detection and cleanup patterns for CocoaPods workarounds
Troubleshooting - Issues, solutions, rollback
Migration Report Template - Post-migration report template
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSEAdded by AutoGineer10.0 KiB
- SKILL.md25.6 KiB
- references/cocoapods-extras-patterns.md3.9 KiB
- references/common-pods-mapping.md20.9 KiB
- references/dsl-reference.md8.1 KiB
- references/migration-report-template.md4.7 KiB
- references/troubleshooting.md23.0 KiB
references/troubleshooting.md
Troubleshooting Guide
Common issues and solutions when migrating from CocoaPods to SwiftPM.
Gradle Issues
"swiftPMDependencies not found"
Symptom: Unresolved reference to swiftPMDependencies in build.gradle.kts
Solution: Add buildscript constraint to root build.gradle.kts:
buildscript {
dependencies.constraints {
"classpath"("org.jetbrains.kotlin:kotlin-gradle-plugin:<kotlin-version>!!")
}
}Also verify:
Kotlin version matches the version recorded in Phase 1.0a in libs.versions.toml
Custom Maven repo (if needed) is in settings.gradle.kts
Import Not Found After Migration
Symptom: Unresolved reference errors for classes that worked with CocoaPods
Solution: The import namespace follows a specific pattern:
swiftPMImport.<group>.<module>.<ClassName>Steps to fix:
Check
groupproperty in build.gradle.ktsReplace
-with.in both group and module namesRun
./gradlew buildto see available classes in error messages
Example:
// If group = "org.jetbrains.kotlin.firebase-sample" and module = "kotlin-library"
// Import becomes:
import swiftPMImport.org.jetbrains.kotlin.firebase.sample.kotlin.library.FIRAnalytics
// ^ ^ ^
// dashes become dots --------+------+Gradle Sync Fails
Symptom: IDE fails to sync project after adding swiftPMDependencies
Solution:
Invalidate caches: File > Invalidate Caches > Invalidate and Restart
Run
./gradlew --refresh-dependenciesCheck all repository declarations include JetBrains Maven
Linker Issues
Missing Symbols / Linker Errors
Symptom: Undefined symbols for architecture errors
Solutions:
Run the linkage integration task (one-time, not a build phase):
./gradlew :moduleName:integrateLinkagePackageVerify SPM package is linked in Xcode:
Open project in Xcode
Check Package Dependencies section
Ensure
_internal_linkage_SwiftPMImportis present
Check framework configuration —
isStatic = trueis recommended. WhileisStatic = falsecan work, dynamic frameworks have known edge cases with SwiftPM import (linker errors, dyld crashes, duplicate class warnings). It is required withdev.gitlive:firebase-*— see below.
"No such module" in Xcode
Symptom: Xcode can't find the Kotlin module
Solution:
Clean Xcode build folder: Shift+Cmd+K
Re-run integration:
./gradlew :moduleName:integrateLinkagePackageRestart Xcode completely
Re-open the correct Xcode project file (
.xcodeprojif all CocoaPods were removed,.xcworkspaceif non-KMP CocoaPods remain)
Build Phase Issues
Build Phase Order Problems
Symptom: Swift compilation fails because Kotlin framework isn't ready
Solution: Ensure "Compile Kotlin" runs BEFORE "Compile Sources":
Open Xcode project
Select app target > Build Phases
Drag "Compile Kotlin" phase above "Compile Sources"
Script Sandboxing Errors
Symptom: Gradle task checkSandboxAndWriteProtection fails during Xcode build:
Execution failed for task ':moduleName:checkSandboxAndWriteProtection'.
> User Script Sandboxing Enabled in Xcode ProjectOr build scripts can't access files or run Gradle.
Cause: Xcode 16+ enables User Script Sandboxing by default. The Gradle build phase needs to write to the project directory, which sandboxing prevents.
Solution:
Disable via command line:
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' /path/to/iosApp/*.xcodeproj/project.pbxprojIf the setting is not present in the
.pbxproj(Xcode defaults to YES without an explicit entry), open the project in Xcode instead.Or disable in Xcode: select app target → Build Settings → Build Options → set "User Script Sandboxing" to NO
Important: After changing the setting, stop the Gradle daemon:
./gradlew --stop
Integration Task Issues
integrateEmbedAndSign Skipped or Does Nothing
Symptom: Running integrateEmbedAndSign completes without errors but the Xcode project is not modified. The embedAndSignAppleFrameworkForXcode build phase is not added or remains commented out.
Cause: The project has code that disables EmbedAndSign tasks. Common patterns:
// In root or module build.gradle.kts
project.gradle.taskGraph.whenReady {
allTasks.filter { it::class.simpleName?.contains("EmbedAndSign") == true }.forEach {
it.enabled = false
}
}This was a CocoaPods-era workaround that inadvertently disables integrateEmbedAndSign.
Solution: Remove the disabler code from build.gradle.kts, then re-run the integration command.
embedAndSignAppleFrameworkForXcode Commented Out in Build Phase
Symptom: Xcode build succeeds but produces no Kotlin framework. The app crashes at runtime with missing module errors.
Cause: The Gradle invocation in the Xcode build phase script was commented out (prefixed with #) — possibly a pre-existing state from before migration.
Solution: Open project.pbxproj and uncomment the Gradle invocation:
-#./gradlew :moduleName:embedAndSignAppleFrameworkForXcode
+./gradlew :moduleName:embedAndSignAppleFrameworkForXcodeThird-Party KMP Libraries with Bundled Klibs
cocoapods.* Class Not Found After Converting to swiftPMImport.*
Symptom: After replacing import cocoapods.FirebaseMessaging.FIRMessaging with import swiftPMImport.<group>.<module>.FIRMessaging, the build fails with Unresolved reference 'FIRMessaging'. Other swiftPMImport classes (e.g., GIDSignIn) resolve fine.
Cause: A third-party KMP library (e.g., KMPNotifier — io.github.mirzemehdi:kmpnotifier) bundles its own pre-built cinterop klib with the cocoapods.FirebaseMessaging namespace. The swiftPMDependencies cinterop generator detects these existing bindings and deliberately skips generating new bindings for that Clang module to avoid duplicate symbols. The swiftPMImport.* bindings for that module simply don't exist.
Solution: Revert the affected imports back to cocoapods.*:
// These resolve to the third-party library's bundled klib, NOT actual CocoaPods
import cocoapods.FirebaseMessaging.FIRMessaging
import cocoapods.FirebaseMessaging.FIRMessagingAPNSTokenTypeThe cocoapods prefix here is just a package namespace embedded in the library's published artifact — no CocoaPods infrastructure is needed at runtime.
How to identify bundled klibs in advance: Check if the project depends on KMP libraries that wrap iOS SDKs. Known libraries: KMPNotifier (bundles cocoapods.FirebaseMessaging). Also check for linkOnly = true pod declarations — this indicates the pod was only needed for linking while a KMP library provided the actual bindings.
Inspecting klib contents: Use klib dump-metadata-signatures to verify which classes a klib provides (docs):
# Find the klib
find ~/.gradle/caches -name "*.klib" -path "*kmpnotifier*" | head -1
# Dump and search for the class in question
klib dump-metadata-signatures /path/to/cinterop.klib | grep "FIRMessaging"
# Output shows: cocoapods.FirebaseMessaging.FIRMessaging → confirms bundled klibYou can also compare before/after migration by dumping the swiftPMImport klib:
# After build, find the swiftPMImport klib
find . -name "*.klib" -path "*swiftPMImport*" | head -1
# Verify which classes are available
klib dump-metadata-signatures /path/to/swiftPMImport.klib | grep "FIRMessaging"
# Empty output = class NOT in swiftPMImport (must use cocoapods.* import)dev.gitlive/firebase-kotlin-sdk Issues
framework 'FirebaseCore' not found (K/N Linker)
Symptom: Kotlin/Native linker fails with:
ld: framework 'FirebaseCore' not foundor similar errors for FirebaseAuth, FirebaseFirestore, etc. The Gradle compilation succeeds but the link step fails.
Cause: firebase-kotlin-sdk (dev.gitlive:firebase-*) was published with CocoaPods-era cinterop klibs. These klibs have -framework FirebaseCore, -framework FirebaseAuth, etc. baked into their linker metadata. With CocoaPods, those frameworks were in Pods/ on the search path. With SPM, they land in per-product subdirectories ($BUILT_PRODUCTS_DIR/FirebaseCore/FirebaseCore.framework) that the K/N linker doesn't search.
Solution (two-part):
Part A — Gradle linkerOpts:
iosTarget.binaries.framework {
val builtProductsDir = System.getenv("BUILT_PRODUCTS_DIR")
if (builtProductsDir != null) {
listOf(
"FirebaseCore", "FirebaseAuth", "FirebaseCoreExtension",
"FirebaseCoreInternal", "FirebaseCrashlytics", "FirebaseFirestore",
"FirebaseFirestoreInternal", "FirebaseInstallations", "FirebaseMessaging",
"FirebaseStorage", "GoogleDataTransport", "GoogleUtilities",
"GTMSessionFetcher", "AppCheckCore", "AppAuth", "GTMAppAuth",
).forEach { product ->
linkerOpts("-F", "$builtProductsDir/$product")
}
}
}The if (builtProductsDir != null) guard ensures ./gradlew :moduleName:compileKotlinIosSimulatorArm64 works without Xcode (compilation doesn't link).
Part B — Xcode FRAMEWORK_SEARCH_PATHS:
Add matching entries in project.pbxproj for both Debug and Release buildSettings:
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(BUILT_PRODUCTS_DIR)/FirebaseCore",
"$(BUILT_PRODUCTS_DIR)/FirebaseAuth",
"$(BUILT_PRODUCTS_DIR)/FirebaseCoreExtension",
// ... same list as Part A ...
);dyld: Library not loaded: @rpath/FirebaseCore.framework/FirebaseCore (Runtime Crash)
Symptom: The Gradle build and Xcode compilation both succeed, but the app crashes at launch with:
dyld: Library not loaded: @rpath/FirebaseCore.framework/FirebaseCore
Referenced from: .../ComposeApp.framework/ComposeAppCause: The KMP framework is dynamic (isStatic = false or default). The K/N linker creates LC_LOAD_DYLIB entries (@rpath/FirebaseCore.framework/FirebaseCore). Firebase SPM products are static libraries — their .framework bundles exist in $BUILT_PRODUCTS_DIR during build but are NOT embedded in the app bundle. At runtime, dyld searches @rpath and finds nothing.
Solution: Switch to a static framework:
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true // Required when using dev.gitlive:firebase-* with SPM
}With a static framework, all symbols are embedded in the .a archive. No LC_LOAD_DYLIB entries are created. Unresolved -framework flags from dev.gitlive klibs are deferred to the final Xcode app link, where _internal_linkage_SwiftPMImport provides them.
After switching to static, also:
Re-run
integrateLinkagePackage— regeneratesPackage.swiftwithtype: .none(static)Remove any "Embed Frameworks" copy phase for the KMP framework — static frameworks must NOT be embedded
Add linker flags previously resolved by the K/N linker (e.g.,
-framework Accelerate,-weak_framework CoreML) toOTHER_LDFLAGSin the Xcode project
Firebase-Specific Issues
cinterop Failures on C++ Modules (gRPC, abseil, leveldb, BoringSSL)
Symptom: Build fails with cinterop errors on modules like grpc, absl, leveldb, openssl_grpc, or other C++ transitive dependencies of Firebase.
Cause: discoverModulesImplicitly = true (the default) makes Kotlin attempt cinterop on every Clang module in the dependency graph, including C++ modules that are not compatible.
Solution: Set discoverModulesImplicitly = false and explicitly list only the Firebase Clang modules you need:
swiftPMDependencies {
discoverModulesImplicitly = false
swiftPackage(
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
version = from("12.6.0"),
products = listOf(product("FirebaseAnalytics"), /* ... */),
importedModules = listOf("FirebaseAnalytics", "FirebaseCore", /* ... */),
)
}See common-pods-mapping.md for the full importedModules reference.
Firebase Classes Not Found (Wrong Clang Module Name)
Symptom: Unresolved reference for Firebase classes like FIRDatabase, FIRRemoteConfig, FIRFirestore, FIRInAppMessaging even though the product is listed.
Cause: Several Firebase products expose ObjC headers through Clang modules whose names differ from the SPM product name. Using the product name in importedModules won't find the headers.
Solution: Use the correct internal Clang module names:
| SPM Product | Correct importedModules entry |
|---|---|
| FirebaseDatabase | FirebaseDatabaseInternal |
| FirebaseFirestore | FirebaseFirestoreInternal |
| FirebaseInAppMessaging-Beta | FirebaseInAppMessagingInternal |
| FirebaseRemoteConfig | FirebaseRemoteConfigInternal |
FirebaseFirestore Import Errors
Symptom: Can't import FIRFirestore classes
Cause: Firestore's Clang module name differs from product name. The internal Clang module exposed to Objective-C is FirebaseFirestoreInternal, not FirebaseFirestore.
Solution: Add explicit importedModules:
swiftPackage(
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
version = from("12.6.0"),
products = listOf(product("FirebaseFirestore")),
importedModules = listOf("FirebaseFirestoreInternal"), // Required
)Firebase Crashlytics: dSYM Upload Script Broken After Migration
Symptom: Crash reports don't appear in Firebase Console after migrating to SPM. Or the build phase fails with "No such file" errors referencing ${PODS_ROOT}/FirebaseCrashlytics/upload-symbols.
Cause: The CocoaPods-era dSYM upload script references ${PODS_ROOT} which no longer exists. The SPM equivalent is at a different path.
Solution: Update the existing "Run Script" build phase (or add one at the END if none exists):
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"With input files:
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist
$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist
$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)Also set Debug Information Format to DWARF with dSYM File for all build configurations in Build Settings.
Firebase Beta Products: SPM Name Differs
Symptom: FirebaseInAppMessaging or FirebaseAppDistribution not found as SPM product
Cause: Beta products have a -Beta suffix in SPM.
Solution: Use the correct SPM product name:
FirebaseInAppMessaging→FirebaseInAppMessaging-BetaFirebaseAppDistribution→FirebaseAppDistribution-Beta
dyld Crash When Mixing Firebase Across CocoaPods and SPM
Symptom: App crashes at launch with a dyld error like:
Symbol not found: _OBJC_CLASS_$_FIRFirestoreor similar _OBJC_CLASS_$_FIR* symbol-not-found errors. The Gradle build and Xcode compilation both succeed, but the app crashes at runtime.
Cause: Some Firebase pods were migrated to SPM while others remained in CocoaPods. All Firebase products share transitive dependencies (gRPC, abseil, leveldb, BoringSSL, nanopb). Having both package managers link these transitive dependencies causes duplicate/conflicting symbols that the dynamic linker cannot resolve.
Solution: Migrate all Firebase pods to SPM at once. This includes Swift-only pods (FirebaseAI, FirebaseFunctions, FirebaseMLModelDownloader) that Kotlin cannot use directly — add them as products entries without importedModules:
products = listOf(
// ObjC pods used by Kotlin:
product("FirebaseAnalytics"),
product("FirebaseAuth"),
// ...
// Swift-only pods (no importedModules needed):
product("FirebaseAI"),
product("FirebaseFunctions"),
),After adding new products, re-run integrateLinkagePackage to regenerate the linkage Swift package.
Firebase Initialization Fails at Runtime
Symptom: App crashes on Firebase initialization
Solution:
Ensure
GoogleService-Info.plistis in iOS app targetCall
FIRApp.configure()before using any Firebase serviceCheck Firebase console for configuration issues
Google Maps Issues
GoogleMaps Version Not Found
Symptom: SPM can't resolve GoogleMaps package
Solution: GoogleMaps requires exact version matching:
swiftPackage(
url = url("https://github.com/googlemaps/ios-maps-sdk.git"),
version = exact("10.6.0"), // Must use exact(), not from()
products = listOf(
product("GoogleMaps", platforms = setOf(iOS()))
),
)Check releases page for valid versions.
KSP (Kotlin Symbol Processing) Compatibility
KSP after updating Kotlin version
KSP should generally work with the target Kotlin version without any changes. Do NOT update KSP as part of the migration — it is out of scope.
If KSP does fail (unlikely), the issue is unrelated to the CocoaPods-to-SwiftPM migration itself. Present the error to the user and let them decide how to handle it separately.
Manual Integration Command Discovery
If the xcodebuild approach in Phase 5.1 fails, discover paths manually and run integration tasks directly:
# Find iOS project directory (contains Podfile)
IOS_DIR=$(dirname "$(find . -name "Podfile" -type f | head -1)")
# Find .xcodeproj (exclude Pods.xcodeproj) - use realpath for absolute path
XCODEPROJ=$(realpath "$(find "$IOS_DIR" -maxdepth 1 -name "*.xcodeproj" -type d | grep -v Pods | head -1)")
# Find KMP module with swiftPMDependencies (module directory name)
KMP_MODULE=$(grep -rl "swiftPMDependencies" --include="build.gradle.kts" . | head -1 | xargs dirname | xargs basename)
XCODEPROJ_PATH="$XCODEPROJ" \
GRADLE_PROJECT_PATH=":$KMP_MODULE" \
./gradlew ":$KMP_MODULE:integrateEmbedAndSign" ":$KMP_MODULE:integrateLinkagePackage"Manual CocoaPods Deintegration from pbxproj
If pod deintegrate is not available, manually remove these CocoaPods references from project.pbxproj:
Pods_<target>.frameworkbuild file and file referencePods-<target>.debug.xcconfig/Pods-<target>.release.xcconfigfile referencesPodsgroup andFrameworksgroup (if it only contained the Pods framework)[CP] Check Pods Manifest.lockshell script build phase[CP] Embed Pods Frameworksshell script build phasebaseConfigurationReferencelines pointing to Pods xcconfig files
Manual Xcode Integration Steps
If the automatic integrateEmbedAndSign / integrateLinkagePackage tasks fail, set up the Xcode project manually:
Open
.xcodeproj(or.xcworkspaceif non-KMP CocoaPods remain)Add "Compile Kotlin" run script phase BEFORE "Compile Sources":
cd "$SRCROOT/.." ./gradlew :moduleName:embedAndSignAppleFrameworkForXcodeSet
ENABLE_USER_SCRIPT_SANDBOXING = NO(Build Settings → Build Options → User Script Sandboxing)Run
./gradlew --stopto restart the Gradle daemon after changing sandboxingAdd local package:
../moduleName/_internal_linkage_SwiftPMImport
When Build Fails After Migration
Do NOT revert the migration as a first response. Instead:
Read the full error log — identify the actual failure type (Gradle resolution, import not found, linker error, Xcode build phase)
Re-check each migration phase — walk through Phases 2-6 and verify each step was applied. Common mistakes:
Missing JetBrains Maven repo in
settings.gradle.ktsWrong
groupor module name in import namespace (dashes not converted to dots)cocoapods {}block or plugin not fully removed (Phase 6)Wrong Xcode project file opened (
.xcodeprojwhen non-KMP CocoaPods remain and.xcworkspaceis needed, or vice versa)isStatic = truemissing from framework config (required with dev.gitlive or similar CocoaPods-era wrapper klibs)integrateLinkagePackagenot runEmbedAndSign disabler code not removed (prevents
integrateEmbedAndSign)embedAndSignAppleFrameworkForXcodecommented out in Xcode build phasecocoapods.*imports replaced that should have been preserved (bundled klib from third-party library)
Consult the sections above for specific error patterns
If unsure, present options to the user — describe what the logs show, list possible causes, and let the user decide
Rollback Instructions (Last Resort)
Only revert if analysis above does not resolve the issue:
Step 1: Restore Git Files
# Restore CocoaPods files (adjust path if iOS project is not in iosApp/)
git checkout -- "**/Podfile" "**/Podfile.lock"
git checkout -- *.podspec
git checkout -- **/build.gradle.kts
git checkout -- **/src/**/*.ktStep 2: Restore CocoaPods in build.gradle.kts
plugins {
kotlin("native.cocoapods") // Re-add
}
kotlin {
cocoapods {
// Restore original configuration
}
// Remove swiftPMDependencies block
}Step 3: Restore Kotlin Imports
Change all imports back:
// FROM:
import swiftPMImport.group.module.ClassName
// TO:
import cocoapods.PodName.ClassNameStep 4: Reinstall CocoaPods
# Navigate to directory containing Podfile (adjust path as needed)
cd <ios-project-directory> # e.g., iosApp/, ios/, or project root
pod installStep 5: Open Workspace
Open *.xcworkspace (not .xcodeproj) from the iOS project directory in Xcode.
Getting Help
If issues persist:
Check sample projects:
Run verbose build:
./gradlew build --infoCheck generated files:
Look in
moduleName/_internal_linkage_SwiftPMImport/for Package.swift
Inspect klib contents using the
klibtool (docs):# Dump all API signatures from a klib klib dump-metadata-signatures /path/to/library.klib # Search for specific classes klib dump-metadata-signatures /path/to/library.klib | grep "ClassName" # Compare before/after — find klibs in build output find . -name "*.klib" -path "*swiftPMImport*" # new swiftPMImport klibs find ~/.gradle/caches -name "*.klib" -path "*libraryName*" # third-party klibsThis is particularly useful for verifying which classes are available in the swiftPMImport klib vs. bundled in third-party dependency klibs.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- JetBrains
- Publisher
- JetBrains (Official)
- Distributed by
- JetBrains
- Source
- JetBrains/skills · kotlin-tooling-cocoapods-spm-migration · commit 3f53782
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (written by AutoGineer)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by JetBrains.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download kotlin-tooling-cocoapods-spm-migration.zip