Refactor a Kotlin Multiplatform / Compose Multiplatform project from CocoaPods-based iOS dependency integration to Swift Package Manager import tooling, preserving compatible versions and updating Gradle/Xcode configuration safely.
How this skill is triggered — by the user, by Claude, or both
Slash command
/kmp-cocoapods-to-swiftpm:kmp-cocoapods-to-swiftpmThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when refactoring a Kotlin Multiplatform / Compose Multiplatform project that currently uses `kotlin("native.cocoapods")` and/or a `cocoapods {}` block, and the goal is to migrate iOS library integration to Swift Package Manager.
Use this skill when refactoring a Kotlin Multiplatform / Compose Multiplatform project that currently uses kotlin("native.cocoapods") and/or a cocoapods {} block, and the goal is to migrate iOS library integration to Swift Package Manager.
The official Kotlin migration flow is:
swiftPMDependencies {} equivalents where possible.build.gradle.kts.swiftPMDependencies {}.cocoapods.framework {} configuration into binaries.framework {} on iOS targets.cocoapods, keep that same version in the SwiftPM declaration whenever the package supports it.cocoapods, search the web for a compatible version and then write that version explicitly in build.gradle.kts.:composeApp as the only shared module path. Depending on the project structure, the shared KMP module may be :composeApp, :shared, :sharedUI, or another module path.When given a project, inspect:
build.gradle.ktsbuild.gradle.ktskotlin("native.cocoapods")cocoapods {} blocksPodfileiosApp.xcodeproj / iOS Xcode projectiosArm64(), iosSimulatorArm64(), iosX64()cocoapods.*Extract every pod currently used, including:
linkOnly, extraOpts, subspecs, or module namescocoapods.<PodName>...Create a migration table like:
| Pod | Pod version | Swift package URL | Swift product(s) | Version to use | Notes |
|---|
Rules for the Version to use column:
Use Kotlin 2.4.0 final for the migration. Replace whatever Kotlin version the project currently uses if it is older than 2.4.0, because swiftPMDependencies {} support arrives with Kotlin 2.4.0.
Typical target refactor pattern:
kotlin {
iosArm64()
iosSimulatorArm64()
swiftPMDependencies {
swiftPackage(
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
version = from("12.5.0"),
products = listOf(product("FirebaseAnalytics")),
)
}
listOf(
iosArm64(),
iosSimulatorArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true
}
}
}
Important notes:
2.4.0 whenever it is below 2.4.0, not just when it is a prerelease.cocoapods {} block during the transition until SwiftPM import and Xcode integration are working.cocoapods.framework { baseName = "..."; isStatic = ... }, move that configuration under binaries.framework {} for each relevant iOS target.embedAndSignAppleFrameworkForXcode task only exists when binaries.framework {} is configured.For each pod:
swiftPackage(...) declaration.Pattern:
swiftPMDependencies {
swiftPackage(
url = url("PACKAGE_GIT_URL"),
version = from("EXPLICIT_VERSION"),
products = listOf(product("PRODUCT_NAME")),
)
}
If multiple products come from the same package:
swiftPMDependencies {
swiftPackage(
url = url("PACKAGE_GIT_URL"),
version = from("EXPLICIT_VERSION"),
products = listOf(
product("ProductA"),
product("ProductB"),
),
)
}
After resolving SwiftPM dependencies, update imports from the CocoaPods namespace to the imported SwiftPM namespace.
Example pattern:
// old
import cocoapods.FirebaseAnalytics.FIRAnalytics
// new
import swiftPMImport.ExampleProject.shared.FIRAnalytics
Where:
ExampleProject is based on the Gradle project identity, typically derived from rootProject.name.shared is the KMP shared module name.shared, composeApp, or another actual shared module name used by the project.Rules for import migration:
swiftPMImport.org.example.package.*.swiftPMImport.<ProjectName>.<SharedModule>.*.Do not mass-rewrite blindly; verify the generated package path in the project.
$stable crashes on serialized modelsIn projects that use Compose together with Ktor and kotlinx.serialization, shared data models may sometimes crash at runtime because of Compose stability instrumentation conflicts involving generated $stable fields. In that situation, add a Compose stability configuration file and exclude only the affected serialized model packages from instrumentation.
Example compose-stability.conf:
// Stability configuration for Compose compiler
// Exclude serialized data models from Compose stability instrumentation
com.example.data.model.**
Example shared-module build.gradle.kts configuration:
composeCompiler {
@Suppress("OPT_IN_USAGE")
stabilityConfigurationFiles.add(
rootProject.layout.projectDirectory.file("compose-stability.conf")
)
}
Rules:
kotlinx.serialization.If the project currently relies on the CocoaPods Gradle plugin, reconfigure the Xcode project for direct integration before deleting CocoaPods.
Expected workflow:
The generated command from Xcode is the source of truth and should be preferred over any hardcoded example.
Typical generated form:
XCODEPROJ_PATH='/path/to/project/iosApp/iosApp.xcodeproj' \
GRADLE_PROJECT_PATH=':shared-module-name' \
'/path/to/project/gradlew' -p '/path/to/project' \
':shared-module-name:integrateEmbedAndSign' \
':shared-module-name:integrateLinkagePackage'
Project-specific examples:
XCODEPROJ_PATH='./iosApp/iosApp.xcodeproj' ./gradlew :composeApp:integrateLinkagePackage
XCODEPROJ_PATH='./iosApp/iosApp.xcodeproj' ./gradlew :shared:integrateLinkagePackage
Rules:
:composeApp with the actual shared KMP module path.:composeApp.:shared.:sharedUI.integrateEmbedAndSign and integrateLinkagePackage.GRADLE_PROJECT_PATH, preserve it.integrateLinkagePackage is needed; use the exact generated command where possible.Only after the project builds successfully with SwiftPM import:
Podfile, or fully remove CocoaPods usage if no pods remain.pod install if partial CocoaPods usage remains.cocoapods {} block entirely when all migrated dependencies are handled by SwiftPM.kotlin("native.cocoapods") from the relevant Gradle plugins if the project no longer needs CocoaPods at all.pod deintegrate where appropriate.When applying this skill to a repository, follow this exact behavior:
2.4.0, replacing the project's existing Kotlin version when it is below 2.4.0 because earlier versions do not support swiftPMDependencies {}.swiftPMDependencies {} without prematurely deleting CocoaPods during the intermediate state.cocoapods.framework {} to binaries.framework {}.cocoapods.* to SwiftPM-imported namespaces.:composeApp and :shared as examples, not universal constants.When refactoring, produce:
A short explanation of:
For each changed file:
Include:
iosX64() / simulator targets unless there is a justified modernization step.:composeApp; derive the actual shared module path from the project.Input:
cocoapods {
pod("FirebaseAnalytics") {
version = "12.5.0"
}
}
Required behavior:
12.5.0 in the SwiftPM dependency declaration.Input:
cocoapods {
pod("SomeLibrary")
}
Required behavior:
swiftPMDependencies.Command:
XCODEPROJ_PATH='./iosApp/iosApp.xcodeproj' ./gradlew :composeApp:integrateLinkagePackage
Required behavior:
:composeApp is the actual shared module.:composeApp:integrateEmbedAndSign.Command:
XCODEPROJ_PATH='./iosApp/iosApp.xcodeproj' ./gradlew :shared:integrateLinkagePackage
Required behavior:
:shared is the actual shared module in the newer project structure.Use this skill with a prompt such as:
Migrate this KMP project from CocoaPods to SwiftPM. Inspect all
build.gradle.kts,Podfile, and iOS integration files. Preserve any explicitly declared pod versions. If a pod has no version, search for a compatible Swift package version and pin it explicitly. Replace the project's current Kotlin version with 2.4.0 final when it is below 2.4.0, because earlier versions do not supportswiftPMDependencies {}. Keep CocoaPods only as long as needed for an intermediate working state, then remove it cleanly. When reconfiguring Xcode, use the generated integration command and substitute the real shared module path, such as:composeAppor:shared.
The migration is complete when:
binaries.framework {} instead of cocoapods.framework {}.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub xabaras/kmp-cocoapods-to-swiftpm --plugin kmp-cocoapods-to-swiftpm