跪拜 Guibai
← Back to the summary

KMP Gets Alpha SwiftPM Support — Here's the CocoaPods Migration Path

KMP Officially Supports SwiftPM: CocoaPods Migration Configuration Guide

How to migrate CocoaPods dependencies in a KMP module to Swift Package Manager? The entry point on the page is Junie + Kotlin AI Skill, but the migration itself still comes back to specific files like Gradle, Xcode, and Kotlin imports.

SwiftPM import is still in Alpha, and the example requires the KMP Gradle plugin to use 2.4.0-RC2. If a project has already integrated iOS SDKs like Firebase or Google Maps via CocoaPods, this migration is not just about replacing the package manager; it will also affect the iOS-side build process.

Image

First, Look at the Boundaries

This time, the topic is "introducing SwiftPM dependencies in a KMP module." That means Kotlin code can call Objective-C APIs via SwiftPM import, and when Kotlin/Native compiles and links the framework, the Gradle plugin will include the machine code required by the SwiftPM dependencies.

This feature does not mean exporting a KMP module that uses SwiftPM import as a Swift package. The documentation explicitly states that this scenario is not yet supported and may not work correctly. If your delivery method is publishing the shared module as a Swift package for the iOS team to consume, you need to confirm this limitation first.

Another boundary is dynamic Kotlin/Native frameworks. When encountering Undefined symbols for architecture ..., dyld: Symbol not found, or the same Objective-C class being implemented in two binaries, the general workaround provided in the documentation is to make the framework static:

kotlin {
    listOf(
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "Shared"
            isStatic = true
        }
    }
}

This point is closely related to the CocoaPods migration. Previously, many projects configured the framework inside cocoapods.framework {}. After migrating to SwiftPM, this part needs to be moved under the Apple target's binaries.framework {}.

Image

Junie and Skill

Using Junie is relatively straightforward. First, install the CLI, then log in to your JetBrains account, or configure an external LLM:

curl -fsSL https://junie.jetbrains.com/install.sh | bash
junie

Install the Kotlin AI Skill in the project directory:

npx skills add Kotlin/kotlin-agent-skills

This command requires npm version 5.2.0 or higher. In the interaction, select kotlin-tooling-cocoapods-spm-migration, choose Junie as the agent, and set the scope to Project, so the Skill is only applied to the current project.

Then, start Junie in the project root directory:

junie

Enter the migration task:

Migrate <project-name> from CocoaPods to SwiftPM

Junie will recognize the newly installed Skill and begin inspecting the project. Before starting, ensure the project is already in Git, because you'll rely on the diff later to see exactly which files it changed. AI tools will get some parts right, but they might also modify the Xcode project, Podfile, or imports incorrectly. You cannot treat the generated results as the final conclusion.

Image

Gradle Changes

SwiftPM import requires the Kotlin Multiplatform Gradle plugin to use 2.4.0-RC2. If the project uses a Version Catalog, the version entry looks roughly like this:

[versions]
kotlin = "2.4.0-RC2"

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }

When migrating dependencies, do not delete CocoaPods right away. The order in the documentation is to first add swiftPMDependencies {}, allowing SwiftPM and CocoaPods dependencies to coexist for a short time. Once the SwiftPM imports can be resolved and the Kotlin code compiles, then clean up CocoaPods.

Taking FirebaseAnalytics as an example, a Swift package declaration will be added in build.gradle.kts:

kotlin {
    iosArm64()
    iosSimulatorArm64()

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

    cocoapods {
        pod("FirebaseAnalytics") {
            version = "12.5.0"
        }
    }
}

If the Clang module name inside a Swift package does not match the product name, you cannot just specify the product. After setting discoverClangModulesImplicitly = false, you need to explicitly list them using importedClangModules. The Objective-C API for FirebaseFirestore is in the FirebaseFirestoreInternal module, as shown in the documentation example.

kotlin {
    swiftPMDependencies {
        discoverClangModulesImplicitly = false

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

Projects with multiple Apple targets also need to consider platform constraints. For example, the Google Maps SDK currently only supports iOS targets. If a project has both iosArm64() and macosArm64(), the product declaration must be restricted to iOS:

kotlin {
    iosArm64()
    iosSimulatorArm64()
    macosArm64()

    swiftPMDependencies {
        swiftPackage(
            url = url("https://github.com/googlemaps/ios-maps-sdk.git"),
            version = exact("10.3.0"),
            products = listOf(
                product("GoogleMaps", platforms = setOf(iOS()))
            )
        )
    }
}

Finally

What percentage of your engineering projects does KMP account for?

#Kotlin #KMP #SwiftPM #CocoaPods #AI Programming

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

超级个体_小熊蛋

Hello teacher, keep it up teacher!