All skills
kotlin avatar

/kotlin-tooling-cocoapods-spm-migration

@7429a68 official
by kotlinkotlin/kotlin-agent-skills1.1k stars
42

Migrate KMP projects from CocoaPods (kotlin("native.cocoapods")) to Swift Package Manager (swiftPMDependencies DSL) — replaces pod() with swiftPackage(), transforms cocoapods.* imports to swiftPMImport.*, and reconfigures the Xcode project.

Use this Skill: https://skilld.dev/gh/kotlin/kotlin-agent-skills/kotlin-tooling-cocoapods-spm-migration

This session only. Nothing lands on disk.

referencesdsl-reference.md

≈2.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SwiftPM Import DSL Reference

Complete reference for the swiftPMDependencies {} DSL in Kotlin Multiplatform.

Basic Structure

swiftPackage() and localSwiftPackage() are annotated with @ExperimentalKotlinGradlePluginApi (warning level). Add the opt-in at the top of build.gradle.kts:

@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
    iosArm64()
    iosSimulatorArm64()

    swiftPMDependencies {
        // Deployment versions
        iosMinimumDeploymentTarget = "16.0"
        macosMinimumDeploymentTarget = "13.0"
        tvosMinimumDeploymentTarget = "16.0"
        watchosMinimumDeploymentTarget = "9.0"

        // Module discovery (default: true)
        discoverClangModulesImplicitly = true

        // Package declarations
        swiftPackage(...)
        localSwiftPackage(...)
    }
}

Package Declaration

The DSL has two API forms. Use the simple string API for most packages. Use the typed API only when you need exact(), branch(), revision(), or platform constraints.

Simple API (Preferred)

Plain strings for URL, version, and products. The version parameter maps to a minimum version (from()) internally. The importedClangModules defaults to the products list automatically.

swiftPackage(
    url = "https://github.com/owner/repo.git",
    version = "1.0.0",   // Equivalent to from("1.0.0") — minimum version
    products = listOf("ProductName", "AnotherProduct"),
)

Typed API (Advanced)

Use when you need exact version pinning, branch tracking, platform constraints, or explicit Clang module control:

swiftPackage(
    url = url("https://github.com/owner/repo.git"),
    version = exact("1.0.0"),
    products = listOf(
        product("ProductName"),
        product("PlatformSpecific", platforms = setOf(iOS()))
    ),
    importedClangModules = listOf("CustomClangModuleName"),
)

Remote Package (Swift Package Registry)

swiftPackage(
    repository = id("scope.package-name"),
    version = from("1.0.0"),
    products = listOf(product("ProductName")),
    packageName = "package-name",
)

Local Package

localSwiftPackage(
    directory = layout.projectDirectory.dir("../LocalPackage"),
    products = listOf("LocalPackage"),
)

To create a new local package (e.g., a Swift/ObjC wrapper around a Swift-only library):

cd /path/to/shared
mkdir LocalPackage && cd LocalPackage
swift package init --type library --name LocalPackage

Then use it in Kotlin:

// src/appleMain/kotlin/useLocalPackage.kt
import swiftPMImport.<group>.<module>.HelloFromLocalPackage

@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
fun useLocalPackage() {
    HelloFromLocalPackage().hello()
}

Version Specification

Syntax Description Use Case
version = "1.0.0" (simple API) Minimum version — equivalent to from("1.0.0") Most packages
version = exact("1.0") Exact version pin Strict dependencies, migration
version = from("1.0") Minimum version (explicit) Same as simple string
version = branch("name") Git branch Development, testing
version = revision("hash") Git commit hash Pinning specific commits
version = range("1.0", "2.0") Version range Constraining upper bound

Important for migration: The simple string version = "X.Y.Z" resolves to a minimum version (from()), which may pull a newer version than what was in CocoaPods. For exact version preservation during migration, use the typed API: version = exact("X.Y.Z").


Product Configuration

Simple API

products = listOf("FirebaseAnalytics", "FirebaseAuth")

With the simple API, importedClangModules defaults to the same list as products. This works when product names match Clang module names.

Typed API — Platform Constraints

For packages that only support certain platforms, use the typed product() function:

products = listOf(
    product("GoogleMaps", platforms = setOf(iOS()))  // iOS only
)

Available platforms: iOS(), macOS(), tvOS(), watchOS()

Typed API — Per-Product Clang Module Override

products = listOf(
    product("FirebaseDatabase", importedClangModules = setOf("FirebaseDatabaseInternal"))
)

Module Import Configuration

Automatic Discovery (Default)

By default, discoverClangModulesImplicitly = true. SwiftPM import automatically discovers and imports all accessible Clang modules.

IMPORTANT: When discoverClangModulesImplicitly = true, the importedClangModules parameter is ignored. Only set importedClangModules when discoverClangModulesImplicitly = false.

IMPORTANT for Firebase: Set discoverClangModulesImplicitly = false when using Firebase. Firebase's transitive C++ dependencies (gRPC, abseil, leveldb, BoringSSL) contain Clang modules that fail cinterop generation. Disable implicit discovery and explicitly list only the Firebase modules you need in importedClangModules.

Explicit Module Import

When automatic discovery is disabled and the Clang module name differs from the product name, use the typed API:

swiftPMDependencies {
    discoverClangModulesImplicitly = false  // Disable auto-discovery

    swiftPackage(
        url = url("https://github.com/firebase/firebase-ios-sdk.git"),
        version = from("12.6.0"),
        products = listOf(
            product("FirebaseAnalytics"),
            product("FirebaseFirestore")
        ),
        importedClangModules = listOf(
            "FirebaseAnalytics",
            "FirebaseCore",
            "FirebaseFirestoreInternal"  // Note: different from product name
        ),
    )
}

When to Use importedClangModules

Scenario Use importedClangModules?
Simple API, product name = Clang module name No (auto-defaulted from products)
Product name != Clang module name Yes (typed API)
Multiple modules per product Yes (typed API)
Using discoverClangModulesImplicitly = false Yes (typed API)

Deployment Versions

Set minimum deployment targets for each platform:

swiftPMDependencies {
    iosMinimumDeploymentTarget = "16.0"
    macosMinimumDeploymentTarget = "13.0"
    tvosMinimumDeploymentTarget = "16.0"
    watchosMinimumDeploymentTarget = "9.0"
}

Complete Example

plugins {
    alias(libs.plugins.kotlinMultiplatform)
}

group = "org.example.myproject"
version = "1.0-SNAPSHOT"

kotlin {
    iosArm64()
    iosSimulatorArm64()

    // Framework configuration (moved from cocoapods block)
    listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "SharedModule"
            isStatic = true
        }
    }

    swiftPMDependencies {
        iosMinimumDeploymentTarget = "16.0"

        // Simple API — most packages
        swiftPackage(
            url = "https://github.com/lukaskubanek/LoremIpsum.git",
            version = "2.0.1",
            products = listOf("LoremIpsum"),
        )

        // Simple API — Google Maps
        swiftPackage(
            url = "https://github.com/googlemaps/ios-maps-sdk.git",
            version = "10.3.0",
            products = listOf("GoogleMaps"),
        )

        // Local package
        localSwiftPackage(
            directory = layout.projectDirectory.dir("LocalWrapper"),
            products = listOf("LocalWrapper"),
        )
    }

    compilerOptions {
        optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
    }
}

Transitive Dependencies

SwiftPM dependencies are handled automatically. When you run Kotlin/Native tests or link a framework, the Kotlin Gradle Plugin will provision necessary machine code from transitive SwiftPM dependencies. This behavior is automatic.

You can optionally declare transitive dependencies explicitly to pin specific versions:

swiftPMDependencies {
    // Main dependency
    swiftPackage(
        url = "https://github.com/firebase/firebase-ios-sdk.git",
        version = "12.5.0",
        products = listOf("FirebaseAnalytics"),
    )

    // Transitive dependency with explicit version pin
    swiftPackage(
        url = url("https://github.com/apple/swift-protobuf.git"),
        version = exact("1.32.0"),
        products = listOf(product("SwiftProtobuf")),
    )
}

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill automates the migration of Kotlin Multiplatform (KMP) projects from CocoaPods to Swift Package Manager (SPM). It handles project analysis, Gradle configuration updates, Kotlin import transformations, and Xcode project reconfiguration. While the migration process involves modifying environment security settings (such as disabling Xcode's User Script Sandboxing) and executing complex build commands, these actions are standard requirements for the KMP toolchain and are performed as part of the tool's intended migration functionality.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 7429a68. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 5 months ago
metadata
{
  "author": "JetBrains",
  "version": "1.0.0"
}
  • kotlin
  • kmp
  • cocoapods
  • swift-package-manager
  • ios
  • migration
  • xcode
  • gradle
  • multiplatform

README badge

README badge for kotlin/kotlin-agent-skills/kotlin-tooling-cocoapods-spm-migration

Migrates Kotlin Multiplatform projects from CocoaPods to Swift Package Manager by replacing the kotlin("native.cocoapods") plugin with swiftPMDependencies DSL. Requires Kotlin 2.4.0-Beta2 or later and Xcode 16.4+, and includes a phased workflow that reconfigures the iOS project and removes CocoaPods dependencies once SwiftPM is integrated.

Generated from the current SKILL.md.

What Kotlin version does this skill require?
Kotlin 2.4.0-Beta2 or later. The skill will upgrade your project automatically if you're on an older version, but warns that version jumps across minor versions can introduce unrelated breaking changes.
Can I remove CocoaPods during the migration, or do I need to keep it?
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.
Does this work with third-party KMP libraries that bundle CocoaPods klibs?
Yes. The skill detects existing bundled klibs (e.g., from KMPNotifier) and skips generating new bindings for those modules, keeping their `cocoapods.*` imports unchanged.
What if my project has CocoaPods dependencies not declared in the Gradle `cocoapods {}` block?
The skill cross-references the Podfile against the Gradle block and flags any Podfile-only dependencies that must be migrated to `swiftPMDependencies` to avoid runtime linkage errors.
Do I need to update other dependencies or the Gradle wrapper during this migration?
No. The skill only updates the Kotlin version (if needed) and adds SwiftPM configuration. Other dependency or Gradle wrapper updates are out of scope and should be done separately.

Generated from the current SKILL.md. These answers refresh after source changes.