关于 kotlin-tooling-cocoapods-spm-migration
帮助 Kotlin Multiplatform 项目从 CocoaPods Gradle 插件迁移到 `swiftPMDependencies {}` DSL。流程先并行配置 SwiftPM 与 CocoaPods,再指导项目重配、清理和验证。
主要功能
- 01分析 CocoaPods 与项目配置
- 02在保留 CocoaPods 时添加 SwiftPM
- 03转换 Kotlin 导入语句
- 04重新配置 iOS 项目
- 05移除 CocoaPods 并验证构建
适用场景
- 01替换 KMP 项目中的 CocoaPods 依赖
- 02将 iOS 框架依赖迁移到 SwiftPM
- 03核对导入与 Podfile 映射
- 04迁移后验证 Gradle 和 Xcode 构建
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kotlin/ kotlin-agent-skills --skill kotlin-tooling-cocoapods-spm-migration安装后在 Claude Code 中输入/kotlin-tooling-cocoapods-spm-migration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/cocoapods-extras-patterns.mdcommon-pods-mapping.mddsl-reference.mdmigration-report-template.mdtroubleshooting.md详细介绍
面向 KMP 的 CocoaPods 到 SwiftPM 迁移
将 Kotlin Multiplatform 项目从 kotlin("native.cocoapods") 迁移到 swiftPMDependencies {} DSL。
要求
- Kotlin:2.4.0-Beta2 或更高版本(首个公开支持
swiftPMDependencies的版本,可在 Maven Central 获取) - Xcode:16.4 或 26.0+
- iOS 部署目标:建议 16.0+
迁移概述
重要:在第 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 |
阶段 1:迁移前分析
1.0 验证项目能否构建
开始迁移之前,找出要迁移的模块,并确认它能够成功编译。
-
找到使用 CocoaPods 的模块——查找包含
cocoapods的build.gradle.kts文件:grep -rl "cocoapods" --include="build.gradle.kts" .从路径中提取模块名称(例如,
./shared/build.gradle.kts→ 模块名称为shared)。注意:多个模块可能使用 CocoaPods,请记录所有模块。通常,只有生成链接到 iOS 应用的框架的模块需要配置swiftPMDependencies;其他模块只需移除 CocoaPods(第 6 阶段)。 -
编译 Kotlin 代码——运行该模块的 Kotlin 编译任务,以验证 Kotlin 源代码能够编译:
./gradlew :moduleName:compileKotlinIosSimulatorArm64将
moduleName替换为模块目录名称(例如,:shared:compileKotlinIosSimulatorArm64)。这比完整的build更快(完整构建还会运行 release 链接),并且足以验证 Kotlin 代码是否正确。 -
构建 iOS 应用(可选)——尝试找到 Xcode 项目并进行构建,以确认整个应用能够编译:
# Find the Xcode project find . -name "*.xcworkspace" -not -path "*/Pods/*" -maxdepth 2 # Build (replace scheme name with the actual app scheme) cd /path/to/iosApp xcodebuild -workspace *.xcworkspace -scheme "<AppScheme>" -destination 'generic/platform=iOS Simulator' ARCHS=arm64如果用户想跳过 Xcode 构建,或找不到 Xcode 项目,则无需构建,直接继续即可——第 2 步的 Kotlin 编译足以继续迁移。
-
如果 Kotlin 编译失败,请让用户选择:
- 提供正确的 Gradle 命令,以验证该模块能否构建;或
- 确认该模块当前处于可正常工作的状态,可以安全地继续
如果用户确认但未提供构建命令,请记录迁移前的构建无法验证,并在迁移结束时(第 7 阶段)提醒用户。
1.0a 确认支持 Swift 导入的 Kotlin 版本
从 gradle/libs.versions.toml(或 build.gradle.kts)中读取当前 Kotlin 版本。
如果项目已使用 Kotlin 2.4.0-Beta2 或更高版本 → 记录该版本并跳过第 2.1 阶段(无需更改版本)。
如果项目使用较早的 Kotlin 版本 → 第 2.1 阶段会将其升级到 2.4.0-Beta2(首个公开支持 swiftPMDependencies 的版本,可在 Maven Central 获取——无需自定义仓库)。提醒用户:“⚠️ Kotlin 版本跨度较大——跨次要版本升级可能会引入与此次迁移无关的破坏性变更。建议先更新版本并确认构建成功,然后重新运行此次迁移。”用户确认后即可继续。
1.1 检查已弃用的 CocoaPods 变通属性
在 gradle.properties 中搜索已弃用的属性:
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
此属性是一个变通方案(参见 KT-64096),用于同时使用 embedAndSign 和 CocoaPods 依赖项的项目。它会抑制有关不受支持配置的错误;此类配置可能导致运行时崩溃或符号重复。迁移到 SwiftPM 导入后,不再需要此属性,且必须在第 6 阶段将其移除。如果发现该属性,请记录下来。
1.2 检查是否有禁用 EmbedAndSign 的代码
在所有 build.gradle.kts 文件中搜索禁用 EmbedAndSign 任务的代码(例如,TaskGraph.whenReady 过滤器、tasks.matching 代码块)。这是 CocoaPods 时代的变通方案,会破坏迁移,因为它也会禁用 integrateEmbedAndSign(Phase 5 需要此任务)。记录所有此类代码——必须在 Phase 6 移除,也可能需要提前移除。有关具体模式,请参阅 troubleshooting.md § "integrateEmbedAndSign Skipped"。
1.3 检查是否有捆绑 cinterop klib 的第三方 KMP 库
一些 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.* 语句进行交叉比对。标记所有匹配项——这些导入不会在 Phase 4 转换。
如果不确定第三方 KMP 库是否捆绑了 cinterop klib,请检查项目中是否有该库的 linkOnly = true pod 依赖——这是该库为这些类提供自身 klib 的有力迹象。
有关如何检查 klib 内容并验证捆绑的绑定,请参阅 troubleshooting.md § "Third-Party KMP Libraries with Bundled Klibs"。
查找并记录:
- CocoaPods 配置 - 在
build.gradle.kts文件中搜索cocoapods - Pod 依赖项 - 从
cocoapods {}块中提取 Pod 名称和版本 - 框架配置 - 记录
cocoapods.framework {}中的baseName、isStatic和部署目标 - linkOnly Pod - 记录声明了
linkOnly = true的 Pod。常见的模式有两种:- KMP 包装库(例如
dev.gitlive:firebase-*):包装库提供 Kotlin API,而 Pod 仅用于链接。详情及相关影响请参阅 common-pods-mapping.md。 - 多模块项目:由于子模块已为该 Pod 提供 cinterop 绑定,使用该子模块的模块会声明
linkOnly = true。在 SwiftPM 中,swiftPackage()声明应仅放在直接使用该 Pod 的子模块中。使用该子模块的模块不得重新声明相同的包——只需一个不包含这些包的swiftPMDependencies {}块(如果所有 Pod 都是linkOnly,也可以是空块)。导入命名空间的影响:使用该子模块的模块导入来自子模块swiftPMDependencies的 SPM 类时,导入路径会使用子模块的组名和模块名作为命名空间(请参阅阶段 4 的导入命名空间公式)。
- KMP 包装库(例如
- Kotlin 导入 - 查找所有
import cocoapods.*语句。与步骤 1.3 交叉核对,确定哪些导入来自捆绑的 klib(必须保留),哪些来自直接 Pod cinterop(必须转换)。 - 将 Pod 映射到 SPM - 请参阅 common-pods-mapping.md
- 定位 iOS 项目目录 - 找到包含
Podfile和.xcworkspace的目录:
记录此路径(例如find . -name "Podfile" -type fiosApp/、ios/或项目根目录)——阶段 5 需要用到 - 检查是否存在非 KMP 的 CocoaPods 依赖 - 确定项目是否使用 CocoaPods 管理 KMP 以外的依赖。这会影响阶段 5 的清理策略。
- 将 Podfile 与
cocoapods {}块交叉核对 - 解析Podfile,并将其中的 Pod 条目与 Gradlecocoapods {}块中声明的 Pod 进行比较。记录所有存在于Podfile中、但未列在cocoapods {}中的依赖。这些仅在 Podfile 中声明的依赖仍会通过 CocoaPods 链接到应用,必须迁移到swiftPMDependencies——遗漏它们会导致运行时出现难以排查的链接错误。 - 检查 Xcode 构建阶段 - 打开
.xcodeproj中的project.pbxproj,搜索 Gradle 构建阶段脚本。检查其中是否存在但被注释掉(前缀为#)的embedAndSignAppleFrameworkForXcode。如果已注释,必须在阶段 5 取消注释——integrateEmbedAndSign任务可能会自动处理,也可能不会。 - 检查是否已有 Crashlytics dSYM 上传脚本 - 如果使用 FirebaseCrashlytics,请在
project.pbxproj中搜索 dSYM 上传 Shell 脚本阶段。记录其当前路径(CocoaPods 时代的脚本会引用${PODS_ROOT}/FirebaseCrashlytics/upload-symbols)。阶段 5 必须将其更新为 SPM 路径。 - 识别构建脚本中与 CocoaPods 相关的额外配置 - 在所有
build.gradle.kts文件中搜索标准cocoapods {}块以外的 CocoaPods 变通方案(例如挂接到podInstall的自定义任务、对Pods.xcodeproj的修补、podspec 元数据、extraSpecAttributes、noPodspec()等)。完整模式列表请参阅 cocoapods-extras-patterns.md。记录所有发现——这些内容将在阶段 6 处理。
阶段 2:Gradle 配置
**重要范围说明:**本次迁移期间请勿升级 Gradle wrapper 版本、更新 KSP 或更新任何其他依赖项。这些属于单独事项,不在本次范围内。只更改以下列出的内容。
2.1 更新 Kotlin 版本
如果项目已使用 Kotlin 2.4.0-Beta2 或更高版本(已在 Phase 1.0a 中记录),请跳过此步骤。
在 gradle/libs.versions.toml 中更新为 2.4.0-Beta2(或支持 Swift Import 的最新可用版本):
[versions]
kotlin = "2.4.0-Beta2"
2.4.0-Beta2 已在 Maven Central 上提供,无需自定义仓库。
Phase 3:添加 swiftPMDependencies(保留 CocoaPods)
暂时不要移除 cocoapods {} 代码块或 kotlin("native.cocoapods") 插件。 在现有 CocoaPods 配置旁添加 swiftPMDependencies {}。
3.1 添加 group 属性
group = "org.example.myproject" // Required for import namespace
Compose Resources 警告: 如果项目使用 Compose Multiplatform 资源(org.jetbrains.compose 插件或 compose.resources),group 属性也会用作生成的资源访问器(例如 Res.string.*、Res.drawable.*)的命名空间。如果 build.gradle.kts 中已存在 group,请勿更改。如果这是首次添加 group,请提醒用户,项目中现有 Compose 资源访问器调用点的命名空间将发生变化,可能需要更新。
3.2 在 cocoapods 旁添加 swiftPMDependencies 块
对于每个 Pod 依赖,添加对应的 SwiftPM 包声明。使用 common-pods-mapping.md 将每个 Pod 映射到其 SPM 包 URL、产品名称和 importedClangModules。
**保留版本:**迁移期间切勿升级依赖版本。使用 cocoapods {} 块中指定的完全相同版本。更改版本可能会解析到不同的库构建版本,导致 cinterop API 出现问题(符号被移除、签名发生变化),并引入与迁移本身无关的问题。
| CocoaPods 版本规范 | SPM 对应写法 | 示例 |
|---|---|---|
version = "1.2.3"(精确) | version = "1.2.3"(简单)或 exact("1.2.3")(类型化) | pod("GoogleMaps") { version = "10.3.0" } → version = "10.3.0" |
version = "~> 1.2"(乐观) | version = "1.2.0"(简单)或 from("1.2.0")(类型化) | pod("FirebaseAuth") { version = "~> 12.5" } → version = "12.5.0" |
| 未指定版本 | 询问用户要锁定哪个版本 | 询问用户要使用哪个版本 |
**两种 API 形式:**该 DSL 提供简单字符串 API 和类型化 API。大多数包请使用简单字符串 API:
swiftPackage(url = "https://github.com/owner/repo.git", version = "1.0.0", products = listOf("ProductName"))
简单 API 会自动将 importedClangModules 默认为 products 列表。仅当需要精确锁定版本、平台约束或显式控制 Clang 模块时,才使用类型化 API(带有 url()、exact()、product() 包装器)。类型化 API 详见 dsl-reference.md。
关键概念:products = SPM 产品名称(控制链接)。importedClangModules = 用于 cinterop 绑定的 Clang 模块名称(仅当 discoverClangModulesImplicitly = false 时使用)。discoverClangModulesImplicitly 默认为 true(为所有 Clang 模块生成绑定);当传递依赖中的 C/C++ 模块导致 cinterop 失败时(Firebase、gRPC),将其设为 false,然后显式列出所需模块。
**重要:**SPM 产品名称和 Clang 模块名称并不总是相同。请务必查阅 common-pods-mapping.md 获取正确值。
**仅存在于 Podfile 中的依赖:**如果 Phase 1 的步骤 9 发现某些依赖存在于 Podfile 中,但不在 Gradle 的 cocoapods {} 块中,也必须将它们作为 products 条目添加到 swiftPMDependencies。尽管 KMP 模块未声明这些依赖,CocoaPods 仍将它们链接到应用中,因此应用构建时可能需要它们。查找每个仅存在于 Podfile 中的 Pod 对应的 SPM 包 URL,并将其作为 swiftPackage() 添加,至少包含其 products。如果通过 cinterop 使用了其中任何 Pod(检查是否存在引用它们的 import cocoapods.* 语句),还要添加 importedClangModules。
不要让同一库套件同时使用 CocoaPods 和 SPM。共享仓库的库(例如所有 Firebase 产品)会共享传递依赖。部分产品通过 CocoaPods 链接、其余产品通过 SPM 链接,会导致符号重复或冲突,并在运行时引发 dyld 崩溃。迁移此类套件时,请一次性将该套件中的所有 Pod 转移到 SPM,包括 Kotlin 不会直接使用的纯 Swift Pod。纯 Swift Pod 作为 products 条目添加(无需 importedClangModules)。添加新产品后,重新运行 integrateLinkagePackage 以重新生成链接关系 Swift 包。
kotlin {
// Keep existing targets
iosArm64()
iosSimulatorArm64()
iosX64()
swiftPMDependencies {
iosMinimumDeploymentTarget = "16.0"
swiftPackage(
url = "https://github.com/owner/repo.git",
version = "1.0.0",
products = listOf("ProductName"),
)
}
cocoapods {
// ... keep existing cocoapods block for now
}
}
3.3 将框架配置移出 cocoapods 块
如果 cocoapods 块包含 framework {} 配置,请将其移至每个目标上的 binaries API。建议设置 isStatic = true——动态框架在通过 SwiftPM 导入时存在已知边缘情况,可能导致链接器错误、dyld 崩溃或重复类警告:
listOf(iosArm64(), iosSimulatorArm64(), iosX64()).forEach { iosTarget ->
iosTarget.binaries.framework { baseName = "Shared"; isStatic = true }
}
如果 cocoapods.framework {} 块包含 export(project(...)) 或 transitiveExport = true,请在新的 binaries.framework {} 块中保留这些配置——对于导出子模块的多模块项目,它们至关重要。
3.4 处理 dev.gitlive/firebase-kotlin-sdk 及类似的 CocoaPods 时代 KMP 封装库
如果项目使用 dev.gitlive:firebase-* 或类似的 KMP 封装库,还需要执行以下两个步骤:
A. 切换到 isStatic = true — 动态框架 + Firebase SPM 会导致运行时 dyld 崩溃。切换后:重新运行 integrateLinkagePackage,移除任何「Embed Frameworks」复制阶段,并将链接器标志移至 OTHER_LDFLAGS。
B. 添加框架搜索路径 — 在 build.gradle.kts 中添加条件式 -F linkerOpts,并在 Xcode 项目中添加对应的 FRAMEWORK_SEARCH_PATHS。
代码片段和完整产品列表请参阅 common-pods-mapping.md § dev.gitlive 和 troubleshooting.md。
3.5 添加选择启用注解
swiftPackage() 和 localSwiftPackage() DSL 函数带有 @ExperimentalKotlinGradlePluginApi 注解。添加此选择启用,以抑制编译器警告:
@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
将此内容放在每个调用 swiftPackage() 或 localSwiftPackage() 的 build.gradle.kts 文件顶部。
还要为 Kotlin 源文件添加 cinterop 选择启用:
kotlin.compilerOptions {
optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
}
完整的 DSL 参考请参阅 dsl-reference.md。
阶段 4:更新 Kotlin 源代码
导入命名空间公式
swiftPMImport.<group>.<module>.<ClassName>
Where:
- group: build.gradle.kts `group` property of the MODULE THAT DECLARES the swiftPMDependencies, dashes (-) → dots (.)
- module: Gradle module name of the MODULE THAT DECLARES the swiftPMDependencies, dashes (-) → dots (.), underscores (_) preserved as-is
- ClassName: Objective-C class name (FIR* for Firebase, GMS* for Google Maps)
命名空间使用声明模块的 group+name,而不是导入模块的。 这是 Agent 最常犯的错误。当模块 A 依赖模块 B,且模块 B 声明了 swiftPMDependencies 时,模块 A 使用模块 B 的 group 和模块名导入 SPM 类,而不是模块 A 的。
转换示例 — 单模块
// 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
转换示例 — 多模块(linkOnly pod)
当使用方模块通过 pod("GoogleMaps") { linkOnly = true } 声明依赖,而子模块提供 cinterop 绑定时:
// composeApp/App.kt — composeApp depends on :google-maps Gradle module
// google-maps has group = "org.jetbrains.kotlin.google-maps", module name = "google-maps"
// google-maps declares swiftPMDependencies with GoogleMaps
// BEFORE (in composeApp):
import cocoapods.GoogleMaps.GMSServices
// AFTER — uses google-maps module's namespace, NOT composeApp's namespace:
import swiftPMImport.org.jetbrains.kotlin.google.maps.google.maps.GMSServices
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^
// google-maps's group (dashes→dots) google-maps's module name (dashes→dots)
导入路径不会使用 composeApp 的 group(org.jetbrains.kotlin.compose.sample)。它使用声明模块的标识,因为 cinterop 绑定是在该模块中生成的。
导入扁平化: Clang 模块名(例如 FirebaseFirestoreInternal、FirebaseAuth)会从导入路径中消失——无论类来自哪个库,都会扁平化到相同的 swiftPMImport.<group>.<module> 前缀下。例如,cocoapods.FirebaseAuth.FIRAuth 和 cocoapods.FirebaseFirestoreInternal.FIRFirestore 都会变为 swiftPMImport.<group>.<module>.FIRAuth 和 swiftPMImport.<group>.<module>.FIRFirestore。
保留捆绑的 Klib 导入
重要: 不要替换解析到第三方 KMP 库捆绑的 cinterop klib 的
cocoapods.*导入(在阶段 1 步骤 1.3 中识别)。这些导入必须保持原样——cocoapods前缀是已发布库 klib 中的包命名空间,并非实际的 CocoaPods 依赖。swiftPMDependencies cinterop 生成器会跳过依赖 klib 已提供的模块,因此这些类对应的swiftPMImport.*会导致「Unresolved reference」错误。
示例(项目使用 KMPNotifier):
// KEEP — resolves to kmpnotifier's bundled cinterop klib
import cocoapods.FirebaseMessaging.FIRMessaging
批量替换
使用正则表达式在所有 Kotlin 源文件中查找并替换,但排除阶段 1 步骤 1.3 中识别的导入。在多模块项目中,针对每个模块分别使用该模块的 group 和名称运行替换:
Find: cocoapods\.\w+\.
Replace: swiftPMImport.<declaring.module.group>.<declaring.module.name>.
批量替换后,手动还原所有应保留的 cocoapods.* 导入(来自捆绑的 klib)。
查找正确的导入路径: 运行 ./gradlew :moduleName:compileKotlinIosSimulatorArm64——错误信息会显示可用的类。
阶段 5:重新配置 iOS 项目
5.1 获取迁移命令
构建 CocoaPods 工作区以获取迁移命令:
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'
构建输出中会包含类似这样的命令:
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,请检查是否存在 EmbedAndSign 禁用项(第 1 阶段第 1.2 步)——先将其移除,然后重新运行。
**确认 embedAndSignAppleFrameworkForXcode 已启用:**运行集成后,检查 project.pbxproj 中的构建阶段脚本。如果 embedAndSignAppleFrameworkForXcode 被注释掉(前缀为 #),请取消注释。
integrateLinkagePackage 任务会在 <iosDir>/ 下生成 KotlinMultiplatformLinkedPackage/,这是一个本地 Swift 包,它会镜像你的 products 列表,并确保 SPM 库链接到最终二进制文件中。
运行集成任务后,请在 .xcodeproj 中禁用 User Script Sandboxing(ENABLE_USER_SCRIPT_SANDBOXING = NO)。Xcode 16+ 默认启用此功能,会阻止 Gradle 构建阶段写入项目目录:
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' "$XCODEPROJ_PATH/project.pbxproj"
如果缺少此设置(Xcode 默认值为 YES),请在应用目标的 buildSettings 部分添加 ENABLE_USER_SCRIPT_SANDBOXING = NO;。然后重启 Gradle 守护进程:./gradlew --stop
**替代方案(如果 xcodebuild 方法失败):**请参阅 troubleshooting.md 中的 §“手动查找集成命令”,了解用于查找路径并直接运行集成任务的备用脚本。
5.2 更新 Crashlytics dSYM 上传脚本(如适用)
如果项目使用 FirebaseCrashlytics,并且包含 dSYM 上传运行脚本阶段(在第 1 阶段第 11 步中确认),请将脚本路径从 ${PODS_ROOT}/FirebaseCrashlytics/upload-symbols 更新为 "${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"。完整脚本和输入文件列表请参阅 troubleshooting.md 中的 §“Firebase Crashlytics:dSYM 上传脚本”以及 common-pods-mapping.md。
5.3 解除 CocoaPods 集成
选项 A:完全解除集成(如果 CocoaPods 仅用于 KMP 依赖项):
删除文件之前,运行 git status --short 并确认路径。如果不确定,请先将文件移至备份位置,而不是立即删除。
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.podspec
此清理代码段独立完整,不会假定你当前的 shell 中仍有早前一次性迁移命令使用的 XCODEPROJ_PATH 或 GRADLE_PROJECT_PATH。
如果无法使用 pod deintegrate,请参阅 troubleshooting.md 中的 §“手动从 pbxproj 解除 CocoaPods 集成”,了解需要移除的完整引用列表。还要从 .gitignore 中移除 Pods/,并删除 .xcworkspace 目录。
选项 B:部分移除(如果仍有其他非 KMP 的 CocoaPods 依赖项):
仅从 Podfile 中移除 KMP pod 行,然后重新运行 pod install:
target 'iosApp' do
# Remove this line:
pod 'shared', :path => '../shared'
# Keep other non-KMP pods
end
cd /path/to/iosApp && pod install
**提示:**也可以考虑将剩余的 pods 迁移到 SPM——大多数常用 iOS 库都原生支持 SPM。在 Xcode 中通过“File → Add Package Dependencies”添加它们,然后在所有 pods 都替换后彻底解除 CocoaPods 集成。
5.4 手动集成(自动集成失败时)
请参阅 troubleshooting.md 中的 §“手动 Xcode 集成步骤”,了解包含 5 个步骤的手动设置(构建阶段、沙盒、链接包)。
第 6 阶段:从 Gradle 中移除 CocoaPods
现在 iOS 项目已重新配置,请从所有使用了 CocoaPods 的模块中移除 CocoaPods 插件和代码块(而不仅仅是主要模块):
6.1 移除 CocoaPods 插件
从每个使用了 CocoaPods 的模块的 plugins {} 中移除 kotlin("native.cocoapods") 或 alias(libs.plugins.kotlinCocoapods):
plugins {
// REMOVE: kotlin("native.cocoapods") or alias(libs.plugins.kotlinCocoapods)
alias(libs.plugins.kotlinMultiplatform) // Keep
}
如果所有模块都已迁移,还要从 gradle/libs.versions.toml 中移除 kotlinCocoapods 插件条目:
[plugins]
# REMOVE: kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
6.2 移除 cocoapods 块
从 build.gradle.kts 中删除整个 cocoapods { ... } 块。Phase 3 中添加的 swiftPMDependencies {} 块和 binaries.framework {} 配置会取代它。还要从模块目录中删除所有生成的 .podspec 文件(例如 shared/shared.podspec)——这些文件由 CocoaPods 插件生成,不再需要。
6.3 移除已弃用的 gradle.properties 条目
如果在 Phase 1.1 中发现了以下条目,请从 gradle.properties 中移除:
# REMOVE — no longer needed after migrating away from CocoaPods (KT-64096)
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
6.4 清理 CocoaPods 相关的其他内容
检查 Phase 1 步骤 12 中发现的其他内容。Podspec 元数据、noPodspec()、CocoaPods 任务钩子和 Pods.xcodeproj 补丁代码都可以安全移除,无需咨询用户。非标准 pod 配置(extraOpts、moduleName)、自定义 cinterop defFile 设置以及 CocoaPods 专用编译器/链接器标志需要分析——如果不确定 SPM 是否会自动处理,请咨询用户。
完整的分类列表和示例请参见 cocoapods-extras-patterns.md。
Phase 7:验证
在应用成功构建之前,绝不要停止。 此阶段是迭代式的——如果任何步骤失败,请诊断错误并修复(参见 troubleshooting.md,并重新检查 Phase 2–6),然后重新运行失败的步骤。重复此过程,直到构建成功,或确认问题明显超出迁移范围(例如,迁移前就存在的 bug 或无关的工具问题)。在构建成功之前,绝不要编写迁移报告(Phase 8)。
7.1 编译 Kotlin 代码
编译已迁移的模块,以验证 Kotlin 源代码正确无误:
./gradlew :moduleName:compileKotlinIosSimulatorArm64
如果编译因未解析的引用而失败,请检查导入转换(Phase 4)和 SwiftPM 依赖声明(Phase 3.2)。常见原因包括:缺少 importedClangModules、Clang 模块名称错误、保留了本应转换的捆绑 klib 导入(或反之)。
7.2 链接框架
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm64
如果链接失败,请检查是否声明了所有必需的 SPM 产品,以及版本约束是否能正确解析。缺少符号的链接错误通常表明某个产品未添加到 swiftPMDependencies,或版本不匹配导致 API 被移除。
7.3 构建 iOS/macOS Xcode 项目
Gradle 步骤成功后,构建 Xcode 项目以验证整个应用能够编译。如果已移除所有 CocoaPods(选项 A),请使用 -project *.xcodeproj;如果仍有非 KMP CocoaPods(选项 B),请使用 -workspace *.xcworkspace:
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 build
如果 checkSandboxAndWriteProtection 失败——说明未在 Phase 5.1 中禁用沙盒。返回并应用 Phase 5.1 中的沙盒修复,然后重试。
如果迁移前的构建未经验证(使用了 Phase 1.0 的备用方案),请提醒用户:
注意:无法完整验证迁移前的构建。如果现在出现构建错误,其中一些可能是迁移之前就存在、与迁移无关的问题。请将这些错误与迁移前的构建输出进行比较,以区分迁移问题和已有问题。
如果构建失败
不要回退迁移。 阅读错误日志,重新检查 Phase 2–6,并参阅 troubleshooting.md。如果不确定,请向用户提供选项——不要悄悄撤销迁移工作。修复问题后,重新运行失败的验证步骤。持续迭代,直到构建成功。
阶段 8:迁移报告
构建成功后,在项目根目录中编写一份完整的 MIGRATION_REPORT.md。使用 migration-report-template.md 中的模板。
报告必须包含:
- 迁移前状态——CocoaPods 依赖项(名称、版本、
linkOnly)、框架配置、cocoapods.*导入、非 KMP Pods、非典型配置 - 迁移步骤——每个阶段的具体变更;对于非简单变更,提供变更前后的代码片段
- 导入转换——列出每项导入变更的表格,明确标记保留的
cocoapods.*导入及其由哪个捆绑的 klib 提供 - 遇到的错误——按结构列出
Error #N条目:阶段、具体症状、根本原因、修复方法、是否可推广 - 非简单决策——
isStatic变更、保留的导入、框架搜索路径、权衡取舍 - 变更文件——按类型分组列出完整清单(Gradle、Kotlin、Xcode、新建、删除)
其他资源
- DSL 参考 - 完整的 swiftPMDependencies 语法
- 常见 Pods 映射 - Pod 到 SPM 的映射表
- CocoaPods 额外配置模式 - CocoaPods 变通方案的检测和清理模式
- 故障排查 - 问题、解决方案、回滚
- 迁移报告模板 - 迁移后的报告模板