refactor(page): 删除旧版页面文件
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# CocoaPods Extras Patterns
|
||||
|
||||
Patterns to look for in `build.gradle.kts` files beyond the standard `cocoapods {}` block. These are workarounds, hacks, and glue code that projects accumulate over time to work around CocoaPods limitations.
|
||||
|
||||
## Detection Patterns (Phase 1 step 11)
|
||||
|
||||
- **Custom tasks that hook into CocoaPods tasks** — e.g., tasks registered with `tasks.named("podInstall") { finalizedBy(...) }` or `tasks.register("fixXcodeProject")` that patch `Pods.xcodeproj/project.pbxproj` to fix paths, tweak build settings, or work around CocoaPods quirks. These are pure CocoaPods workarounds and become dead code after migration.
|
||||
- **Pods.xcodeproj patching** — any code that reads/writes `Pods.xcodeproj` files (e.g., replacing Gradle invocation paths, fixing scheme settings). The `Pods.xcodeproj` will no longer exist after migration.
|
||||
- `cocoapods.summary`, `cocoapods.homepage`, `cocoapods.version`, `cocoapods.name` — podspec metadata (safe to remove)
|
||||
- `cocoapods.podfile` — explicit Podfile path reference
|
||||
- `cocoapods.extraSpecAttributes` — custom podspec attributes
|
||||
- `pod("...", extraOpts = ...)` or `pod("...", moduleName = ...)` — non-standard pod configurations
|
||||
- `noPodspec()` — disables podspec generation
|
||||
- **Any code referencing `Pods/` directory, `.xcworkspace`, or `podspec` files** — build logic, path constants, or task inputs/outputs tied to CocoaPods artifacts
|
||||
- Compiler flags or linker settings added specifically for CocoaPods interop (e.g., `-framework`, cinterop `defFile` for pod headers)
|
||||
|
||||
---
|
||||
|
||||
## Phase 6.4 Cleanup Categories
|
||||
|
||||
### Safe to remove (no user consultation needed)
|
||||
|
||||
- `cocoapods.summary`, `cocoapods.homepage`, `cocoapods.version`, `cocoapods.name` — podspec metadata, not used by SPM
|
||||
- `cocoapods.podfile = project.file(...)` — Podfile path reference, not used by SPM
|
||||
- `cocoapods.extraSpecAttributes` — podspec attributes, not used by SPM
|
||||
- `noPodspec()` — podspec generation flag, not used by SPM
|
||||
- Custom Gradle tasks that hook into CocoaPods tasks (`podInstall`, `podSetup`, `generatePodspec`) — these tasks no longer exist without the plugin. This includes any tasks registered via `tasks.named("podInstall") { finalizedBy(...) }` or similar wiring. Example of dead code to remove entirely:
|
||||
```kotlin
|
||||
// REMOVE — CocoaPods workaround, no longer needed
|
||||
tasks.register("fixXcodeProject") {
|
||||
doLast {
|
||||
val xcodeProjectFile = project.file("../iosApp/Pods/Pods.xcodeproj/project.pbxproj")
|
||||
// ... patching Pods.xcodeproj paths ...
|
||||
}
|
||||
}
|
||||
tasks.named("podInstall") { finalizedBy("fixXcodeProject") }
|
||||
```
|
||||
- Any code that reads/writes `Pods.xcodeproj` files — the `Pods/` directory will no longer exist
|
||||
- References to `Pods/` directory paths, `.xcworkspace` files, or `podspec` files in build configurations
|
||||
|
||||
### Requires analysis — consult the user if unsure
|
||||
|
||||
- `pod("...", extraOpts = ...)` — extra options may indicate special compilation flags needed. Check if the underlying library needs equivalent flags in `swiftPMDependencies` (e.g., `importedClangModules`, platform constraints)
|
||||
- `pod("...", moduleName = ...)` — custom module name may indicate the Clang module name differs from the pod name. This likely maps to an `importedClangModules` entry in the SPM package declaration
|
||||
- Custom cinterop `defFile` configurations for pod headers — these may need to be adapted or may no longer be needed if the SwiftPM import handles the headers automatically. Present findings to the user before removing
|
||||
- Compiler or linker flags added specifically for CocoaPods interop (e.g., `-framework Pod`, custom `cinterops {}` blocks) — analyze whether the SPM integration handles this automatically. If unclear, present the flags to the user and ask whether they are still needed
|
||||
- Any custom task wiring or build logic that references CocoaPods outputs — explain what the task does and ask the user whether equivalent functionality is needed
|
||||
@@ -0,0 +1,474 @@
|
||||
# Common Pods to SwiftPM Mapping
|
||||
|
||||
Reference for migrating popular CocoaPods dependencies to SwiftPM.
|
||||
|
||||
## Firebase Suite
|
||||
|
||||
All Firebase products come from a single repository: `https://github.com/firebase/firebase-ios-sdk.git`
|
||||
|
||||
**Key facts:**
|
||||
- SPM product names match CocoaPods pod names (e.g., pod `FirebaseAuth` → product `FirebaseAuth`)
|
||||
- Exception: Beta products have a `-Beta` suffix in SPM (e.g., `FirebaseAppDistribution-Beta`)
|
||||
- The CocoaPods umbrella pod `Firebase` does not exist in SPM — import specific products
|
||||
- **Platform requirements**: iOS 15+, macOS 10.15+, tvOS 15+, watchOS 7+
|
||||
- **Xcode**: 16.2+
|
||||
|
||||
> **WARNING: Do not mix Firebase across CocoaPods and SPM.** All Firebase products share a single repository and common transitive dependencies (gRPC, abseil, leveldb, BoringSSL, nanopb, etc.). If some Firebase pods remain in CocoaPods while others are added via SPM, the shared transitive dependencies get linked twice with conflicting symbols, causing **dyld crashes at runtime** (e.g., `Symbol not found: _OBJC_CLASS_$_FIRFirestore`). When migrating Firebase, move **all** Firebase pods to SPM at once — including Swift-only pods (FirebaseAI, FirebaseFunctions, FirebaseMLModelDownloader) that Kotlin cannot use directly. Add Swift-only pods as `products` entries without `importedClangModules`. After adding new products, re-run `integrateLinkagePackage` to regenerate the linkage Swift package.
|
||||
|
||||
### Firebase SPM Products Reference
|
||||
|
||||
| CocoaPods Pod | SPM Product | Platform | KMP Notes |
|
||||
|---------------|-------------|----------|-----------|
|
||||
| FirebaseAnalytics | FirebaseAnalytics | All | ObjC classes: `FIRAnalytics`, `FIRApp` |
|
||||
| FirebaseAuth | FirebaseAuth | All (partial on macOS/tvOS/watchOS) | ObjC classes: `FIRAuth`, `FIRUser` |
|
||||
| FirebaseCore | FirebaseCore | All | ObjC class: `FIRApp` |
|
||||
| FirebaseCrashlytics | FirebaseCrashlytics | All | ObjC class: `FIRCrashlytics` |
|
||||
| FirebaseDatabase | FirebaseDatabase | All | **importedClangModules: `FirebaseDatabaseInternal`** — ObjC classes: `FIRDatabase`, `FIRDatabaseReference` |
|
||||
| FirebaseFirestore | FirebaseFirestore | All | **Special case** — see below |
|
||||
| FirebaseFunctions | FirebaseFunctions | All | Swift-only — no `importedClangModules` entry needed |
|
||||
| FirebaseMessaging | FirebaseMessaging | All | ObjC classes: `FIRMessaging` |
|
||||
| FirebaseRemoteConfig | FirebaseRemoteConfig | All | **importedClangModules: `FirebaseRemoteConfigInternal`** — ObjC class: `FIRRemoteConfig` |
|
||||
| FirebaseStorage | FirebaseStorage | All | ObjC class: `FIRStorage` |
|
||||
| FirebaseAppCheck | FirebaseAppCheck | All (watchOS 9+) | ObjC class: `FIRAppCheck` |
|
||||
| FirebasePerformance | FirebasePerformance | iOS/tvOS only | ObjC class: `FIRPerformance` |
|
||||
| FirebaseInAppMessaging | FirebaseInAppMessaging-Beta | iOS/tvOS only | `-Beta` suffix in SPM, **importedClangModules: `FirebaseInAppMessagingInternal`** |
|
||||
| FirebaseAppDistribution | FirebaseAppDistribution-Beta | iOS only | Note `-Beta` suffix in SPM |
|
||||
| FirebaseInstallations | FirebaseInstallations | All | ObjC class: `FIRInstallations` |
|
||||
| FirebaseABTesting | *(no SPM product)* | All | **Module-only**: pulled transitively by RemoteConfig. List in `importedClangModules` only |
|
||||
| FirebaseAILogic | FirebaseAI | All | **Renamed in SPM**. Swift-only — no `importedClangModules` entry needed |
|
||||
| FirebaseMLModelDownloader | FirebaseMLModelDownloader | All | Swift-only — no `importedClangModules` entry needed |
|
||||
|
||||
### FirebaseAnalytics
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("FirebaseAnalytics") { version = "12.5.0" }
|
||||
|
||||
// SwiftPM — use same version as pod
|
||||
swiftPackage(
|
||||
url = "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
version = "12.5.0",
|
||||
products = listOf("FirebaseAnalytics"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.FIRAnalytics
|
||||
import swiftPMImport.<group>.<module>.FIRApp
|
||||
```
|
||||
|
||||
### FirebaseAuth
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("FirebaseAuth") { version = "12.5.0" }
|
||||
|
||||
// SwiftPM — use same version as pod
|
||||
swiftPackage(
|
||||
url = "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
version = "12.5.0",
|
||||
products = listOf("FirebaseAuth"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.FIRAuth
|
||||
import swiftPMImport.<group>.<module>.FIRUser
|
||||
```
|
||||
|
||||
### FirebaseDatabase
|
||||
|
||||
Database's Clang module name differs from its SPM product name. You **must** specify `importedClangModules` (requires typed API):
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("FirebaseDatabase") { version = "12.5.0" }
|
||||
|
||||
// SwiftPM - Note the importedClangModules parameter (typed API required)
|
||||
swiftPackage(
|
||||
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
|
||||
version = from("12.5.0"),
|
||||
products = listOf(product("FirebaseDatabase")),
|
||||
importedClangModules = listOf("FirebaseDatabaseInternal"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.FIRDatabase
|
||||
import swiftPMImport.<group>.<module>.FIRDatabaseReference
|
||||
```
|
||||
|
||||
### FirebaseFirestore (Special Case)
|
||||
|
||||
Firestore's Clang module name differs from its SPM product name. You **must** specify `importedClangModules` (requires typed API):
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("FirebaseFirestore") { version = "12.5.0" }
|
||||
|
||||
// SwiftPM - Note the importedClangModules parameter (typed API required)
|
||||
swiftPackage(
|
||||
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
|
||||
version = from("12.5.0"),
|
||||
products = listOf(product("FirebaseFirestore")),
|
||||
importedClangModules = listOf("FirebaseFirestoreInternal"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.FIRFirestore
|
||||
import swiftPMImport.<group>.<module>.FIRDocumentReference
|
||||
```
|
||||
|
||||
**Why is this needed?** Firestore distributes as a binary xcframework. The internal Clang module exposed to Objective-C is named `FirebaseFirestoreInternal`, not `FirebaseFirestore`. Without `importedClangModules`, the KMP compiler cannot discover the Objective-C headers.
|
||||
|
||||
### FirebaseCrashlytics
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("FirebaseCrashlytics") { version = "12.5.0" }
|
||||
|
||||
// SwiftPM — use same version as pod
|
||||
swiftPackage(
|
||||
url = "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
version = "12.5.0",
|
||||
products = listOf("FirebaseCrashlytics"),
|
||||
)
|
||||
```
|
||||
|
||||
**iOS project requirement:** Crashlytics needs a dSYM upload run script in the Xcode build phases. After migration, add a "Run Script" phase at the END of build phases:
|
||||
|
||||
```bash
|
||||
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"
|
||||
```
|
||||
|
||||
With input files:
|
||||
```
|
||||
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
|
||||
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
|
||||
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist
|
||||
$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist
|
||||
$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)
|
||||
```
|
||||
|
||||
Also set **Debug Information Format** to `DWARF with dSYM File` for all build configurations.
|
||||
|
||||
### Combined Firebase Example
|
||||
|
||||
When using multiple Firebase products, declare them in a single package. **Set `discoverClangModulesImplicitly = false`** — Firebase's transitive C++ dependencies (gRPC, abseil, leveldb, BoringSSL) contain Clang modules that fail cinterop. Explicitly list only the modules you need.
|
||||
|
||||
```kotlin
|
||||
swiftPMDependencies {
|
||||
discoverClangModulesImplicitly = false
|
||||
|
||||
// Combined Firebase requires typed API for importedClangModules control
|
||||
swiftPackage(
|
||||
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
|
||||
version = from("12.5.0"),
|
||||
products = listOf(
|
||||
product("FirebaseAnalytics"),
|
||||
product("FirebaseAuth"),
|
||||
product("FirebaseDatabase"),
|
||||
product("FirebaseFirestore"),
|
||||
product("FirebaseCrashlytics"),
|
||||
product("FirebaseMessaging"),
|
||||
product("FirebaseRemoteConfig"),
|
||||
// Swift-only pods (products only, no importedClangModules):
|
||||
product("FirebaseAI"),
|
||||
product("FirebaseFunctions"),
|
||||
),
|
||||
importedClangModules = listOf(
|
||||
"FirebaseAnalytics",
|
||||
"FirebaseAuth",
|
||||
"FirebaseCore",
|
||||
"FirebaseCrashlytics",
|
||||
"FirebaseDatabaseInternal", // Not "FirebaseDatabase"
|
||||
"FirebaseFirestoreInternal", // Not "FirebaseFirestore"
|
||||
"FirebaseMessaging",
|
||||
"FirebaseRemoteConfigInternal", // Not "FirebaseRemoteConfig"
|
||||
"FirebaseABTesting", // Module-only, no product
|
||||
),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Firebase importedClangModules Reference
|
||||
|
||||
Several Firebase products expose ObjC headers through Clang modules whose names differ from the SPM product name:
|
||||
|
||||
| SPM Product | Clang Module (importedClangModules) | Notes |
|
||||
|---|---|---|
|
||||
| FirebaseAnalytics | FirebaseAnalytics | Same name |
|
||||
| FirebaseAuth | FirebaseAuth | Same name |
|
||||
| FirebaseCore | FirebaseCore | Same name |
|
||||
| FirebaseCrashlytics | FirebaseCrashlytics | Same name |
|
||||
| FirebaseDatabase | **FirebaseDatabaseInternal** | Different |
|
||||
| FirebaseFirestore | **FirebaseFirestoreInternal** | Different |
|
||||
| FirebaseInAppMessaging-Beta | **FirebaseInAppMessagingInternal** | Different |
|
||||
| FirebaseRemoteConfig | **FirebaseRemoteConfigInternal** | Different |
|
||||
| FirebaseInstallations | FirebaseInstallations | Same name |
|
||||
| FirebaseMessaging | FirebaseMessaging | Same name |
|
||||
| FirebasePerformance | FirebasePerformance | Same name |
|
||||
| FirebaseStorage | FirebaseStorage | Same name |
|
||||
| FirebaseAppCheck | FirebaseAppCheck | Same name |
|
||||
| FirebaseAppDistribution-Beta | FirebaseAppDistribution | Same name (no `-Beta`) |
|
||||
| *(transitive)* | **FirebaseABTesting** | Module-only, no product |
|
||||
| FirebaseAI | *(none)* | Swift-only, no cinterop |
|
||||
| FirebaseFunctions | *(none)* | Swift-only, no cinterop |
|
||||
| FirebaseMLModelDownloader | *(none)* | Swift-only, no cinterop |
|
||||
|
||||
**Note:** When `discoverClangModulesImplicitly = false` (recommended for Firebase), you must list every Clang module you import in `importedClangModules`. When `true` (default), `importedClangModules` is ignored — but this will fail for Firebase due to C++ transitive dependencies.
|
||||
|
||||
### Firebase Initialization
|
||||
|
||||
Ensure `GoogleService-Info.plist` is included in the iOS app target. In the app's entry point:
|
||||
|
||||
```swift
|
||||
import Firebase
|
||||
FirebaseApp.configure() // Must be called before using any Firebase service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Google Maps
|
||||
|
||||
Repository: `https://github.com/googlemaps/ios-maps-sdk.git`
|
||||
|
||||
**Key facts:**
|
||||
- **iOS 16+ only** — no macOS, tvOS, or watchOS support
|
||||
- **Xcode 16.0+** required
|
||||
- Must use `exact()` version — `from()` will fail to resolve
|
||||
- Single SPM product: `GoogleMaps` (wraps a binary xcframework via `GoogleMapsTarget`)
|
||||
- CocoaPods subspec `GoogleMaps/Maps` maps to the single `GoogleMaps` SPM product
|
||||
- Requires a Google Maps Platform API key configured in the iOS app
|
||||
- Check [releases](https://github.com/googlemaps/ios-maps-sdk/releases) for available SPM versions
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("GoogleMaps") { version = "10.10.0" }
|
||||
|
||||
// SwiftPM — use the exact same version as the pod
|
||||
swiftPackage(
|
||||
url = "https://github.com/googlemaps/ios-maps-sdk.git",
|
||||
version = "10.10.0",
|
||||
products = listOf("GoogleMaps"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.GMSMapView
|
||||
import swiftPMImport.<group>.<module>.GMSCameraPosition
|
||||
import swiftPMImport.<group>.<module>.GMSMarker
|
||||
import swiftPMImport.<group>.<module>.GMSServices
|
||||
```
|
||||
|
||||
**iOS project requirement:** The API key must be set in the app delegate or SwiftUI app entry point:
|
||||
|
||||
```swift
|
||||
import GoogleMaps
|
||||
GMSServices.provideAPIKey("YOUR_API_KEY")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Google Sign-In
|
||||
|
||||
Repository: `https://github.com/google/GoogleSignIn-iOS.git`
|
||||
|
||||
**Key facts:**
|
||||
- **iOS 12+, macOS 10.15+** — broad platform support
|
||||
- Two SPM products: `GoogleSignIn` (core) and `GoogleSignInSwift` (SwiftUI support)
|
||||
- CocoaPods pods: `GoogleSignIn` and `GoogleSignInSwiftSupport`
|
||||
- Uses `from()` versioning (latest: 9.1.0)
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("GoogleSignIn") { version = "8.0.0" }
|
||||
|
||||
// SwiftPM — use same version as pod
|
||||
swiftPackage(
|
||||
url = "https://github.com/google/GoogleSignIn-iOS.git",
|
||||
version = "8.0.0",
|
||||
products = listOf("GoogleSignIn"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.GIDSignIn
|
||||
import swiftPMImport.<group>.<module>.GIDSignInButton
|
||||
```
|
||||
|
||||
**iOS project requirement:** Add `GIDClientID` to `Info.plist` and configure the URL scheme for OAuth redirect. See [Google Sign-In iOS docs](https://developers.google.com/identity/sign-in/ios/start-integrating).
|
||||
|
||||
---
|
||||
|
||||
## LoremIpsum
|
||||
|
||||
Simple text generation library with direct mapping.
|
||||
|
||||
```kotlin
|
||||
// CocoaPods
|
||||
pod("LoremIpsum") { version = "2.0.1" }
|
||||
|
||||
// SwiftPM — use same version as pod
|
||||
swiftPackage(
|
||||
url = "https://github.com/lukaskubanek/LoremIpsum.git",
|
||||
version = "2.0.1",
|
||||
products = listOf("LoremIpsum"),
|
||||
)
|
||||
```
|
||||
|
||||
**Kotlin import:**
|
||||
```kotlin
|
||||
import swiftPMImport.<group>.<module>.LoremIpsum
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
| Pod Name | SPM Product | SPM Repository | Version Type | Platform | Notes |
|
||||
|----------|-------------|----------------|--------------|----------|-------|
|
||||
| FirebaseAnalytics | FirebaseAnalytics | firebase/firebase-ios-sdk.git | from() | All | |
|
||||
| FirebaseAuth | FirebaseAuth | firebase/firebase-ios-sdk.git | from() | All | |
|
||||
| FirebaseCore | FirebaseCore | firebase/firebase-ios-sdk.git | from() | All | |
|
||||
| FirebaseCrashlytics | FirebaseCrashlytics | firebase/firebase-ios-sdk.git | from() | All | Needs dSYM upload script |
|
||||
| FirebaseDatabase | FirebaseDatabase | firebase/firebase-ios-sdk.git | from() | All | importedClangModules: FirebaseDatabaseInternal |
|
||||
| FirebaseFirestore | FirebaseFirestore | firebase/firebase-ios-sdk.git | from() | All | importedClangModules: FirebaseFirestoreInternal |
|
||||
| FirebaseFunctions | FirebaseFunctions | firebase/firebase-ios-sdk.git | from() | All | Swift-only, no cinterop |
|
||||
| FirebaseMessaging | FirebaseMessaging | firebase/firebase-ios-sdk.git | from() | All | |
|
||||
| FirebaseRemoteConfig | FirebaseRemoteConfig | firebase/firebase-ios-sdk.git | from() | All | importedClangModules: FirebaseRemoteConfigInternal |
|
||||
| FirebaseStorage | FirebaseStorage | firebase/firebase-ios-sdk.git | from() | All | |
|
||||
| FirebasePerformance | FirebasePerformance | firebase/firebase-ios-sdk.git | from() | iOS/tvOS | |
|
||||
| FirebaseInAppMessaging | FirebaseInAppMessaging-Beta | firebase/firebase-ios-sdk.git | from() | iOS/tvOS | `-Beta` suffix, importedClangModules: FirebaseInAppMessagingInternal |
|
||||
| FirebaseAppDistribution | FirebaseAppDistribution-Beta | firebase/firebase-ios-sdk.git | from() | iOS only | `-Beta` suffix |
|
||||
| FirebaseABTesting | *(none)* | firebase/firebase-ios-sdk.git | — | All | Module-only, importedClangModules only |
|
||||
| FirebaseAILogic | FirebaseAI | firebase/firebase-ios-sdk.git | from() | All | Renamed, Swift-only |
|
||||
| GoogleMaps | GoogleMaps | googlemaps/ios-maps-sdk.git | exact() | iOS 16+ only | |
|
||||
| GoogleSignIn | GoogleSignIn | google/GoogleSignIn-iOS.git | from() | iOS 12+, macOS 10.15+ | |
|
||||
| GoogleSignInSwiftSupport | GoogleSignInSwift | google/GoogleSignIn-iOS.git | from() | iOS 12+, macOS 10.15+ | SwiftUI support |
|
||||
| LoremIpsum | LoremIpsum | lukaskubanek/LoremIpsum.git | from() | All | |
|
||||
|
||||
---
|
||||
|
||||
## KMP Wrapper Libraries with Bundled Cinterop Klibs
|
||||
|
||||
Some KMP libraries that wrap iOS SDKs ship pre-built cinterop klibs using the `cocoapods.*` package namespace. After migrating to SwiftPM, these `cocoapods.*` imports **must be preserved** — they resolve to the library's bundled klib, not to actual CocoaPods infrastructure.
|
||||
|
||||
### KMPNotifier
|
||||
|
||||
Repository: [https://github.com/mirzemehdi/KMPNotifier](https://github.com/mirzemehdi/KMPNotifier)
|
||||
Maven: `io.github.mirzemehdi:kmpnotifier`
|
||||
|
||||
**What it provides:** A KMP push notification library that wraps Firebase Cloud Messaging on iOS. The library bundles its own cinterop klib with namespace `cocoapods.FirebaseMessaging`, providing Kotlin bindings for `FIRMessaging`, `FIRMessagingAPNSTokenType`, and related classes.
|
||||
|
||||
**Impact on migration:**
|
||||
- When `swiftPMDependencies` generates cinterop bindings, it detects that `FirebaseMessaging` bindings already exist in KMPNotifier's klib and **skips generating new bindings** for that Clang module
|
||||
- `import cocoapods.FirebaseMessaging.FIRMessaging` must remain unchanged — do NOT replace with `swiftPMImport.*`
|
||||
- `FirebaseMessaging` should still be listed in `products` and `importedClangModules` for SPM linking, even though cinterop bindings won't be generated for it
|
||||
|
||||
**Verifying bundled klib contents:** Use `klib dump-metadata-signatures` to inspect what a library's klib provides ([docs](https://kotlinlang.org/docs/native-libraries.html#using-kotlin-native-compiler)):
|
||||
|
||||
```bash
|
||||
find ~/.gradle/caches -name "*.klib" -path "*kmpnotifier*" | head -1
|
||||
klib dump-metadata-signatures /path/to/cinterop.klib | grep "FIRMessaging"
|
||||
# Shows: cocoapods.FirebaseMessaging/FIRMessaging → confirms bundled klib
|
||||
```
|
||||
|
||||
**Example — project using both KMPNotifier and GoogleSignIn:**
|
||||
```kotlin
|
||||
// IOSDelegate.kt — after migration
|
||||
import cocoapods.FirebaseMessaging.FIRMessaging // KEEP — from kmpnotifier klib
|
||||
import cocoapods.FirebaseMessaging.FIRMessagingAPNSTokenType // KEEP — from kmpnotifier klib
|
||||
import swiftPMImport.com.example.app.GIDSignIn // REPLACE — direct cinterop
|
||||
```
|
||||
|
||||
### dev.gitlive/firebase-kotlin-sdk
|
||||
|
||||
Repository: [https://github.com/GitLiveApp/firebase-kotlin-sdk](https://github.com/GitLiveApp/firebase-kotlin-sdk)
|
||||
Maven: `dev.gitlive:firebase-auth`, `dev.gitlive:firebase-firestore`, `dev.gitlive:firebase-storage`, etc.
|
||||
|
||||
**What it provides:** Kotlin-first Firebase APIs for KMP. Unlike KMPNotifier, dev.gitlive libraries provide **high-level Kotlin APIs** — you typically don't use `cocoapods.*` imports directly. Instead, the Firebase pods were declared with `linkOnly = true` in CocoaPods to provide native linking only.
|
||||
|
||||
**Impact on migration:**
|
||||
|
||||
1. **Linker flags baked into published klibs.** The dev.gitlive klibs contain `-framework FirebaseCore`, `-framework FirebaseAuth`, etc. from the CocoaPods era. These persist when the consuming project switches to SPM. With SPM, Firebase frameworks land in per-product subdirectories (`$BUILT_PRODUCTS_DIR/FirebaseCore/FirebaseCore.framework`) that the K/N linker doesn't search automatically.
|
||||
|
||||
**Fix:** Add per-product `-F` linkerOpts to `build.gradle.kts`:
|
||||
```kotlin
|
||||
val builtProductsDir = System.getenv("BUILT_PRODUCTS_DIR")
|
||||
if (builtProductsDir != null) {
|
||||
listOf("FirebaseCore", "FirebaseAuth", "FirebaseCoreExtension",
|
||||
"FirebaseCoreInternal", "FirebaseCrashlytics", "FirebaseFirestore",
|
||||
"FirebaseFirestoreInternal", "FirebaseInstallations", "FirebaseMessaging",
|
||||
"FirebaseStorage", "GoogleDataTransport", "GoogleUtilities",
|
||||
"GTMSessionFetcher", "AppCheckCore", /* ... */).forEach { product ->
|
||||
linkerOpts("-F", "$builtProductsDir/$product")
|
||||
}
|
||||
}
|
||||
```
|
||||
Also add matching `FRAMEWORK_SEARCH_PATHS` in the Xcode project for both Debug and Release.
|
||||
|
||||
2. **Must use `isStatic = true`.** With a dynamic framework, the K/N linker creates `@rpath/FirebaseCore.framework/FirebaseCore` load instructions. Firebase SPM products are static libraries — their `.framework` bundles are not embedded in the app bundle. At runtime, `dyld` crashes with `Library not loaded`. Switching to `isStatic = true` embeds all symbols and defers unresolved framework flags to the final Xcode link.
|
||||
|
||||
3. **iOS test tasks may fail.** The K/N test runner cannot find Firebase frameworks outside of Xcode context. You may need to disable iOS test tasks:
|
||||
```kotlin
|
||||
tasks.matching {
|
||||
(it.name.contains("Ios") || it.name.contains("ios")) &&
|
||||
(it.name.contains("Test") || it.name.contains("test"))
|
||||
}.configureEach { enabled = false }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Researching Other Pods
|
||||
|
||||
For pods not listed here:
|
||||
|
||||
1. **Check GitHub repository** - Look for a `Package.swift` file in the repo
|
||||
2. **Check CocoaPods spec** - The `source` field often points to the Git URL
|
||||
3. **Search Swift Package Index** - https://swiftpackageindex.com/
|
||||
4. **Check library documentation** - Many libraries document SPM installation
|
||||
|
||||
### Finding the Clang Module Name
|
||||
|
||||
If you're unsure of the correct Clang module name:
|
||||
|
||||
1. Keep `discoverClangModulesImplicitly = true` (default)
|
||||
2. Run `./gradlew build`
|
||||
3. Check build errors for available class names
|
||||
4. Or check the library's `module.modulemap` file in its source
|
||||
|
||||
### Identifying Bundled Cinterop Klibs in Unknown Libraries
|
||||
|
||||
If you suspect a KMP library bundles its own cinterop klibs (common for libraries wrapping iOS SDKs), use the `klib` tool to inspect them ([docs](https://kotlinlang.org/docs/native-libraries.html#using-kotlin-native-compiler)):
|
||||
|
||||
```bash
|
||||
# Find klibs from a specific library in Gradle caches
|
||||
find ~/.gradle/caches -name "*.klib" -path "*libraryName*"
|
||||
|
||||
# Dump API signatures to see what namespaces and classes are provided
|
||||
klib dump-metadata-signatures /path/to/library.klib | grep "cocoapods\."
|
||||
|
||||
# If output shows cocoapods.* entries, the library bundles cinterop klibs
|
||||
# Those cocoapods.* imports must be preserved after migration
|
||||
```
|
||||
|
||||
Indicators that a library may bundle cinterop klibs:
|
||||
- The project has `linkOnly = true` pod declarations for the same native SDK
|
||||
- The library's documentation mentions CocoaPods integration or cinterop
|
||||
- The library provides Kotlin APIs for an iOS SDK (Firebase, Maps, etc.)
|
||||
|
||||
### Version Compatibility
|
||||
|
||||
Do NOT bump dependency versions during migration — use the exact same version from the `cocoapods {}` block. Always:
|
||||
1. **Use the same version.** If the pod was `version = "1.4.1"`, the SPM package must be `exact("1.4.1")`. Bumping versions can break cinterop APIs and introduce issues unrelated to the migration.
|
||||
2. CocoaPods `version = "X.Y.Z"` (without `~>`) is an exact pin — use `exact("X.Y.Z")` in SPM, not `from()`. Only use `from()` when the CocoaPods spec used optimistic versioning (`~>`).
|
||||
3. Check the GitHub releases page to confirm the exact version is available as an SPM release
|
||||
4. Test thoroughly after migration
|
||||
@@ -0,0 +1,293 @@
|
||||
# SwiftPM Import DSL Reference
|
||||
|
||||
Complete reference for the `swiftPMDependencies {}` DSL in Kotlin Multiplatform.
|
||||
|
||||
## Basic Structure
|
||||
|
||||
`swiftPackage()` and `localSwiftPackage()` are annotated with `@ExperimentalKotlinGradlePluginApi` (warning level). Add the opt-in at the top of `build.gradle.kts`:
|
||||
|
||||
```kotlin
|
||||
@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
|
||||
```
|
||||
|
||||
```kotlin
|
||||
kotlin {
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
swiftPMDependencies {
|
||||
// Deployment versions
|
||||
iosMinimumDeploymentTarget = "16.0"
|
||||
macosMinimumDeploymentTarget = "13.0"
|
||||
tvosMinimumDeploymentTarget = "16.0"
|
||||
watchosMinimumDeploymentTarget = "9.0"
|
||||
|
||||
// Module discovery (default: true)
|
||||
discoverClangModulesImplicitly = true
|
||||
|
||||
// Package declarations
|
||||
swiftPackage(...)
|
||||
localSwiftPackage(...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Package Declaration
|
||||
|
||||
The DSL has two API forms. **Use the simple string API** for most packages. Use the typed API only when you need `exact()`, `branch()`, `revision()`, or platform constraints.
|
||||
|
||||
### Simple API (Preferred)
|
||||
|
||||
Plain strings for URL, version, and products. The `version` parameter maps to a **minimum version** (`from()`) internally. The `importedClangModules` defaults to the `products` list automatically.
|
||||
|
||||
```kotlin
|
||||
swiftPackage(
|
||||
url = "https://github.com/owner/repo.git",
|
||||
version = "1.0.0", // Equivalent to from("1.0.0") — minimum version
|
||||
products = listOf("ProductName", "AnotherProduct"),
|
||||
)
|
||||
```
|
||||
|
||||
### Typed API (Advanced)
|
||||
|
||||
Use when you need exact version pinning, branch tracking, platform constraints, or explicit Clang module control:
|
||||
|
||||
```kotlin
|
||||
swiftPackage(
|
||||
url = url("https://github.com/owner/repo.git"),
|
||||
version = exact("1.0.0"),
|
||||
products = listOf(
|
||||
product("ProductName"),
|
||||
product("PlatformSpecific", platforms = setOf(iOS()))
|
||||
),
|
||||
importedClangModules = listOf("CustomClangModuleName"),
|
||||
)
|
||||
```
|
||||
|
||||
### Remote Package (Swift Package Registry)
|
||||
|
||||
```kotlin
|
||||
swiftPackage(
|
||||
repository = id("scope.package-name"),
|
||||
version = from("1.0.0"),
|
||||
products = listOf(product("ProductName")),
|
||||
packageName = "package-name",
|
||||
)
|
||||
```
|
||||
|
||||
### Local Package
|
||||
|
||||
```kotlin
|
||||
localSwiftPackage(
|
||||
directory = layout.projectDirectory.dir("../LocalPackage"),
|
||||
products = listOf("LocalPackage"),
|
||||
)
|
||||
```
|
||||
|
||||
To create a new local package (e.g., a Swift/ObjC wrapper around a Swift-only library):
|
||||
|
||||
```shell
|
||||
cd /path/to/shared
|
||||
mkdir LocalPackage && cd LocalPackage
|
||||
swift package init --type library --name LocalPackage
|
||||
```
|
||||
|
||||
Then use it in Kotlin:
|
||||
```kotlin
|
||||
// src/appleMain/kotlin/useLocalPackage.kt
|
||||
import swiftPMImport.<group>.<module>.HelloFromLocalPackage
|
||||
|
||||
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
|
||||
fun useLocalPackage() {
|
||||
HelloFromLocalPackage().hello()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version Specification
|
||||
|
||||
| Syntax | Description | Use Case |
|
||||
|--------|-------------|----------|
|
||||
| `version = "1.0.0"` (simple API) | Minimum version — equivalent to `from("1.0.0")` | Most packages |
|
||||
| `version = exact("1.0")` | Exact version pin | Strict dependencies, migration |
|
||||
| `version = from("1.0")` | Minimum version (explicit) | Same as simple string |
|
||||
| `version = branch("name")` | Git branch | Development, testing |
|
||||
| `version = revision("hash")` | Git commit hash | Pinning specific commits |
|
||||
| `version = range("1.0", "2.0")` | Version range | Constraining upper bound |
|
||||
|
||||
**Important for migration:** The simple string `version = "X.Y.Z"` resolves to a minimum version (`from()`), which may pull a newer version than what was in CocoaPods. For exact version preservation during migration, use the typed API: `version = exact("X.Y.Z")`.
|
||||
|
||||
---
|
||||
|
||||
## Product Configuration
|
||||
|
||||
### Simple API
|
||||
|
||||
```kotlin
|
||||
products = listOf("FirebaseAnalytics", "FirebaseAuth")
|
||||
```
|
||||
|
||||
With the simple API, `importedClangModules` defaults to the same list as `products`. This works when product names match Clang module names.
|
||||
|
||||
### Typed API — Platform Constraints
|
||||
|
||||
For packages that only support certain platforms, use the typed `product()` function:
|
||||
|
||||
```kotlin
|
||||
products = listOf(
|
||||
product("GoogleMaps", platforms = setOf(iOS())) // iOS only
|
||||
)
|
||||
```
|
||||
|
||||
Available platforms: `iOS()`, `macOS()`, `tvOS()`, `watchOS()`
|
||||
|
||||
### Typed API — Per-Product Clang Module Override
|
||||
|
||||
```kotlin
|
||||
products = listOf(
|
||||
product("FirebaseDatabase", importedClangModules = setOf("FirebaseDatabaseInternal"))
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Import Configuration
|
||||
|
||||
### Automatic Discovery (Default)
|
||||
|
||||
By default, `discoverClangModulesImplicitly = true`. SwiftPM import automatically discovers and imports all accessible Clang modules.
|
||||
|
||||
**IMPORTANT:** When `discoverClangModulesImplicitly = true`, the `importedClangModules` parameter is ignored. Only set `importedClangModules` when `discoverClangModulesImplicitly = false`.
|
||||
|
||||
**IMPORTANT for Firebase:** Set `discoverClangModulesImplicitly = false` when using Firebase. Firebase's transitive C++ dependencies (gRPC, abseil, leveldb, BoringSSL) contain Clang modules that fail cinterop generation. Disable implicit discovery and explicitly list only the Firebase modules you need in `importedClangModules`.
|
||||
|
||||
### Explicit Module Import
|
||||
|
||||
When automatic discovery is disabled and the Clang module name differs from the product name, use the typed API:
|
||||
|
||||
```kotlin
|
||||
swiftPMDependencies {
|
||||
discoverClangModulesImplicitly = false // Disable auto-discovery
|
||||
|
||||
swiftPackage(
|
||||
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
|
||||
version = from("12.6.0"),
|
||||
products = listOf(
|
||||
product("FirebaseAnalytics"),
|
||||
product("FirebaseFirestore")
|
||||
),
|
||||
importedClangModules = listOf(
|
||||
"FirebaseAnalytics",
|
||||
"FirebaseCore",
|
||||
"FirebaseFirestoreInternal" // Note: different from product name
|
||||
),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### When to Use importedClangModules
|
||||
|
||||
| Scenario | Use importedClangModules? |
|
||||
|----------|---------------------|
|
||||
| Simple API, product name = Clang module name | No (auto-defaulted from products) |
|
||||
| Product name != Clang module name | Yes (typed API) |
|
||||
| Multiple modules per product | Yes (typed API) |
|
||||
| Using discoverClangModulesImplicitly = false | Yes (typed API) |
|
||||
|
||||
---
|
||||
|
||||
## Deployment Versions
|
||||
|
||||
Set minimum deployment targets for each platform:
|
||||
|
||||
```kotlin
|
||||
swiftPMDependencies {
|
||||
iosMinimumDeploymentTarget = "16.0"
|
||||
macosMinimumDeploymentTarget = "13.0"
|
||||
tvosMinimumDeploymentTarget = "16.0"
|
||||
watchosMinimumDeploymentTarget = "9.0"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Example
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
}
|
||||
|
||||
group = "org.example.myproject"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
kotlin {
|
||||
iosArm64()
|
||||
iosSimulatorArm64()
|
||||
|
||||
// Framework configuration (moved from cocoapods block)
|
||||
listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
|
||||
iosTarget.binaries.framework {
|
||||
baseName = "SharedModule"
|
||||
isStatic = true
|
||||
}
|
||||
}
|
||||
|
||||
swiftPMDependencies {
|
||||
iosMinimumDeploymentTarget = "16.0"
|
||||
|
||||
// Simple API — most packages
|
||||
swiftPackage(
|
||||
url = "https://github.com/lukaskubanek/LoremIpsum.git",
|
||||
version = "2.0.1",
|
||||
products = listOf("LoremIpsum"),
|
||||
)
|
||||
|
||||
// Simple API — Google Maps
|
||||
swiftPackage(
|
||||
url = "https://github.com/googlemaps/ios-maps-sdk.git",
|
||||
version = "10.3.0",
|
||||
products = listOf("GoogleMaps"),
|
||||
)
|
||||
|
||||
// Local package
|
||||
localSwiftPackage(
|
||||
directory = layout.projectDirectory.dir("LocalWrapper"),
|
||||
products = listOf("LocalWrapper"),
|
||||
)
|
||||
}
|
||||
|
||||
compilerOptions {
|
||||
optIn.add("kotlinx.cinterop.ExperimentalForeignApi")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transitive Dependencies
|
||||
|
||||
SwiftPM dependencies are handled automatically. When you run Kotlin/Native tests or link a framework, the Kotlin Gradle Plugin will provision necessary machine code from transitive SwiftPM dependencies. This behavior is automatic.
|
||||
|
||||
You can optionally declare transitive dependencies explicitly to pin specific versions:
|
||||
|
||||
```kotlin
|
||||
swiftPMDependencies {
|
||||
// Main dependency
|
||||
swiftPackage(
|
||||
url = "https://github.com/firebase/firebase-ios-sdk.git",
|
||||
version = "12.5.0",
|
||||
products = listOf("FirebaseAnalytics"),
|
||||
)
|
||||
|
||||
// Transitive dependency with explicit version pin
|
||||
swiftPackage(
|
||||
url = url("https://github.com/apple/swift-protobuf.git"),
|
||||
version = exact("1.32.0"),
|
||||
products = listOf(product("SwiftProtobuf")),
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,145 @@
|
||||
# Migration Report Template
|
||||
|
||||
After migration (whether successful or not), write a comprehensive `MIGRATION_REPORT.md` in the project root. This document serves both as human-readable documentation and as structured input for AI agents analyzing the migration.
|
||||
|
||||
## Template
|
||||
|
||||
```markdown
|
||||
# Migration Report: CocoaPods to SwiftPM Import
|
||||
|
||||
**Project:** <project name>
|
||||
**Module migrated:** <module name>
|
||||
**Date:** <YYYY-MM-DD>
|
||||
**Kotlin version:** <old version> → <new version>
|
||||
**Status:** <Completed successfully | Completed with workarounds | Failed — see Errors>
|
||||
|
||||
---
|
||||
|
||||
## Pre-Migration State
|
||||
|
||||
### CocoaPods Dependencies
|
||||
|
||||
| Pod | Version | Mode | Notes |
|
||||
|-----|---------|------|-------|
|
||||
| <PodName> | <version> | Regular / linkOnly | <e.g., cinterop used in Kotlin code> |
|
||||
|
||||
### Framework Configuration
|
||||
|
||||
- **baseName:** <name>
|
||||
- **isStatic:** <true/false> → <true/false after migration>
|
||||
- **Deployment target:** <version>
|
||||
|
||||
### Kotlin Files Using `cocoapods.*` Imports
|
||||
|
||||
| File | Imports |
|
||||
|------|---------|
|
||||
| <path> | `cocoapods.<Module>.<Class>`, ... |
|
||||
|
||||
### Non-KMP CocoaPods
|
||||
|
||||
<List any pods in Podfile not managed by KMP, or "None">
|
||||
|
||||
### Atypical Project Configuration
|
||||
|
||||
<Document anything unusual found in Phase 1 that required special handling:
|
||||
EmbedAndSign disablers, commented-out build phases, custom Gradle tasks
|
||||
hooking into CocoaPods, non-standard framework configs, missing `group`
|
||||
property, etc. If nothing unusual, write "Standard configuration.">
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Phase 2: Gradle Configuration
|
||||
|
||||
<List exact changes made to settings.gradle.kts, libs.versions.toml,
|
||||
root build.gradle.kts, gradle.properties. Include before/after snippets
|
||||
for non-trivial changes.>
|
||||
|
||||
### Phase 3: swiftPMDependencies
|
||||
|
||||
<Show the complete `swiftPMDependencies {}` block added.
|
||||
Document decisions: why `discoverClangModulesImplicitly = false`,
|
||||
which `importedClangModules` were chosen and why, framework search
|
||||
paths added, static/dynamic choice, etc.>
|
||||
|
||||
### Phase 4: Import Transformations
|
||||
|
||||
<Table of import changes. Clearly mark any preserved `cocoapods.*` imports.>
|
||||
|
||||
| File | Before | After | Source |
|
||||
|------|--------|-------|--------|
|
||||
| <path> | `cocoapods.<Mod>.<Cls>` | `swiftPMImport.<grp>.<mod>.<Cls>` | swiftPMImport cinterop |
|
||||
| <path> | `cocoapods.<Mod>.<Cls>` | `cocoapods.<Mod>.<Cls>` (unchanged) | <library> bundled klib |
|
||||
|
||||
### Phase 5: iOS Project Reconfiguration
|
||||
|
||||
<Document: Option A or B, integration commands run, sandboxing fix,
|
||||
Crashlytics dSYM script update, any manual pbxproj edits.>
|
||||
|
||||
### Phase 6: CocoaPods Removal
|
||||
|
||||
<List everything removed: plugin, cocoapods block, gradle.properties
|
||||
entries, custom tasks, podspec files, Podfile changes.>
|
||||
|
||||
### Phase 7: Verification
|
||||
|
||||
<Build commands run and their outcomes. Include the final successful
|
||||
build command or note that verification was deferred to the user.>
|
||||
|
||||
---
|
||||
|
||||
## Errors Encountered
|
||||
|
||||
<For each error, use this structure:>
|
||||
|
||||
### Error #N: <Short title>
|
||||
|
||||
**Phase:** <which phase>
|
||||
**Symptom:** <exact error message or behavior>
|
||||
**Root cause:** <why it happened>
|
||||
**Fix:** <what was done to resolve it>
|
||||
**Generalizable:** <Yes/No — is this likely to affect other projects?>
|
||||
|
||||
---
|
||||
|
||||
## Non-Trivial Decisions
|
||||
|
||||
<Document decisions that required judgment, not just following the guide:
|
||||
- Why a specific `importedClangModules` list was chosen
|
||||
- Why `isStatic` was changed (or kept)
|
||||
- Why certain `cocoapods.*` imports were preserved
|
||||
- Framework search paths added and how the product list was determined
|
||||
- Any trade-offs made (e.g., disabling iOS tests)>
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
<Complete list of files modified, created, or deleted during migration.
|
||||
Group by type: Gradle files, Kotlin sources, Xcode project files, other.>
|
||||
|
||||
### Gradle Files
|
||||
- <path> — <brief description of change>
|
||||
|
||||
### Kotlin Sources
|
||||
- <path> — <brief description of change>
|
||||
|
||||
### Xcode Project Files
|
||||
- <path> — <brief description of change>
|
||||
|
||||
### Created
|
||||
- <path> — <what it is>
|
||||
|
||||
### Deleted
|
||||
- <path> — <what it was>
|
||||
```
|
||||
|
||||
## Writing Guidelines
|
||||
|
||||
- **Be specific.** Include actual file paths, class names, error messages. Avoid vague statements like "updated the config."
|
||||
- **Show before/after.** For non-trivial changes, include code snippets of what was changed and why.
|
||||
- **Explain the "why."** Every error and non-trivial decision should include root cause analysis, not just the fix.
|
||||
- **Mark preserved `cocoapods.*` imports clearly.** These are the most confusing aspect of the migration for future readers — explain exactly why each one was kept and which library provides the bundled klib.
|
||||
- **Flag generalizable issues.** Mark errors that are likely to affect other projects so this report can improve the migration tooling.
|
||||
- **Keep it machine-parseable.** Use consistent markdown headings, tables, and the `Error #N` format so AI agents can extract structured data.
|
||||
@@ -0,0 +1,606 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
Common issues and solutions when migrating from CocoaPods to SwiftPM.
|
||||
|
||||
## Gradle Issues
|
||||
|
||||
### Import Not Found After Migration
|
||||
|
||||
**Symptom:** `Unresolved reference` errors for classes that worked with CocoaPods
|
||||
|
||||
**Solution:** The import namespace follows a specific pattern:
|
||||
|
||||
```
|
||||
swiftPMImport.<group>.<module>.<ClassName>
|
||||
```
|
||||
|
||||
**Steps to fix:**
|
||||
1. Check `group` property in build.gradle.kts
|
||||
2. Replace `-` with `.` in both group and module names
|
||||
3. Run `./gradlew build` to see available classes in error messages
|
||||
|
||||
**Example:**
|
||||
```kotlin
|
||||
// If group = "org.jetbrains.kotlin.firebase-sample" and module = "kotlin-library"
|
||||
// Import becomes:
|
||||
import swiftPMImport.org.jetbrains.kotlin.firebase.sample.kotlin.library.FIRAnalytics
|
||||
// ^ ^ ^
|
||||
// dashes become dots --------+------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Gradle Sync Fails
|
||||
|
||||
**Symptom:** IDE fails to sync project after adding swiftPMDependencies
|
||||
|
||||
**Solution:**
|
||||
1. Invalidate caches: File > Invalidate Caches > Invalidate and Restart
|
||||
2. Run `./gradlew --refresh-dependencies`
|
||||
3. Check all repository declarations include JetBrains Maven
|
||||
|
||||
---
|
||||
|
||||
## Linker Issues
|
||||
|
||||
### Missing Symbols / Linker Errors
|
||||
|
||||
**Symptom:** `Undefined symbols for architecture` errors
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Run the linkage integration task** (one-time, not a build phase):
|
||||
```bash
|
||||
./gradlew :moduleName:integrateLinkagePackage
|
||||
```
|
||||
|
||||
2. **Verify SPM package is linked in Xcode:**
|
||||
- Open project in Xcode
|
||||
- Check Package Dependencies section
|
||||
- Ensure `KotlinMultiplatformLinkedPackage` is present
|
||||
|
||||
3. **Check framework configuration** — `isStatic = true` is recommended. While `isStatic = false` can work, dynamic frameworks have known edge cases with SwiftPM import (linker errors, dyld crashes, duplicate class warnings). It is required with `dev.gitlive:firebase-*` — see below.
|
||||
|
||||
---
|
||||
|
||||
### "No such module" in Xcode
|
||||
|
||||
**Symptom:** Xcode can't find the Kotlin module
|
||||
|
||||
**Solution:**
|
||||
1. Clean Xcode build folder: Shift+Cmd+K
|
||||
2. Re-run integration:
|
||||
```bash
|
||||
./gradlew :moduleName:integrateLinkagePackage
|
||||
```
|
||||
3. Restart Xcode completely
|
||||
4. Re-open the correct Xcode project file (`.xcodeproj` if all CocoaPods were removed, `.xcworkspace` if non-KMP CocoaPods remain)
|
||||
|
||||
---
|
||||
|
||||
## Build Phase Issues
|
||||
|
||||
### Build Phase Order Problems
|
||||
|
||||
**Symptom:** Swift compilation fails because Kotlin framework isn't ready
|
||||
|
||||
**Solution:** Ensure "Compile Kotlin" runs BEFORE "Compile Sources":
|
||||
|
||||
1. Open Xcode project
|
||||
2. Select app target > Build Phases
|
||||
3. Drag "Compile Kotlin" phase above "Compile Sources"
|
||||
|
||||
---
|
||||
|
||||
### Script Sandboxing Errors
|
||||
|
||||
**Symptom:** Gradle task `checkSandboxAndWriteProtection` fails during Xcode build:
|
||||
|
||||
```
|
||||
Execution failed for task ':moduleName:checkSandboxAndWriteProtection'.
|
||||
> User Script Sandboxing Enabled in Xcode Project
|
||||
```
|
||||
|
||||
Or build scripts can't access files or run Gradle.
|
||||
|
||||
**Cause:** Xcode 16+ enables User Script Sandboxing by default. The Gradle build phase needs to write to the project directory, which sandboxing prevents.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Disable via command line:
|
||||
```bash
|
||||
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' /path/to/iosApp/*.xcodeproj/project.pbxproj
|
||||
```
|
||||
If the setting is not present in the `.pbxproj` (Xcode defaults to YES without an explicit entry), open the project in Xcode instead.
|
||||
|
||||
2. Or disable in Xcode: select app target → Build Settings → Build Options → set "User Script Sandboxing" to NO
|
||||
|
||||
3. **Important:** After changing the setting, stop the Gradle daemon:
|
||||
```bash
|
||||
./gradlew --stop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Task Issues
|
||||
|
||||
### `integrateEmbedAndSign` Skipped or Does Nothing
|
||||
|
||||
**Symptom:** Running `integrateEmbedAndSign` completes without errors but the Xcode project is not modified. The `embedAndSignAppleFrameworkForXcode` build phase is not added or remains commented out.
|
||||
|
||||
**Cause:** The project has code that disables `EmbedAndSign` tasks. Common patterns:
|
||||
|
||||
```kotlin
|
||||
// In root or module build.gradle.kts
|
||||
project.gradle.taskGraph.whenReady {
|
||||
allTasks.filter { it::class.simpleName?.contains("EmbedAndSign") == true }.forEach {
|
||||
it.enabled = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This was a CocoaPods-era workaround that inadvertently disables `integrateEmbedAndSign`.
|
||||
|
||||
**Solution:** Remove the disabler code from `build.gradle.kts`, then re-run the integration command.
|
||||
|
||||
---
|
||||
|
||||
### `embedAndSignAppleFrameworkForXcode` Commented Out in Build Phase
|
||||
|
||||
**Symptom:** Xcode build succeeds but produces no Kotlin framework. The app crashes at runtime with missing module errors.
|
||||
|
||||
**Cause:** The Gradle invocation in the Xcode build phase script was commented out (prefixed with `#`) — possibly a pre-existing state from before migration.
|
||||
|
||||
**Solution:** Open `project.pbxproj` and uncomment the Gradle invocation:
|
||||
|
||||
```diff
|
||||
-#./gradlew :moduleName:embedAndSignAppleFrameworkForXcode
|
||||
+./gradlew :moduleName:embedAndSignAppleFrameworkForXcode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Third-Party KMP Libraries with Bundled Klibs
|
||||
|
||||
### `cocoapods.*` Class Not Found After Converting to `swiftPMImport.*`
|
||||
|
||||
**Symptom:** After replacing `import cocoapods.FirebaseMessaging.FIRMessaging` with `import swiftPMImport.<group>.<module>.FIRMessaging`, the build fails with `Unresolved reference 'FIRMessaging'`. Other swiftPMImport classes (e.g., `GIDSignIn`) resolve fine.
|
||||
|
||||
**Cause:** A third-party KMP library (e.g., [KMPNotifier](https://github.com/mirzemehdi/KMPNotifier) — `io.github.mirzemehdi:kmpnotifier`) bundles its own pre-built cinterop klib with the `cocoapods.FirebaseMessaging` namespace. The swiftPMDependencies cinterop generator detects these existing bindings and **deliberately skips** generating new bindings for that Clang module to avoid duplicate symbols. The `swiftPMImport.*` bindings for that module simply don't exist.
|
||||
|
||||
**Solution:** Revert the affected imports back to `cocoapods.*`:
|
||||
|
||||
```kotlin
|
||||
// These resolve to the third-party library's bundled klib, NOT actual CocoaPods
|
||||
import cocoapods.FirebaseMessaging.FIRMessaging
|
||||
import cocoapods.FirebaseMessaging.FIRMessagingAPNSTokenType
|
||||
```
|
||||
|
||||
The `cocoapods` prefix here is just a package namespace embedded in the library's published artifact — no CocoaPods infrastructure is needed at runtime.
|
||||
|
||||
**How to identify bundled klibs in advance:** Check if the project depends on KMP libraries that wrap iOS SDKs. Known libraries: [KMPNotifier](https://github.com/mirzemehdi/KMPNotifier) (bundles `cocoapods.FirebaseMessaging`). Also check for `linkOnly = true` pod declarations — this indicates the pod was only needed for linking while a KMP library provided the actual bindings.
|
||||
|
||||
**Inspecting klib contents:** Use `klib dump-metadata-signatures` to verify which classes a klib provides ([docs](https://kotlinlang.org/docs/native-libraries.html#using-kotlin-native-compiler)):
|
||||
|
||||
```bash
|
||||
# Find the klib
|
||||
find ~/.gradle/caches -name "*.klib" -path "*kmpnotifier*" | head -1
|
||||
|
||||
# Dump and search for the class in question
|
||||
klib dump-metadata-signatures /path/to/cinterop.klib | grep "FIRMessaging"
|
||||
# Output shows: cocoapods.FirebaseMessaging.FIRMessaging → confirms bundled klib
|
||||
```
|
||||
|
||||
You can also compare before/after migration by dumping the swiftPMImport klib:
|
||||
```bash
|
||||
# After build, find the swiftPMImport klib
|
||||
find . -name "*.klib" -path "*swiftPMImport*" | head -1
|
||||
|
||||
# Verify which classes are available
|
||||
klib dump-metadata-signatures /path/to/swiftPMImport.klib | grep "FIRMessaging"
|
||||
# Empty output = class NOT in swiftPMImport (must use cocoapods.* import)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## dev.gitlive/firebase-kotlin-sdk Issues
|
||||
|
||||
### `framework 'FirebaseCore' not found` (K/N Linker)
|
||||
|
||||
**Symptom:** Kotlin/Native linker fails with:
|
||||
```
|
||||
ld: framework 'FirebaseCore' not found
|
||||
```
|
||||
or similar errors for `FirebaseAuth`, `FirebaseFirestore`, etc. The Gradle compilation succeeds but the link step fails.
|
||||
|
||||
**Cause:** [firebase-kotlin-sdk](https://github.com/GitLiveApp/firebase-kotlin-sdk) (`dev.gitlive:firebase-*`) was published with CocoaPods-era cinterop klibs. These klibs have `-framework FirebaseCore`, `-framework FirebaseAuth`, etc. baked into their linker metadata. With CocoaPods, those frameworks were in `Pods/` on the search path. With SPM, they land in per-product subdirectories (`$BUILT_PRODUCTS_DIR/FirebaseCore/FirebaseCore.framework`) that the K/N linker doesn't search.
|
||||
|
||||
**Solution (two-part):**
|
||||
|
||||
**Part A — Gradle linkerOpts:**
|
||||
```kotlin
|
||||
iosTarget.binaries.framework {
|
||||
val builtProductsDir = System.getenv("BUILT_PRODUCTS_DIR")
|
||||
if (builtProductsDir != null) {
|
||||
listOf(
|
||||
"FirebaseCore", "FirebaseAuth", "FirebaseCoreExtension",
|
||||
"FirebaseCoreInternal", "FirebaseCrashlytics", "FirebaseFirestore",
|
||||
"FirebaseFirestoreInternal", "FirebaseInstallations", "FirebaseMessaging",
|
||||
"FirebaseStorage", "GoogleDataTransport", "GoogleUtilities",
|
||||
"GTMSessionFetcher", "AppCheckCore", "AppAuth", "GTMAppAuth",
|
||||
).forEach { product ->
|
||||
linkerOpts("-F", "$builtProductsDir/$product")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `if (builtProductsDir != null)` guard ensures `./gradlew :moduleName:compileKotlinIosSimulatorArm64` works without Xcode (compilation doesn't link).
|
||||
|
||||
**Part B — Xcode FRAMEWORK_SEARCH_PATHS:**
|
||||
|
||||
Add matching entries in `project.pbxproj` for both Debug and Release `buildSettings`:
|
||||
```
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(BUILT_PRODUCTS_DIR)/FirebaseCore",
|
||||
"$(BUILT_PRODUCTS_DIR)/FirebaseAuth",
|
||||
"$(BUILT_PRODUCTS_DIR)/FirebaseCoreExtension",
|
||||
// ... same list as Part A ...
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `dyld: Library not loaded: @rpath/FirebaseCore.framework/FirebaseCore` (Runtime Crash)
|
||||
|
||||
**Symptom:** The Gradle build and Xcode compilation both succeed, but the app crashes at launch with:
|
||||
```
|
||||
dyld: Library not loaded: @rpath/FirebaseCore.framework/FirebaseCore
|
||||
Referenced from: .../ComposeApp.framework/ComposeApp
|
||||
```
|
||||
|
||||
**Cause:** The KMP framework is **dynamic** (`isStatic = false` or default). The K/N linker creates `LC_LOAD_DYLIB` entries (`@rpath/FirebaseCore.framework/FirebaseCore`). Firebase SPM products are **static** libraries — their `.framework` bundles exist in `$BUILT_PRODUCTS_DIR` during build but are NOT embedded in the app bundle. At runtime, `dyld` searches `@rpath` and finds nothing.
|
||||
|
||||
**Solution:** Switch to a static framework:
|
||||
|
||||
```kotlin
|
||||
iosTarget.binaries.framework {
|
||||
baseName = "Shared"
|
||||
isStatic = true // Required when using dev.gitlive:firebase-* with SPM
|
||||
}
|
||||
```
|
||||
|
||||
With a static framework, all symbols are embedded in the `.a` archive. No `LC_LOAD_DYLIB` entries are created. Unresolved `-framework` flags from dev.gitlive klibs are deferred to the final Xcode app link, where `KotlinMultiplatformLinkedPackage` provides them.
|
||||
|
||||
**After switching to static, also:**
|
||||
1. Re-run `integrateLinkagePackage` — regenerates `Package.swift` with `type: .none` (static)
|
||||
2. Remove any "Embed Frameworks" copy phase for the KMP framework — static frameworks must NOT be embedded
|
||||
3. Add linker flags previously resolved by the K/N linker (e.g., `-framework Accelerate`, `-weak_framework CoreML`) to `OTHER_LDFLAGS` in the Xcode project
|
||||
|
||||
---
|
||||
|
||||
## Firebase-Specific Issues
|
||||
|
||||
### cinterop Failures on C++ Modules (gRPC, abseil, leveldb, BoringSSL)
|
||||
|
||||
**Symptom:** Build fails with cinterop errors on modules like `grpc`, `absl`, `leveldb`, `openssl_grpc`, or other C++ transitive dependencies of Firebase.
|
||||
|
||||
**Cause:** `discoverClangModulesImplicitly = true` (the default) makes Kotlin attempt cinterop on every Clang module in the dependency graph, including C++ modules that are not compatible.
|
||||
|
||||
**Solution:** Set `discoverClangModulesImplicitly = false` and explicitly list only the Firebase Clang modules you need:
|
||||
|
||||
```kotlin
|
||||
swiftPMDependencies {
|
||||
discoverClangModulesImplicitly = false
|
||||
|
||||
swiftPackage(
|
||||
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
|
||||
version = from("12.6.0"),
|
||||
products = listOf(product("FirebaseAnalytics"), /* ... */),
|
||||
importedClangModules = listOf("FirebaseAnalytics", "FirebaseCore", /* ... */),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See [common-pods-mapping.md](common-pods-mapping.md) for the full importedClangModules reference.
|
||||
|
||||
---
|
||||
|
||||
### Firebase Classes Not Found (Wrong Clang Module Name)
|
||||
|
||||
**Symptom:** `Unresolved reference` for Firebase classes like `FIRDatabase`, `FIRRemoteConfig`, `FIRFirestore`, `FIRInAppMessaging` even though the product is listed.
|
||||
|
||||
**Cause:** Several Firebase products expose ObjC headers through Clang modules whose names differ from the SPM product name. Using the product name in `importedClangModules` won't find the headers.
|
||||
|
||||
**Solution:** Use the correct internal Clang module names:
|
||||
|
||||
| SPM Product | Correct importedClangModules entry |
|
||||
|---|---|
|
||||
| FirebaseDatabase | `FirebaseDatabaseInternal` |
|
||||
| FirebaseFirestore | `FirebaseFirestoreInternal` |
|
||||
| FirebaseInAppMessaging-Beta | `FirebaseInAppMessagingInternal` |
|
||||
| FirebaseRemoteConfig | `FirebaseRemoteConfigInternal` |
|
||||
|
||||
---
|
||||
|
||||
### FirebaseFirestore Import Errors
|
||||
|
||||
**Symptom:** Can't import FIRFirestore classes
|
||||
|
||||
**Cause:** Firestore's Clang module name differs from product name. The internal Clang module exposed to Objective-C is `FirebaseFirestoreInternal`, not `FirebaseFirestore`.
|
||||
|
||||
**Solution:** Add explicit importedClangModules:
|
||||
|
||||
```kotlin
|
||||
swiftPackage(
|
||||
url = url("https://github.com/firebase/firebase-ios-sdk.git"),
|
||||
version = from("12.6.0"),
|
||||
products = listOf(product("FirebaseFirestore")),
|
||||
importedClangModules = listOf("FirebaseFirestoreInternal"), // Required
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Firebase Crashlytics: dSYM Upload Script Broken After Migration
|
||||
|
||||
**Symptom:** Crash reports don't appear in Firebase Console after migrating to SPM. Or the build phase fails with "No such file" errors referencing `${PODS_ROOT}/FirebaseCrashlytics/upload-symbols`.
|
||||
|
||||
**Cause:** The CocoaPods-era dSYM upload script references `${PODS_ROOT}` which no longer exists. The SPM equivalent is at a different path.
|
||||
|
||||
**Solution:** Update the existing "Run Script" build phase (or add one at the END if none exists):
|
||||
|
||||
```bash
|
||||
"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run"
|
||||
```
|
||||
|
||||
With input files:
|
||||
```
|
||||
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
|
||||
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
|
||||
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist
|
||||
$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist
|
||||
$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)
|
||||
```
|
||||
|
||||
Also set **Debug Information Format** to `DWARF with dSYM File` for all build configurations in Build Settings.
|
||||
|
||||
---
|
||||
|
||||
### Firebase Beta Products: SPM Name Differs
|
||||
|
||||
**Symptom:** `FirebaseInAppMessaging` or `FirebaseAppDistribution` not found as SPM product
|
||||
|
||||
**Cause:** Beta products have a `-Beta` suffix in SPM.
|
||||
|
||||
**Solution:** Use the correct SPM product name:
|
||||
- `FirebaseInAppMessaging` → `FirebaseInAppMessaging-Beta`
|
||||
- `FirebaseAppDistribution` → `FirebaseAppDistribution-Beta`
|
||||
|
||||
---
|
||||
|
||||
### dyld Crash When Mixing Firebase Across CocoaPods and SPM
|
||||
|
||||
**Symptom:** App crashes at launch with a dyld error like:
|
||||
```
|
||||
Symbol not found: _OBJC_CLASS_$_FIRFirestore
|
||||
```
|
||||
or similar `_OBJC_CLASS_$_FIR*` symbol-not-found errors. The Gradle build and Xcode compilation both succeed, but the app crashes at runtime.
|
||||
|
||||
**Cause:** Some Firebase pods were migrated to SPM while others remained in CocoaPods. All Firebase products share transitive dependencies (gRPC, abseil, leveldb, BoringSSL, nanopb). Having both package managers link these transitive dependencies causes duplicate/conflicting symbols that the dynamic linker cannot resolve.
|
||||
|
||||
**Solution:** Migrate **all** Firebase pods to SPM at once. This includes Swift-only pods (FirebaseAI, FirebaseFunctions, FirebaseMLModelDownloader) that Kotlin cannot use directly — add them as `products` entries without `importedClangModules`:
|
||||
|
||||
```kotlin
|
||||
products = listOf(
|
||||
// ObjC pods used by Kotlin:
|
||||
product("FirebaseAnalytics"),
|
||||
product("FirebaseAuth"),
|
||||
// ...
|
||||
// Swift-only pods (no importedClangModules needed):
|
||||
product("FirebaseAI"),
|
||||
product("FirebaseFunctions"),
|
||||
),
|
||||
```
|
||||
|
||||
After adding new products, re-run `integrateLinkagePackage` to regenerate the linkage Swift package.
|
||||
|
||||
---
|
||||
|
||||
### Firebase Initialization Fails at Runtime
|
||||
|
||||
**Symptom:** App crashes on Firebase initialization
|
||||
|
||||
**Solution:**
|
||||
1. Ensure `GoogleService-Info.plist` is in iOS app target
|
||||
2. Call `FIRApp.configure()` before using any Firebase service
|
||||
3. Check Firebase console for configuration issues
|
||||
|
||||
---
|
||||
|
||||
## Google Maps Issues
|
||||
|
||||
### GoogleMaps Version Not Found
|
||||
|
||||
**Symptom:** SPM can't resolve GoogleMaps package
|
||||
|
||||
**Solution:** GoogleMaps requires exact version matching:
|
||||
|
||||
```kotlin
|
||||
swiftPackage(
|
||||
url = url("https://github.com/googlemaps/ios-maps-sdk.git"),
|
||||
version = exact("10.6.0"), // Must use exact(), not from()
|
||||
products = listOf(
|
||||
product("GoogleMaps", platforms = setOf(iOS()))
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
Check [releases page](https://github.com/googlemaps/ios-maps-sdk/releases) for valid versions.
|
||||
|
||||
---
|
||||
|
||||
## KSP (Kotlin Symbol Processing) Compatibility
|
||||
|
||||
### KSP after updating Kotlin version
|
||||
|
||||
KSP should generally work with the target Kotlin version without any changes. Do NOT update KSP as part of the migration — it is out of scope.
|
||||
|
||||
If KSP does fail (unlikely), the issue is unrelated to the CocoaPods-to-SwiftPM migration itself. Present the error to the user and let them decide how to handle it separately.
|
||||
|
||||
---
|
||||
|
||||
## Manual Integration Command Discovery
|
||||
|
||||
If the xcodebuild approach in Phase 5.1 fails, discover paths manually and run integration tasks directly:
|
||||
|
||||
```bash
|
||||
# Find iOS project directory (contains Podfile)
|
||||
IOS_DIR=$(dirname "$(find . -name "Podfile" -type f | head -1)")
|
||||
|
||||
# Find .xcodeproj (exclude Pods.xcodeproj) - use realpath for absolute path
|
||||
XCODEPROJ=$(realpath "$(find "$IOS_DIR" -maxdepth 1 -name "*.xcodeproj" -type d | grep -v Pods | head -1)")
|
||||
|
||||
# Find KMP module with swiftPMDependencies (module directory name)
|
||||
KMP_MODULE=$(grep -rl "swiftPMDependencies" --include="build.gradle.kts" . | head -1 | xargs dirname | xargs basename)
|
||||
|
||||
XCODEPROJ_PATH="$XCODEPROJ" \
|
||||
GRADLE_PROJECT_PATH=":$KMP_MODULE" \
|
||||
./gradlew ":$KMP_MODULE:integrateEmbedAndSign" ":$KMP_MODULE:integrateLinkagePackage"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manual CocoaPods Deintegration from pbxproj
|
||||
|
||||
If `pod deintegrate` is not available, manually remove these CocoaPods references from `project.pbxproj`:
|
||||
|
||||
- `Pods_<target>.framework` build file and file reference
|
||||
- `Pods-<target>.debug.xcconfig` / `Pods-<target>.release.xcconfig` file references
|
||||
- `Pods` group and `Frameworks` group (if it only contained the Pods framework)
|
||||
- `[CP] Check Pods Manifest.lock` shell script build phase
|
||||
- `[CP] Embed Pods Frameworks` shell script build phase
|
||||
- `baseConfigurationReference` lines pointing to Pods xcconfig files
|
||||
|
||||
---
|
||||
|
||||
## Manual Xcode Integration Steps
|
||||
|
||||
If the automatic `integrateEmbedAndSign` / `integrateLinkagePackage` tasks fail, set up the Xcode project manually:
|
||||
|
||||
1. Open `.xcodeproj` (or `.xcworkspace` if non-KMP CocoaPods remain)
|
||||
2. Add "Compile Kotlin" run script phase BEFORE "Compile Sources":
|
||||
```bash
|
||||
cd "$SRCROOT/.."
|
||||
./gradlew :moduleName:embedAndSignAppleFrameworkForXcode
|
||||
```
|
||||
3. Set `ENABLE_USER_SCRIPT_SANDBOXING = NO` (Build Settings → Build Options → User Script Sandboxing)
|
||||
4. Run `./gradlew --stop` to restart the Gradle daemon after changing sandboxing
|
||||
5. Add local package: `../moduleName/KotlinMultiplatformLinkedPackage`
|
||||
|
||||
---
|
||||
|
||||
## When Build Fails After Migration
|
||||
|
||||
**Do NOT revert the migration as a first response.** Instead:
|
||||
|
||||
1. **Read the full error log** — identify the actual failure type (Gradle resolution, import not found, linker error, Xcode build phase)
|
||||
2. **Re-check each migration phase** — walk through Phases 2-6 and verify each step was applied. Common mistakes:
|
||||
- Missing JetBrains Maven repo in `settings.gradle.kts`
|
||||
- Wrong `group` or module name in import namespace (dashes not converted to dots)
|
||||
- `cocoapods {}` block or plugin not fully removed (Phase 6)
|
||||
- Wrong Xcode project file opened (`.xcodeproj` when non-KMP CocoaPods remain and `.xcworkspace` is needed, or vice versa)
|
||||
- `isStatic = true` missing from framework config (required with dev.gitlive or similar CocoaPods-era wrapper klibs)
|
||||
- `integrateLinkagePackage` not run
|
||||
- EmbedAndSign disabler code not removed (prevents `integrateEmbedAndSign`)
|
||||
- `embedAndSignAppleFrameworkForXcode` commented out in Xcode build phase
|
||||
- `cocoapods.*` imports replaced that should have been preserved (bundled klib from third-party library)
|
||||
3. **Consult the sections above** for specific error patterns
|
||||
4. **If unsure, present options to the user** — describe what the logs show, list possible causes, and let the user decide
|
||||
|
||||
---
|
||||
|
||||
## Rollback Instructions (Last Resort)
|
||||
|
||||
Only revert if analysis above does not resolve the issue:
|
||||
|
||||
### Step 1: Restore Git Files
|
||||
|
||||
```bash
|
||||
# Restore CocoaPods files (adjust path if iOS project is not in iosApp/)
|
||||
git checkout -- "**/Podfile" "**/Podfile.lock"
|
||||
git checkout -- *.podspec
|
||||
git checkout -- **/build.gradle.kts
|
||||
git checkout -- **/src/**/*.kt
|
||||
```
|
||||
|
||||
### Step 2: Restore CocoaPods in build.gradle.kts
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
kotlin("native.cocoapods") // Re-add
|
||||
}
|
||||
|
||||
kotlin {
|
||||
cocoapods {
|
||||
// Restore original configuration
|
||||
}
|
||||
// Remove swiftPMDependencies block
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Restore Kotlin Imports
|
||||
|
||||
Change all imports back:
|
||||
```kotlin
|
||||
// FROM:
|
||||
import swiftPMImport.group.module.ClassName
|
||||
|
||||
// TO:
|
||||
import cocoapods.PodName.ClassName
|
||||
```
|
||||
|
||||
### Step 4: Reinstall CocoaPods
|
||||
|
||||
```bash
|
||||
# Navigate to directory containing Podfile (adjust path as needed)
|
||||
cd <ios-project-directory> # e.g., iosApp/, ios/, or project root
|
||||
pod install
|
||||
```
|
||||
|
||||
### Step 5: Open Workspace
|
||||
|
||||
Open `*.xcworkspace` (not .xcodeproj) from the iOS project directory in Xcode.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
If issues persist:
|
||||
|
||||
1. **Check sample projects:**
|
||||
- [kmp-with-cocoapods-compose-sample (spm_import branch)](https://github.com/Kotlin/kmp-with-cocoapods-compose-sample/tree/spm_import)
|
||||
- [kmp-with-cocoapods-firebase-sample (spm_import branch)](https://github.com/Kotlin/kmp-with-cocoapods-firebase-sample/tree/spm_import)
|
||||
|
||||
2. **Run verbose build:**
|
||||
```bash
|
||||
./gradlew build --info
|
||||
```
|
||||
|
||||
3. **Check generated files:**
|
||||
- Look in `moduleName/KotlinMultiplatformLinkedPackage/` for Package.swift
|
||||
|
||||
4. **Inspect klib contents** using the `klib` tool ([docs](https://kotlinlang.org/docs/native-libraries.html#using-kotlin-native-compiler)):
|
||||
```bash
|
||||
# Dump all API signatures from a klib
|
||||
klib dump-metadata-signatures /path/to/library.klib
|
||||
|
||||
# Search for specific classes
|
||||
klib dump-metadata-signatures /path/to/library.klib | grep "ClassName"
|
||||
|
||||
# Compare before/after — find klibs in build output
|
||||
find . -name "*.klib" -path "*swiftPMImport*" # new swiftPMImport klibs
|
||||
find ~/.gradle/caches -name "*.klib" -path "*libraryName*" # third-party klibs
|
||||
```
|
||||
This is particularly useful for verifying which classes are available in the swiftPMImport klib vs. bundled in third-party dependency klibs.
|
||||
Reference in New Issue
Block a user