kotlin-tooling-cocoapods-spm-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCocoaPods to SwiftPM Migration for KMP
针对KMP的CocoaPods至SwiftPM迁移指南
Migrate Kotlin Multiplatform projects from to DSL.
kotlin("native.cocoapods")swiftPMDependencies {}将Kotlin Multiplatform项目从迁移至 DSL。
kotlin("native.cocoapods")swiftPMDependencies {}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 block and plugin active until Phase 6. The migration adds alongside the existing CocoaPods setup first, reconfigures Xcode, and only then removes CocoaPods.
cocoapods {}swiftPMDependencies {}| Phase | Action |
|---|---|
| 1 | Analyze existing CocoaPods configuration |
| 2 | Update Gradle configuration (repos, Kotlin version) |
| 3 | Add |
| 4 | Transform Kotlin imports |
| 5 | Reconfigure iOS project and deintegrate CocoaPods |
| 6 | Remove CocoaPods plugin from Gradle |
| 7 | Verify Gradle build and Xcode project build |
| 8 | Write MIGRATION_REPORT.md |
重要提示:在完成第6阶段前,请保留代码块和插件处于激活状态。迁移会先在现有CocoaPods配置旁添加,重新配置Xcode,之后再移除CocoaPods。
cocoapods {}swiftPMDependencies {}| 阶段 | 操作 |
|---|---|
| 1 | 分析现有CocoaPods配置 |
| 2 | 更新Gradle配置(仓库、Kotlin版本) |
| 3 | 在现有 |
| 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.
-
Find the module that uses CocoaPods — look forfiles containing
build.gradle.kts:cocoapodsbashgrep -rl "cocoapods" --include="build.gradle.kts" .Extract the module name from the path (e.g.,→ module name is./shared/build.gradle.kts).shared -
Build only that module (avoids building the entire multi-module project):bash
./gradlew :moduleName:buildReplacewith the directory name of the module (e.g.,moduleName).:shared:build -
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).
开始迁移前,确定要迁移的模块并确认其可成功编译。
-
定位使用CocoaPods的模块 — 查找包含的
cocoapods文件:build.gradle.ktsbashgrep -rl "cocoapods" --include="build.gradle.kts" .从路径中提取模块名称(例如→ 模块名称为./shared/build.gradle.kts)。shared -
仅构建该模块(避免构建整个多模块项目):bash
./gradlew :moduleName:build将替换为模块的目录名称(例如moduleName)。:shared:build -
如果目标模块构建失败,请让用户选择:
- 提供验证模块构建的正确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 (or ), record it, and skip Phase 2.2 (no version change needed).
gradle/libs.versions.tomlbuild.gradle.ktsIf 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 as default). Phase 2.1 will add it.
https://packages.jetbrains.team/maven/p/kt/dev - 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., → ), 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.
2.1.02.4.0询问用户:
您的项目是否已使用支持Swift Import的Kotlin版本(swiftPMDependencies DSL)?
如果是 → 从(或)中读取当前Kotlin版本并记录,跳过第2.2阶段(无需更改版本)。
gradle/libs.versions.tomlbuild.gradle.kts如果否 → 询问:
请提供要使用的Kotlin版本(例如"2.4.0"、"2.4.0-Beta1"、"2.4.0-dev-123")。
记录用户提供的版本,然后询问:
该Kotlin版本是否需要自定义Maven仓库(例如JetBrains开发仓库)?
- 是 → 询问仓库URL(默认建议),第2.1阶段将添加该仓库。
https://packages.jetbrains.team/maven/p/kt/dev - 否 → 跳过第2.1阶段(无需自定义仓库)。
最后,检查项目当前的Kotlin版本,对比主版本.次版本与目标版本。如果差异较大(例如 → ),发出警告:"⚠️ Kotlin版本跨级更新 — 跨次版本升级可能引入与本次迁移无关的破坏性变更。建议:先单独升级Kotlin版本,验证构建正常后再重新执行迁移。" 如果用户确认仍要继续,则推进流程。
2.1.02.4.01.1 Check for deprecated CocoaPods workaround property
1.1 检查已弃用的CocoaPods兼容属性
Search for the deprecated property:
gradle.propertiesproperties
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=trueThis property was a workaround (see KT-64096) for projects using 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.
embedAndSign在中搜索已弃用的属性:
gradle.propertiesproperties
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true该属性是针对同时使用和CocoaPods依赖的项目的兼容方案(详见KT-64096),用于抑制关于不支持配置的错误提示,这类错误可能导致运行时崩溃或符号重复。迁移至SwiftPM Import后,该属性不再需要,必须在第6阶段移除。如果找到该属性,请记录。
embedAndSign1.2 Check for EmbedAndSign disablers
1.2 检查EmbedAndSign禁用代码
Search all files for code that disables tasks (e.g., filters, blocks). This is a CocoaPods-era workaround that breaks the migration because (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 § " Skipped" for patterns.
build.gradle.ktsEmbedAndSignTaskGraph.whenReadytasks.matchingintegrateEmbedAndSignintegrateEmbedAndSign在所有文件中搜索禁用任务的代码(例如过滤器、代码块)。这是CocoaPods时代的兼容方案,会破坏迁移流程,因为第5阶段需要的任务也会被禁用。记录所有此类代码 — 必须在第6阶段移除,部分情况可能需要提前移除。有关代码模式,请参阅troubleshooting.md中的“ Skipped”章节。
build.gradle.ktsEmbedAndSignTaskGraph.whenReadytasks.matchingintegrateEmbedAndSignintegrateEmbedAndSign1.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 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 imports for those modules must be kept as-is — they resolve to the third-party library's bundled klib, not to actual CocoaPods.
cocoapods.*cocoapods.*Known libraries with bundled klibs:
cocoapods.*| Library | Maven artifact | Bundled klib namespace | Classes provided |
|---|---|---|---|
| KMPNotifier | | | |
How to detect: Search Gradle dependency declarations for known libraries, then cross-reference their bundled namespaces against the statements found in step 4. Mark any matches — these imports will NOT be transformed in Phase 4.
import cocoapods.*If unsure whether a third-party KMP library bundles cinterop klibs, check if it has a pod dependency in the project — this is a strong indicator that the library provides its own klib for those classes.
linkOnly = trueTo inspect klib contents and verify bundled bindings, see troubleshooting.md § "Third-Party KMP Libraries with Bundled Klibs".
Find and record:
- CocoaPods configuration - Search for in
cocoapodsfilesbuild.gradle.kts - Pod dependencies - Extract pod names, versions from blocks
cocoapods {} - Framework configuration - Record ,
baseName, deployment target fromisStaticcocoapods.framework {} - linkOnly pods - Record pods declared with . These pods provide native linking only — cinterop bindings come from a KMP wrapper library (e.g.,
linkOnly = true). See common-pods-mapping.md for implications.dev.gitlive:firebase-* - Kotlin imports - Find all 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).
import cocoapods.* - Map pods to SPM - See common-pods-mapping.md
- Locate iOS project directory - Find the directory containing and
Podfile:.xcworkspaceRecord this path (e.g.,bashfind . -name "Podfile" -type f,iosApp/, or project root) - needed for Phase 5ios/ - Check for non-KMP CocoaPods - Determine if the project uses CocoaPods for dependencies other than KMP. This affects cleanup strategy in Phase 5.
- Check Xcode build phases - Open the 's
.xcodeprojand search for the Gradle build phase script. Check ifproject.pbxprojis present but commented out (prefixed withembedAndSignAppleFrameworkForXcode). If commented out, it must be uncommented during Phase 5 — the#task may or may not handle this automatically.integrateEmbedAndSign - Check for existing Crashlytics dSYM upload script - If using FirebaseCrashlytics, search for a dSYM upload shell script phase. Record its current path (CocoaPods-era scripts reference
project.pbxproj). This must be updated to the SPM path in Phase 5.${PODS_ROOT}/FirebaseCrashlytics/upload-symbols - Identify CocoaPods-related extras in build scripts - Search all files for CocoaPods workarounds beyond the standard
build.gradle.ktsblock (custom tasks hooking intococoapods {},podInstallpatching, podspec metadata,Pods.xcodeproj,extraSpecAttributes, etc.). See cocoapods-extras-patterns.md for the full pattern list. Record all findings — these will be handled in Phase 6.noPodspec()
部分KMP库会随带预构建的cinterop klib,其包命名空间为。迁移后,swiftPMDependencies的cinterop生成器会检测到这些现有绑定,会跳过为这些Clang模块生成新绑定,以避免重复。这意味着针对这些模块的导入语句必须保持不变 — 它们指向第三方库的捆绑klib,而非实际的CocoaPods依赖。
cocoapods.*cocoapods.*已知包含捆绑klib的库:
cocoapods.*| 库 | Maven制品 | 捆绑klib命名空间 | 提供的类 |
|---|---|---|---|
| KMPNotifier | | | |
检测方法: 在Gradle依赖声明中搜索已知库,然后将其与步骤4中找到的语句交叉比对。标记出哪些导入来自捆绑klib(需保留),哪些来自直接pod cinterop(需转换)。
import cocoapods.*如果不确定第三方KMP库是否包含捆绑klib,检查项目中是否存在的pod依赖 — 这是强烈信号,表示该pod仅提供原生链接,cinterop绑定来自KMP封装库(例如)。有关影响,请参阅common-pods-mapping.md。
linkOnly = truedev.gitlive:firebase-*查找并记录以下内容:
- CocoaPods配置 - 在文件中搜索
build.gradle.ktscocoapods - Pod依赖 - 从代码块中提取pod名称和版本
cocoapods {} - Framework配置 - 记录中的
cocoapods.framework {}、baseName和部署目标isStatic - linkOnly类型pod - 记录声明为的pod。这些pod仅提供原生链接,cinterop绑定来自KMP封装库(例如
linkOnly = true)。详见common-pods-mapping.mddev.gitlive:firebase-* - Kotlin导入语句 - 查找所有语句,与步骤1.3交叉比对,区分哪些来自捆绑klib(需保留),哪些来自直接pod cinterop(需转换)
import cocoapods.* - Pod到SPM的映射 - 参阅common-pods-mapping.md
- 定位iOS项目目录 - 查找包含和
Podfile的目录:.xcworkspace记录该路径(例如bashfind . -name "Podfile" -type f、iosApp/或项目根目录),第5阶段需要使用ios/ - 检查非KMP的CocoaPods依赖 - 确定项目是否将CocoaPods用于KMP以外的依赖,这会影响第5阶段的清理策略
- 检查Xcode构建阶段 - 打开的
.xcodeproj,搜索Gradle构建阶段脚本。检查project.pbxproj是否存在但被注释(前缀为embedAndSignAppleFrameworkForXcode)。如果已注释,第5阶段必须取消注释 —#任务可能会自动处理,也可能需要手动操作integrateEmbedAndSign - 检查现有Crashlytics dSYM上传脚本 - 如果使用FirebaseCrashlytics,在中搜索dSYM上传Shell脚本阶段。记录其当前路径(CocoaPods时代的脚本引用
project.pbxproj),第5阶段必须将其更新为SPM路径${PODS_ROOT}/FirebaseCrashlytics/upload-symbols - 识别构建脚本中与CocoaPods相关的额外代码 - 在所有文件中搜索标准
build.gradle.kts代码块以外的CocoaPods兼容代码(例如自定义任务钩子、cocoapods {}补丁、podspec元数据、Pods.xcodeproj等)。有关完整模式列表,请参阅cocoapods-extras-patterns.md。记录所有发现,这些将在第6阶段处理noPodspec()
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.ktskotlin
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.ktskotlin
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
undefinedgradle/libs.versions.toml
gradle/libs.versions.toml
[versions]
kotlin = "<kotlin-version>"
undefined[versions]
kotlin = "<kotlin-version>"
undefined2.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 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-version>!!kotlin
// root build.gradle.kts
buildscript {
dependencies.constraints {
"classpath"("org.jetbrains.kotlin:kotlin-gradle-plugin:<kotlin-version>!!")
}
}将替换为第1.0a阶段记录的版本。后缀强制严格版本解析,确保没有其他依赖引入不同版本的Kotlin Gradle插件。
<kotlin-version>!!Phase 3: Add swiftPMDependencies (Keep CocoaPods)
阶段3:添加swiftPMDependencies(保留CocoaPods)
Do NOT remove the block or plugin yet. Add alongside the existing CocoaPods configuration.
cocoapods {}kotlin("native.cocoapods")swiftPMDependencies {}请勿移除代码块或插件。 在现有CocoaPods配置旁添加。
cocoapods {}kotlin("native.cocoapods")swiftPMDependencies {}3.1 Add group property
3.1 添加group属性
kotlin
group = "org.example.myproject" // Required for import namespacekotlin
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 .
importedModulesKey concepts: = SPM product names (controls linking). = Clang module names for cinterop bindings (only when ). defaults to (bindings for all Clang modules); set when transitive C/C++ modules fail cinterop (Firebase, gRPC), then list needed modules explicitly.
productsimportedModulesdiscoverModulesImplicitly = falsediscoverModulesImplicitlytruefalseImportant: 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 entries (no needed). After adding new products, re-run to regenerate the linkage Swift package.
productsimportedModulesintegrateLinkagePackagekotlin
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核心概念: = SPM产品名称(控制链接)。 = 用于cinterop绑定的Clang模块名称(仅当时需要)。默认为(为所有Clang模块生成绑定);当传递性C/C++模块无法通过cinterop时(如Firebase、gRPC),设置为,然后显式列出需要的模块。
productsimportedModulesdiscoverModulesImplicitly = falsediscoverModulesImplicitlytruefalse重要提示: SPM产品名称和Clang模块名称并非始终一致。请务必查阅common-pods-mapping.md获取正确值。
请勿在CocoaPods和SPM中混合使用同一套件的库。 共享同一仓库的库(例如所有Firebase产品)也共享传递性依赖。部分产品通过CocoaPods链接,部分通过SPM链接会导致符号重复/冲突,运行时出现dyld崩溃。迁移此类套件时,请一次性将该套件的所有pod迁移至SPM — 包括Kotlin不直接使用的纯Swift pod。将纯Swift pod添加为条目(无需)。添加新产品后,重新运行以重新生成链接Swift包。
productsimportedModulesintegrateLinkagePackagekotlin
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 block contains a configuration, move it to the API on each target. is recommended — dynamic frameworks have known edge cases with SwiftPM import that can cause linker errors, dyld crashes, or duplicate class warnings:
cocoapodsframework {}binariesisStatic = truekotlin
listOf(iosArm64(), iosSimulatorArm64(), iosX64()).forEach { iosTarget ->
iosTarget.binaries.framework { baseName = "Shared"; isStatic = true }
}如果代码块中包含配置,请将其移至每个target的 API中。推荐设置 — 动态框架与SwiftPM Import存在已知边缘情况,可能导致链接器错误、dyld崩溃或类重复警告:
cocoapodsframework {}binariesisStatic = truekotlin
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 or similar KMP wrapper libraries, two additional steps are required:
dev.gitlive:firebase-*A. Switch to — dynamic frameworks + Firebase SPM = runtime crash. After switching: re-run , remove any "Embed Frameworks" copy phase, move linker flags to .
isStatic = truedyldintegrateLinkagePackageOTHER_LDFLAGSB. Add framework search paths — add conditional linkerOpts in and matching in the Xcode project.
-Fbuild.gradle.ktsFRAMEWORK_SEARCH_PATHSSee common-pods-mapping.md § dev.gitlive and troubleshooting.md for code snippets and the full product list.
如果项目使用或类似KMP封装库,需要额外执行两个步骤:
dev.gitlive:firebase-*A. 切换为 — 动态框架 + Firebase SPM = 运行时崩溃。切换后:重新运行,移除所有“Embed Frameworks”复制阶段,将链接器标志移至。
isStatic = truedyldintegrateLinkagePackageOTHER_LDFLAGSB. 添加framework搜索路径 — 在中添加条件式链接器选项,并在Xcode项目中添加对应的。
build.gradle.kts-FFRAMEWORK_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.FIRAnalyticsImport flattening: The Clang module name (e.g., , ) disappears from the import path — all classes are flattened under the same prefix regardless of which library they come from. For example, both and become and .
FirebaseFirestoreInternalFirebaseAuthswiftPMImport.<group>.<module>cocoapods.FirebaseAuth.FIRAuthcocoapods.FirebaseFirestoreInternal.FIRFirestoreswiftPMImport.<group>.<module>.FIRAuthswiftPMImport.<group>.<module>.FIRFirestorekotlin
// group = "org.jetbrains.kotlin.firebase.sample", module = "kotlin-library"
// 转换前:
import cocoapods.FirebaseAnalytics.FIRAnalytics
// 转换后:
import swiftPMImport.org.jetbrains.kotlin.firebase.sample.kotlin.library.FIRAnalytics导入扁平化: Clang模块名称(例如、)会从导入路径中消失 — 所有类都会被扁平化到同一个前缀下,无论它们来自哪个库。例如,和都会变为和。
FirebaseFirestoreInternalFirebaseAuthswiftPMImport.<group>.<module>cocoapods.FirebaseAuth.FIRAuthcocoapods.FirebaseFirestoreInternal.FIRFirestoreswiftPMImport.<group>.<module>.FIRAuthswiftPMImport.<group>.<module>.FIRFirestorePreserving Bundled Klib Imports
保留捆绑Klib的导入语句
CRITICAL: Do NOT replaceimports that resolve to third-party KMP libraries' bundled cinterop klibs (identified in Phase 1 step 1.3). These imports must remain as-is — thecocoapods.*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, sococoapodsfor those classes will fail with "Unresolved reference".swiftPMImport.*
Example (project using KMPNotifier):
kotlin
// KEEP — resolves to kmpnotifier's bundled cinterop klib
import cocoapods.FirebaseMessaging.FIRMessaging关键提示: 请勿替换指向第三方KMP库捆绑cinterop klib的导入语句(第1阶段步骤1.3已识别)。这些导入语句必须保持不变 —cocoapods.*前缀是该库发布的klib中的包命名空间,而非实际的CocoaPods依赖。swiftPMDependencies的cinterop生成器会跳过依赖库已提供的模块,因此针对这些类的cocoapods导入会出现“Unresolved reference”错误。swiftPMImport.*
示例(使用KMPNotifier的项目):
kotlin
// 保留 — 指向kmpnotifier的捆绑cinterop klib
import cocoapods.FirebaseMessaging.FIRMessagingBulk 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 imports that should be preserved (from bundled klibs).
cocoapods.*Finding correct import path: Run - errors show available classes.
./gradlew :moduleName:build对所有Kotlin源文件执行正则表达式查找替换,排除第1阶段步骤1.3识别的导入语句:
查找: cocoapods\.\w+\.
替换: swiftPMImport.<your.group>.<your.module>.批量替换后,手动恢复所有需要保留的导入语句(来自捆绑klib的)。
cocoapods.*获取正确导入路径的方法: 运行 — 错误信息会显示可用的类。
./gradlew :moduleName:buildPhase 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 to trigger during the build. is a one-time setup — it does not need to be added as a build phase. If is skipped, check for EmbedAndSign disablers (Phase 1 step 1.2) — remove them first, then re-run.
.xcodeprojembedAndSignAppleFrameworkForXcodeintegrateLinkagePackageintegrateEmbedAndSignVerify is active: After running integration, check the build phase script in . If is commented out (prefixed with ), uncomment it.
embedAndSignAppleFrameworkForXcodeproject.pbxprojembedAndSignAppleFrameworkForXcode#The task generates at — a local Swift package that mirrors your list and ensures SPM libraries are linked into the final binary.
integrateLinkagePackage_internal_linkage_SwiftPMImport/<iosDir>/productsAfter running the integration tasks, disable User Script Sandboxing () in the . Xcode 16+ enables it by default, which prevents the Gradle build phase from writing to the project directory:
ENABLE_USER_SCRIPT_SANDBOXING = NO.xcodeprojbash
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 to the app target's sections. Then restart the Gradle daemon:
ENABLE_USER_SCRIPT_SANDBOXING = NO;buildSettings./gradlew --stopAlternative (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'运行该命令,它会修改以在构建期间触发。是一次性设置任务 — 无需将其添加为构建阶段。如果被跳过,请检查第1阶段步骤1.2中的EmbedAndSign禁用代码 — 先移除这些代码,然后重新运行命令。
.xcodeprojembedAndSignAppleFrameworkForXcodeintegrateLinkagePackageintegrateEmbedAndSign验证已激活: 运行集成命令后,检查中的构建阶段脚本。如果被注释(前缀为),请取消注释。
embedAndSignAppleFrameworkForXcodeproject.pbxprojembedAndSignAppleFrameworkForXcode#integrateLinkagePackage<iosDir>/_internal_linkage_SwiftPMImport/products运行集成任务后,禁用用户脚本沙箱()。Xcode 16+默认启用该功能,会阻止Gradle构建阶段写入项目目录:
ENABLE_USER_SCRIPT_SANDBOXING = NObash
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' "$XCODEPROJ_PATH/project.pbxproj"如果该设置不存在(Xcode默认值为YES),请在应用target的部分添加。然后重启Gradle守护进程:
buildSettingsENABLE_USER_SCRIPT_SANDBOXING = NO;./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 to . See troubleshooting.md § "Firebase Crashlytics: dSYM Upload Script" and common-pods-mapping.md for the full script and input files list.
${PODS_ROOT}/FirebaseCrashlytics/upload-symbols"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"如果项目使用FirebaseCrashlytics且存在dSYM上传运行脚本阶段(第1阶段步骤10已识别),请将脚本路径从更新为。有关完整脚本和输入文件列表,请参阅troubleshooting.md中的“Firebase Crashlytics: dSYM Upload Script”章节,以及common-pods-mapping.md。
${PODS_ROOT}/FirebaseCrashlytics/upload-symbols"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"5.3 Deintegrate CocoaPods
5.3 移除CocoaPods
Option A: Full deintegration (if CocoaPods was used ONLY for KMP dependencies):
Before deleting files, run and verify the paths. If unsure, move files to a backup location instead of deleting immediately.
git status --shortbash
cd /path/to/iosApp
pod deintegrate
rm -rf Podfile Podfile.lock Pods/选项A:完全移除(如果CocoaPods仅用于KMP依赖):
删除文件前,运行并验证路径。如果不确定,可先将文件移至备份目录,而非直接删除。
git status --shortbash
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
endbash
cd /path/to/iosApp && pod installTip: 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
endbash
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 block from . The block and configuration added in Phase 3 replace it.
cocoapods { ... }build.gradle.ktsswiftPMDependencies {}binaries.framework {}从中删除整个代码块。第3阶段添加的代码块和配置会替代它。
build.gradle.ktscocoapods { ... }swiftPMDependencies {}binaries.framework {}6.3 Remove deprecated gradle.properties entries
6.3 移除已弃用的gradle.properties条目
If found in Phase 1.1, remove from :
gradle.propertiesproperties
undefined如果第1.1阶段找到该条目,请从中移除:
gradle.propertiesproperties
undefinedREMOVE — no longer needed after migrating away from CocoaPods (KT-64096)
移除 — 迁移出CocoaPods后不再需要(KT-64096)
kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
undefinedkotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true
undefined6.4 Clean up CocoaPods-related extras
6.4 清理与CocoaPods相关的额外代码
Review the extras identified in Phase 1 step 11. Podspec metadata, , CocoaPods task hooks, and patching code are safe to remove without user consultation. Non-standard pod configurations (, ), custom cinterop setups, and CocoaPods-specific compiler/linker flags require analysis — consult the user if unsure whether SPM handles them automatically.
noPodspec()Pods.xcodeprojextraOptsmoduleNamedefFileSee cocoapods-extras-patterns.md for the full categorized list with examples.
回顾第1阶段步骤11识别的额外代码。Podspec元数据、、CocoaPods任务钩子、补丁代码无需用户确认即可安全移除。非标准pod配置(、)、自定义cinterop 设置、CocoaPods特定的编译器/链接器标志需要分析 — 如果不确定SPM是否会自动处理,请咨询用户。
noPodspec()Pods.xcodeprojextraOptsmoduleNamedefFile有关完整分类列表和示例,请参阅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:build7.2 Link framework
7.2 链接Framework
bash
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm64bash
./gradlew :moduleName:linkDebugFrameworkIosSimulatorArm647.3 Build iOS/macOS Xcode project
7.3 构建iOS/macOS Xcode项目
After the Gradle build succeeds, build the Xcode project. Use if all CocoaPods were removed (Option A), or if non-KMP CocoaPods remain (Option B):
-project *.xcodeproj-workspace *.xcworkspacebash
cd /path/to/iosAppGradle构建成功后,构建Xcode项目。如果所有CocoaPods已移除(选项A),使用;如果仍有非KMP的CocoaPods依赖(选项B),使用:
-project *.xcodeproj-workspace *.xcworkspacebash
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如果失败 — 第5.1阶段未禁用沙箱。返回并应用第5.1阶段的沙箱修复,然后重试。
checkSandboxAndWriteProtection如果迁移前构建未验证(使用了第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-template.md中的模板。
MIGRATION_REPORT.md报告必须包含:
- 迁移前状态 — CocoaPods依赖(名称、版本、状态)、Framework配置、
linkOnly导入语句、非KMP pod、特殊配置cocoapods.* - 迁移步骤 — 每个阶段的具体变更,非平凡变更需提供变更前后的代码片段
- 导入转换 — 所有导入变更的表格,明确标记保留的导入语句及其对应的捆绑klib
cocoapods.* - 遇到的错误 — 结构化的条目:阶段、具体症状、根本原因、修复方案、通用标识
Error #N - 非平凡决策 — 变更、保留的导入语句、Framework搜索路径、权衡取舍
isStatic - 变更文件列表 — 按类型分组的完整文件列表(Gradle、Kotlin、Xcode、新增、删除)
Phase 8: Migration Report
额外资源
After migration (whether successful or not), write a comprehensive in the project root. Use the template in migration-report-template.md.
MIGRATION_REPORT.mdThe report must include:
- Pre-Migration State — CocoaPods dependencies (name, version, ), framework config,
linkOnlyimports, non-KMP pods, atypical configurationcocoapods.* - Migration Steps — exact changes per phase with before/after snippets for non-trivial changes
- Import Transformations — table of every import change, clearly marking preserved imports and which bundled klib provides them
cocoapods.* - Errors Encountered — structured entries: phase, exact symptom, root cause, fix, generalizable flag
Error #N - Non-Trivial Decisions — changes, preserved imports, framework search paths, trade-offs
isStatic - 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
—