kotlin-tooling-cocoapods-spm-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CocoaPods to SwiftPM Migration for KMP

针对KMP的CocoaPods至SwiftPM迁移指南

Migrate Kotlin Multiplatform projects from
kotlin("native.cocoapods")
to
swiftPMDependencies {}
DSL.
将Kotlin Multiplatform项目从
kotlin("native.cocoapods")
迁移至
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
  • Kotlin:支持Swift Import的版本(例如2.4.0-Beta1或更高版本)
  • Xcode:16.4或26.0+
  • iOS部署目标:推荐16.0+

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.
PhaseAction
1Analyze existing CocoaPods configuration
2Update Gradle configuration (repos, Kotlin version)
3Add
swiftPMDependencies {}
alongside existing
cocoapods {}
4Transform Kotlin imports
5Reconfigure iOS project and deintegrate CocoaPods
6Remove CocoaPods plugin from Gradle
7Verify Gradle build and Xcode project build
8Write MIGRATION_REPORT.md

重要提示:在完成第6阶段前,请保留
cocoapods {}
代码块和插件处于激活状态。迁移会先在现有CocoaPods配置旁添加
swiftPMDependencies {}
,重新配置Xcode,之后再移除CocoaPods。
阶段操作
1分析现有CocoaPods配置
2更新Gradle配置(仓库、Kotlin版本)
3在现有
cocoapods {}
旁添加
swiftPMDependencies {}
4转换Kotlin导入语句
5重新配置iOS项目并移除CocoaPods
6从Gradle中移除CocoaPods插件
7验证Gradle构建和Xcode项目构建
8编写MIGRATION_REPORT.md

Phase 1: Pre-Migration Analysis

阶段1:迁移前分析

1.0 Verify the project builds

1.0 验证项目可正常构建

Before starting migration, identify the module to migrate and confirm it compiles successfully.
  1. Find the module that uses CocoaPods — look for
    build.gradle.kts
    files containing
    cocoapods
    :
    bash
    grep -rl "cocoapods" --include="build.gradle.kts" .
    Extract the module name from the path (e.g.,
    ./shared/build.gradle.kts
    → module name is
    shared
    ).
  2. Build only that module (avoids building the entire multi-module project):
    bash
    ./gradlew :moduleName:build
    Replace
    moduleName
    with the directory name of the module (e.g.,
    :shared:build
    ).
  3. 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. 定位使用CocoaPods的模块 — 查找包含
    cocoapods
    build.gradle.kts
    文件:
    bash
    grep -rl "cocoapods" --include="build.gradle.kts" .
    从路径中提取模块名称(例如
    ./shared/build.gradle.kts
    → 模块名称为
    shared
    )。
  2. 仅构建该模块(避免构建整个多模块项目):
    bash
    ./gradlew :moduleName:build
    moduleName
    替换为模块的目录名称(例如
    :shared:build
    )。
  3. 如果目标模块构建失败,请让用户选择:
    • 提供验证模块构建的正确Gradle命令,或
    • 确认模块处于可正常工作状态,可安全继续
    如果用户未提供构建命令但确认可继续,请记录迁移前构建未验证,并在迁移结束时(第7阶段)发出警告。

1.0a Confirm Kotlin version with Swift Import support

1.0a 确认项目使用支持Swift Import的Kotlin版本

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/dev
    as 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.
询问用户:
您的项目是否已使用支持Swift Import的Kotlin版本(swiftPMDependencies DSL)?
如果是 → 从
gradle/libs.versions.toml
(或
build.gradle.kts
)中读取当前Kotlin版本并记录,跳过第2.2阶段(无需更改版本)。
如果否 → 询问:
请提供要使用的Kotlin版本(例如"2.4.0"、"2.4.0-Beta1"、"2.4.0-dev-123")。
记录用户提供的版本,然后询问:
该Kotlin版本是否需要自定义Maven仓库(例如JetBrains开发仓库)?
  • → 询问仓库URL(默认建议
    https://packages.jetbrains.team/maven/p/kt/dev
    ),第2.1阶段将添加该仓库。
  • → 跳过第2.1阶段(无需自定义仓库)。
最后,检查项目当前的Kotlin版本,对比主版本.次版本与目标版本。如果差异较大(例如
2.1.0
2.4.0
),发出警告:"⚠️ Kotlin版本跨级更新 — 跨次版本升级可能引入与本次迁移无关的破坏性变更。建议:先单独升级Kotlin版本,验证构建正常后再重新执行迁移。" 如果用户确认仍要继续,则推进流程。

1.1 Check for deprecated CocoaPods workaround property

1.1 检查已弃用的CocoaPods兼容属性

Search
gradle.properties
for the deprecated property:
properties
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
This 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.
gradle.properties
中搜索已弃用的属性:
properties
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
该属性是针对同时使用
embedAndSign
和CocoaPods依赖的项目的兼容方案(详见KT-64096),用于抑制关于不支持配置的错误提示,这类错误可能导致运行时崩溃或符号重复。迁移至SwiftPM Import后,该属性不再需要,必须在第6阶段移除。如果找到该属性,请记录。

1.2 Check for EmbedAndSign disablers

1.2 检查EmbedAndSign禁用代码

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.
在所有
build.gradle.kts
文件中搜索禁用
EmbedAndSign
任务的代码(例如
TaskGraph.whenReady
过滤器、
tasks.matching
代码块)。这是CocoaPods时代的兼容方案,会破坏迁移流程,因为第5阶段需要的
integrateEmbedAndSign
任务也会被禁用。记录所有此类代码 — 必须在第6阶段移除,部分情况可能需要提前移除。有关代码模式,请参阅troubleshooting.md中的“
integrateEmbedAndSign
Skipped”章节。

1.3 Check for third-party KMP libraries with bundled cinterop klibs

1.3 检查包含捆绑cinterop klib的第三方KMP库

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:
LibraryMaven artifactBundled klib namespaceClasses 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:
  1. CocoaPods configuration - Search for
    cocoapods
    in
    build.gradle.kts
    files
  2. Pod dependencies - Extract pod names, versions from
    cocoapods {}
    blocks
  3. Framework configuration - Record
    baseName
    ,
    isStatic
    , deployment target from
    cocoapods.framework {}
  4. 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.
  5. 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).
  6. Map pods to SPM - See common-pods-mapping.md
  7. Locate iOS project directory - Find the directory containing
    Podfile
    and
    .xcworkspace
    :
    bash
    find . -name "Podfile" -type f
    Record this path (e.g.,
    iosApp/
    ,
    ios/
    , or project root) - needed for Phase 5
  8. Check for non-KMP CocoaPods - Determine if the project uses CocoaPods for dependencies other than KMP. This affects cleanup strategy in Phase 5.
  9. Check Xcode build phases - Open the
    .xcodeproj
    's
    project.pbxproj
    and search for the Gradle build phase script. Check if
    embedAndSignAppleFrameworkForXcode
    is present but commented out (prefixed with
    #
    ). If commented out, it must be uncommented during Phase 5 — the
    integrateEmbedAndSign
    task may or may not handle this automatically.
  10. Check for existing Crashlytics dSYM upload script - If using FirebaseCrashlytics, search
    project.pbxproj
    for 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.
  11. Identify CocoaPods-related extras in build scripts - Search all
    build.gradle.kts
    files for CocoaPods workarounds beyond the standard
    cocoapods {}
    block (custom tasks hooking into
    podInstall
    ,
    Pods.xcodeproj
    patching, 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.

部分KMP库会随带预构建的cinterop klib,其包命名空间为
cocoapods.*
。迁移后,swiftPMDependencies的cinterop生成器会检测到这些现有绑定,会跳过为这些Clang模块生成新绑定,以避免重复。这意味着针对这些模块的
cocoapods.*
导入语句必须保持不变 — 它们指向第三方库的捆绑klib,而非实际的CocoaPods依赖。
已知包含
cocoapods.*
捆绑klib的库:
Maven制品捆绑klib命名空间提供的类
KMPNotifier
io.github.mirzemehdi:kmpnotifier
cocoapods.FirebaseMessaging
FIRMessaging
,
FIRMessagingAPNSTokenType
检测方法: 在Gradle依赖声明中搜索已知库,然后将其与步骤4中找到的
import cocoapods.*
语句交叉比对。标记出哪些导入来自捆绑klib(需保留),哪些来自直接pod cinterop(需转换)。
如果不确定第三方KMP库是否包含捆绑klib,检查项目中是否存在
linkOnly = true
的pod依赖 — 这是强烈信号,表示该pod仅提供原生链接,cinterop绑定来自KMP封装库(例如
dev.gitlive:firebase-*
)。有关影响,请参阅common-pods-mapping.md
查找并记录以下内容:
  1. CocoaPods配置 - 在
    build.gradle.kts
    文件中搜索
    cocoapods
  2. Pod依赖 - 从
    cocoapods {}
    代码块中提取pod名称和版本
  3. Framework配置 - 记录
    cocoapods.framework {}
    中的
    baseName
    isStatic
    和部署目标
  4. linkOnly类型pod - 记录声明为
    linkOnly = true
    的pod。这些pod仅提供原生链接,cinterop绑定来自KMP封装库(例如
    dev.gitlive:firebase-*
    )。详见common-pods-mapping.md
  5. Kotlin导入语句 - 查找所有
    import cocoapods.*
    语句,与步骤1.3交叉比对,区分哪些来自捆绑klib(需保留),哪些来自直接pod cinterop(需转换)
  6. Pod到SPM的映射 - 参阅common-pods-mapping.md
  7. 定位iOS项目目录 - 查找包含
    Podfile
    .xcworkspace
    的目录:
    bash
    find . -name "Podfile" -type f
    记录该路径(例如
    iosApp/
    ios/
    或项目根目录),第5阶段需要使用
  8. 检查非KMP的CocoaPods依赖 - 确定项目是否将CocoaPods用于KMP以外的依赖,这会影响第5阶段的清理策略
  9. 检查Xcode构建阶段 - 打开
    .xcodeproj
    project.pbxproj
    ,搜索Gradle构建阶段脚本。检查
    embedAndSignAppleFrameworkForXcode
    是否存在但被注释(前缀为
    #
    )。如果已注释,第5阶段必须取消注释 —
    integrateEmbedAndSign
    任务可能会自动处理,也可能需要手动操作
  10. 检查现有Crashlytics dSYM上传脚本 - 如果使用FirebaseCrashlytics,在
    project.pbxproj
    中搜索dSYM上传Shell脚本阶段。记录其当前路径(CocoaPods时代的脚本引用
    ${PODS_ROOT}/FirebaseCrashlytics/upload-symbols
    ),第5阶段必须将其更新为SPM路径
  11. 识别构建脚本中与CocoaPods相关的额外代码 - 在所有
    build.gradle.kts
    文件中搜索标准
    cocoapods {}
    代码块以外的CocoaPods兼容代码(例如自定义任务钩子、
    Pods.xcodeproj
    补丁、podspec元数据、
    noPodspec()
    等)。有关完整模式列表,请参阅cocoapods-extras-patterns.md。记录所有发现,这些将在第6阶段处理

Phase 2: Gradle Configuration

阶段2:Gradle配置

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.
重要范围说明: 本次迁移期间,请勿升级Gradle包装器版本、更新KSP或其他依赖。这些属于独立任务,不在本次迁移范围内。仅修改以下列出的内容。

2.1 Add custom Maven repository (if needed)

2.1 添加自定义Maven仓库(如需要)

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
:
kotlin
pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
        maven("<custom-repo-url>")  // ADD
    }
}

dependencyResolutionManagement {
    repositories {
        mavenCentral()
        maven("<custom-repo-url>")  // ADD
    }
}
如果用户在第1.0a阶段表示其Kotlin版本不需要自定义Maven仓库(即该版本是Maven Central上的正式版、Beta版或RC版),则跳过此步骤。
对于开发版/自定义构建版本,将第1.0a阶段记录的自定义Maven仓库URL添加到
settings.gradle.kts
kotlin
pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
        maven("<custom-repo-url>")  // 添加此行
    }
}

dependencyResolutionManagement {
    repositories {
        mavenCentral()
        maven("<custom-repo-url>")  // 添加此行
    }
}

2.2 Update Kotlin version

2.2 更新Kotlin版本

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:
toml
undefined
如果用户的项目已使用支持Swift Import的Kotlin版本(第1.0a阶段已记录),则跳过此步骤。
更新为第1.0a阶段记录的版本:
toml
undefined

gradle/libs.versions.toml

gradle/libs.versions.toml

[versions] kotlin = "<kotlin-version>"
undefined
[versions] kotlin = "<kotlin-version>"
undefined

2.3 Add buildscript constraint (if swiftPMDependencies not recognized)

2.3 添加构建脚本约束(如果swiftPMDependencies未被识别)

kotlin
// 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.

kotlin
// root build.gradle.kts
buildscript {
    dependencies.constraints {
        "classpath"("org.jetbrains.kotlin:kotlin-gradle-plugin:<kotlin-version>!!")
    }
}
<kotlin-version>
替换为第1.0a阶段记录的版本。
!!
后缀强制严格版本解析,确保没有其他依赖引入不同版本的Kotlin Gradle插件。

Phase 3: Add swiftPMDependencies (Keep CocoaPods)

阶段3:添加swiftPMDependencies(保留CocoaPods)

Do NOT remove the
cocoapods {}
block or
kotlin("native.cocoapods")
plugin yet.
Add
swiftPMDependencies {}
alongside the existing CocoaPods configuration.
请勿移除
cocoapods {}
代码块或
kotlin("native.cocoapods")
插件。
在现有CocoaPods配置旁添加
swiftPMDependencies {}

3.1 Add group property

3.1 添加group属性

kotlin
group = "org.example.myproject"  // Required for import namespace
kotlin
group = "org.example.myproject"  // 导入命名空间必需

3.2 Add swiftPMDependencies block alongside cocoapods

3.2 在cocoapods旁添加swiftPMDependencies代码块

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
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
    }
}
针对每个pod依赖,添加对应的SwiftPM包声明。使用common-pods-mapping.md将每个pod映射到对应的SPM包URL、产品名称和
importedModules
核心概念:
products
= SPM产品名称(控制链接)。
importedModules
= 用于cinterop绑定的Clang模块名称(仅当
discoverModulesImplicitly = false
时需要)。
discoverModulesImplicitly
默认为
true
(为所有Clang模块生成绑定);当传递性C/C++模块无法通过cinterop时(如Firebase、gRPC),设置为
false
,然后显式列出需要的模块。
重要提示: SPM产品名称和Clang模块名称并非始终一致。请务必查阅common-pods-mapping.md获取正确值。
请勿在CocoaPods和SPM中混合使用同一套件的库。 共享同一仓库的库(例如所有Firebase产品)也共享传递性依赖。部分产品通过CocoaPods链接,部分通过SPM链接会导致符号重复/冲突,运行时出现dyld崩溃。迁移此类套件时,请一次性将该套件的所有pod迁移至SPM — 包括Kotlin不直接使用的纯Swift pod。将纯Swift pod添加为
products
条目(无需
importedModules
)。添加新产品后,重新运行
integrateLinkagePackage
以重新生成链接Swift包。
kotlin
kotlin {
    // 保留现有目标
    iosArm64()
    iosSimulatorArm64()
    iosX64()

    swiftPMDependencies {
        iosDeploymentVersion.set("16.0")

        // 如果使用KMP IntelliJ插件,请指定.xcodeproj路径:
        // 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 {
        // ... 暂时保留现有cocoapods代码块
    }
}

3.3 Move framework configuration out of cocoapods block

3.3 将framework配置从cocoapods代码块中移出

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:
kotlin
listOf(iosArm64(), iosSimulatorArm64(), iosX64()).forEach { iosTarget ->
    iosTarget.binaries.framework { baseName = "Shared"; isStatic = true }
}
如果
cocoapods
代码块中包含
framework {}
配置,请将其移至每个target的
binaries
API中。推荐设置
isStatic = true
— 动态框架与SwiftPM Import存在已知边缘情况,可能导致链接器错误、dyld崩溃或类重复警告:
kotlin
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

3.4 处理dev.gitlive/firebase-kotlin-sdk及类似CocoaPods时代的KMP封装库

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.
如果项目使用
dev.gitlive:firebase-*
或类似KMP封装库,需要额外执行两个步骤:
A. 切换为
isStatic = true
— 动态框架 + Firebase SPM = 运行时
dyld
崩溃。切换后:重新运行
integrateLinkagePackage
,移除所有“Embed Frameworks”复制阶段,将链接器标志移至
OTHER_LDFLAGS
B. 添加framework搜索路径 — 在
build.gradle.kts
中添加条件式
-F
链接器选项,并在Xcode项目中添加对应的
FRAMEWORK_SEARCH_PATHS
有关代码片段和完整产品列表,请参阅common-pods-mapping.md中的dev.gitlive章节,以及troubleshooting.md

3.5 Add opt-in for cinterop API

3.5 添加cinterop API的opt-in声明

kotlin
kotlin.compilerOptions {
    optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
}
For full DSL reference, see dsl-reference.md.

kotlin
kotlin.compilerOptions {
    optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
}
有关完整DSL参考,请参阅dsl-reference.md

Phase 4: Kotlin Source Updates

阶段4:Kotlin源代码更新

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)
swiftPMImport.<group>.<module>.<ClassName>

参数说明:
- group: build.gradle.kts中的`group`属性,短横线(-)替换为点(.)
- module: Gradle模块名称,短横线(-)替换为点(.)
- ClassName: Objective-C类名(Firebase为FIR*,Google Maps为GMS*)

Example Transformation

转换示例

kotlin
// group = "org.jetbrains.kotlin.firebase.sample", module = "kotlin-library"

// BEFORE:
import cocoapods.FirebaseAnalytics.FIRAnalytics

// AFTER:
import swiftPMImport.org.jetbrains.kotlin.firebase.sample.kotlin.library.FIRAnalytics
Import 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
.
kotlin
// group = "org.jetbrains.kotlin.firebase.sample", module = "kotlin-library"

// 转换前:
import cocoapods.FirebaseAnalytics.FIRAnalytics

// 转换后:
import swiftPMImport.org.jetbrains.kotlin.firebase.sample.kotlin.library.FIRAnalytics
导入扁平化: Clang模块名称(例如
FirebaseFirestoreInternal
FirebaseAuth
)会从导入路径中消失 — 所有类都会被扁平化到同一个
swiftPMImport.<group>.<module>
前缀下,无论它们来自哪个库。例如,
cocoapods.FirebaseAuth.FIRAuth
cocoapods.FirebaseFirestoreInternal.FIRFirestore
都会变为
swiftPMImport.<group>.<module>.FIRAuth
swiftPMImport.<group>.<module>.FIRFirestore

Preserving Bundled Klib Imports

保留捆绑Klib的导入语句

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 — the
cocoapods
prefix 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, so
swiftPMImport.*
for those classes will fail with "Unresolved reference".
Example (project using KMPNotifier):
kotlin
// KEEP — resolves to kmpnotifier's bundled cinterop klib
import cocoapods.FirebaseMessaging.FIRMessaging
关键提示: 请勿替换指向第三方KMP库捆绑cinterop klib的
cocoapods.*
导入语句(第1阶段步骤1.3已识别)。这些导入语句必须保持不变 —
cocoapods
前缀是该库发布的klib中的包命名空间,而非实际的CocoaPods依赖。swiftPMDependencies的cinterop生成器会跳过依赖库已提供的模块,因此针对这些类的
swiftPMImport.*
导入会出现“Unresolved reference”错误。
示例(使用KMPNotifier的项目):
kotlin
// 保留 — 指向kmpnotifier的捆绑cinterop klib
import cocoapods.FirebaseMessaging.FIRMessaging

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

对所有Kotlin源文件执行正则表达式查找替换,排除第1阶段步骤1.3识别的导入语句
查找:    cocoapods\.\w+\.
替换: swiftPMImport.<your.group>.<your.module>.
批量替换后,手动恢复所有需要保留的
cocoapods.*
导入语句(来自捆绑klib的)。
获取正确导入路径的方法: 运行
./gradlew :moduleName:build
— 错误信息会显示可用的类。

Phase 5: iOS Project Reconfiguration

阶段5:iOS项目重新配置

5.1 Get migration command

5.1 获取迁移命令

Build the CocoaPods workspace to obtain the migration command:
bash
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:
bash
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:
bash
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.
构建CocoaPods工作区以获取迁移命令:
bash
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'
构建输出会包含类似以下的命令:
bash
XCODEPROJ_PATH='/path/to/project/iosApp.xcodeproj' GRADLE_PROJECT_PATH=':shared' '/path/to/project/gradlew' -p '/path/to/project' ':shared:integrateEmbedAndSign' ':shared:integrateLinkagePackage'
运行该命令,它会修改
.xcodeproj
以在构建期间触发
embedAndSignAppleFrameworkForXcode
integrateLinkagePackage
是一次性设置任务 — 无需将其添加为构建阶段。如果
integrateEmbedAndSign
被跳过,请检查第1阶段步骤1.2中的EmbedAndSign禁用代码 — 先移除这些代码,然后重新运行命令。
验证
embedAndSignAppleFrameworkForXcode
已激活:
运行集成命令后,检查
project.pbxproj
中的构建阶段脚本。如果
embedAndSignAppleFrameworkForXcode
被注释(前缀为
#
),请取消注释。
integrateLinkagePackage
任务会在
<iosDir>/
下生成
_internal_linkage_SwiftPMImport/
目录 — 这是一个本地Swift包,镜像您的
products
列表,确保SPM库被链接到最终二进制文件中。
运行集成任务后,禁用用户脚本沙箱
ENABLE_USER_SCRIPT_SANDBOXING = NO
)。Xcode 16+默认启用该功能,会阻止Gradle构建阶段写入项目目录:
bash
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' "$XCODEPROJ_PATH/project.pbxproj"
如果该设置不存在(Xcode默认值为YES),请在应用target的
buildSettings
部分添加
ENABLE_USER_SCRIPT_SANDBOXING = NO;
。然后重启Gradle守护进程:
./gradlew --stop
替代方案(如果xcodebuild方法失败): 请参阅troubleshooting.md中的“Manual Integration Command Discovery”章节,获取用于发现路径并直接运行集成任务的备用脚本。

5.2 Update Crashlytics dSYM upload script (if applicable)

5.2 更新Crashlytics dSYM上传脚本(如适用)

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.
如果项目使用FirebaseCrashlytics且存在dSYM上传运行脚本阶段(第1阶段步骤10已识别),请将脚本路径从
${PODS_ROOT}/FirebaseCrashlytics/upload-symbols
更新为
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"
。有关完整脚本和输入文件列表,请参阅troubleshooting.md中的“Firebase Crashlytics: dSYM Upload Script”章节,以及common-pods-mapping.md

5.3 Deintegrate CocoaPods

5.3 移除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.
bash
cd /path/to/iosApp
pod deintegrate
rm -rf Podfile Podfile.lock Pods/
选项A:完全移除(如果CocoaPods仅用于KMP依赖):
删除文件前,运行
git status --short
并验证路径。如果不确定,可先将文件移至备份目录,而非直接删除。
bash
cd /path/to/iosApp
pod deintegrate
rm -rf Podfile Podfile.lock Pods/

Remove the workspace that matches your app xcodeproj name

删除与应用xcodeproj同名的workspace

XCODEPROJ_NAME=$(basename "$(find . -maxdepth 1 -name "*.xcodeproj" -type d | grep -v Pods | head -1)" .xcodeproj) rm -rf "${XCODEPROJ_NAME}.xcworkspace"
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 ..
cd ..

Remove the migrated module podspec only (for example, shared.podspec)

仅移除已迁移模块的podspec(例如shared.podspec)

If unknown, list candidates and remove the matching one explicitly:

如果不确定,列出候选文件并明确删除匹配的文件:

ls -1 *.podspec
ls -1 *.podspec

rm -f shared.podspec

rm -f shared.podspec


This 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](references/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:

```ruby
target 'iosApp' do
  # Remove this line:
  pod 'shared', :path => '../shared'
  # Keep other non-KMP pods
end
bash
cd /path/to/iosApp && pod install
Tip: 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.

如果`pod deintegrate`不可用,请参阅[troubleshooting.md](references/troubleshooting.md)中的“Manual CocoaPods Deintegration from pbxproj”章节,获取需要移除的所有引用列表。同时从`.gitignore`中移除`Pods/`,并删除`.xcworkspace`目录。

**选项B:部分移除**(如果项目仍有非KMP的CocoaPods依赖):

仅从`Podfile`中移除KMP pod行,然后重新运行pod install:

```ruby
target 'iosApp' do
  // 移除此行:
  pod 'shared', :path => '../shared'
  // 保留其他非KMP pod
end
bash
cd /path/to/iosApp && pod install
提示: 考虑将剩余的pod也迁移至SPM — 大多数主流iOS库原生支持SPM。通过Xcode的File → Add Package Dependencies添加,待所有pod替换完成后再完全移除CocoaPods。

5.4 Manual integration (if automatic fails)

5.4 手动集成(如果自动集成失败)

See troubleshooting.md § "Manual Xcode Integration Steps" for the 5-step manual setup (build phase, sandboxing, linkage package).

请参阅troubleshooting.md中的“Manual Xcode Integration Steps”章节,获取5步手动设置流程(构建阶段、沙箱、链接包)。

Phase 6: Remove CocoaPods from Gradle

阶段6:从Gradle中移除CocoaPods

Now that the iOS project is reconfigured, remove the CocoaPods plugin and block:
iOS项目重新配置完成后,移除CocoaPods插件和代码块:

6.1 Remove CocoaPods plugin

6.1 移除CocoaPods插件

kotlin
plugins {
    // REMOVE: kotlin("native.cocoapods")
    alias(libs.plugins.kotlinMultiplatform)  // Keep
}
kotlin
plugins {
    // 移除:kotlin("native.cocoapods")
    alias(libs.plugins.kotlinMultiplatform)  // 保留
}

6.2 Remove cocoapods block

6.2 移除cocoapods代码块

Delete the entire
cocoapods { ... }
block from
build.gradle.kts
. The
swiftPMDependencies {}
block and
binaries.framework {}
configuration added in Phase 3 replace it.
build.gradle.kts
中删除整个
cocoapods { ... }
代码块。第3阶段添加的
swiftPMDependencies {}
代码块和
binaries.framework {}
配置会替代它。

6.3 Remove deprecated gradle.properties entries

6.3 移除已弃用的gradle.properties条目

If found in Phase 1.1, remove from
gradle.properties
:
properties
undefined
如果第1.1阶段找到该条目,请从
gradle.properties
中移除:
properties
undefined

REMOVE — no longer needed after migrating away from CocoaPods (KT-64096)

移除 — 迁移出CocoaPods后不再需要(KT-64096)

kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
undefined
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
undefined

6.4 Clean up CocoaPods-related extras

6.4 清理与CocoaPods相关的额外代码

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.

回顾第1阶段步骤11识别的额外代码。Podspec元数据、
noPodspec()
、CocoaPods任务钩子、
Pods.xcodeproj
补丁代码无需用户确认即可安全移除。非标准pod配置(
extraOpts
moduleName
)、自定义cinterop
defFile
设置、CocoaPods特定的编译器/链接器标志需要分析 — 如果不确定SPM是否会自动处理,请咨询用户。
有关完整分类列表和示例,请参阅cocoapods-extras-patterns.md

Phase 7: Verification

阶段7:验证

7.1 Build Gradle project

7.1 构建Gradle项目

Build the migrated module to verify the migration succeeded:
bash
./gradlew :moduleName:build
构建已迁移的模块以验证迁移成功:
bash
./gradlew :moduleName:build

7.2 Link framework

7.2 链接Framework

bash
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm64
bash
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm64

7.3 Build iOS/macOS Xcode project

7.3 构建iOS/macOS Xcode项目

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):
bash
cd /path/to/iosApp
Gradle构建成功后,构建Xcode项目。如果所有CocoaPods已移除(选项A),使用
-project *.xcodeproj
;如果仍有非KMP的CocoaPods依赖(选项B),使用
-workspace *.xcworkspace
bash
cd /path/to/iosApp
// 发现scheme并构建(根据需要替换-project/-workspace;macOS使用-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 build
如果
checkSandboxAndWriteProtection
失败
— 第5.1阶段未禁用沙箱。返回并应用第5.1阶段的沙箱修复,然后重试。
如果迁移前构建未验证(使用了第1.0阶段的备选方案),请向用户发出警告:
注意:迁移前的构建未完全验证。如果现在出现构建错误,部分可能是迁移前已存在的问题,与本次迁移无关。请将错误与迁移前的构建输出对比,以区分迁移相关问题与原有问题。

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 build

**If `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.
请勿回滚迁移。 阅读错误日志,重新检查第2-6阶段,参考troubleshooting.md。如果不确定,请向用户提供选项,不要静默撤销迁移操作。

If the build fails

阶段8:迁移报告

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.

迁移完成后(无论成功与否),在项目根目录编写完整的
MIGRATION_REPORT.md
。使用migration-report-template.md中的模板。
报告必须包含:
  1. 迁移前状态 — CocoaPods依赖(名称、版本、
    linkOnly
    状态)、Framework配置、
    cocoapods.*
    导入语句、非KMP pod、特殊配置
  2. 迁移步骤 — 每个阶段的具体变更,非平凡变更需提供变更前后的代码片段
  3. 导入转换 — 所有导入变更的表格,明确标记保留的
    cocoapods.*
    导入语句及其对应的捆绑klib
  4. 遇到的错误 — 结构化的
    Error #N
    条目:阶段、具体症状、根本原因、修复方案、通用标识
  5. 非平凡决策
    isStatic
    变更、保留的导入语句、Framework搜索路径、权衡取舍
  6. 变更文件列表 — 按类型分组的完整文件列表(Gradle、Kotlin、Xcode、新增、删除)

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:
  1. Pre-Migration State — CocoaPods dependencies (name, version,
    linkOnly
    ), framework config,
    cocoapods.*
    imports, non-KMP pods, atypical configuration
  2. Migration Steps — exact changes per phase with before/after snippets for non-trivial changes
  3. Import Transformations — table of every import change, clearly marking preserved
    cocoapods.*
    imports and which bundled klib provides them
  4. Errors Encountered — structured
    Error #N
    entries: phase, exact symptom, root cause, fix, generalizable flag
  5. Non-Trivial Decisions
    isStatic
    changes, preserved imports, framework search paths, trade-offs
  6. Files Changed — complete list grouped by type (Gradle, Kotlin, Xcode, created, deleted)

  • DSL参考 - swiftPMDependencies完整语法
  • 常见Pod映射 - Pod到SPM的映射表
  • CocoaPods额外代码模式 - CocoaPods兼容代码的检测与清理模式
  • 故障排除 - 问题、解决方案、回滚
  • 迁移报告模板 - 迁移后报告模板

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