23 KiB
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:
- Check
groupproperty in build.gradle.kts - Replace
-with.in both group and module names - Run
./gradlew buildto see available classes in error messages
Example:
// 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:
- Invalidate caches: File > Invalidate Caches > Invalidate and Restart
- Run
./gradlew --refresh-dependencies - Check all repository declarations include JetBrains Maven
Linker Issues
Missing Symbols / Linker Errors
Symptom: Undefined symbols for architecture errors
Solutions:
-
Run the linkage integration task (one-time, not a build phase):
./gradlew :moduleName:integrateLinkagePackage -
Verify SPM package is linked in Xcode:
- Open project in Xcode
- Check Package Dependencies section
- Ensure
KotlinMultiplatformLinkedPackageis present
-
Check framework configuration —
isStatic = trueis recommended. WhileisStatic = falsecan work, dynamic frameworks have known edge cases with SwiftPM import (linker errors, dyld crashes, duplicate class warnings). It is required withdev.gitlive:firebase-*— see below.
"No such module" in Xcode
Symptom: Xcode can't find the Kotlin module
Solution:
- Clean Xcode build folder: Shift+Cmd+K
- Re-run integration:
./gradlew :moduleName:integrateLinkagePackage - Restart Xcode completely
- Re-open the correct Xcode project file (
.xcodeprojif all CocoaPods were removed,.xcworkspaceif 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":
- Open Xcode project
- Select app target > Build Phases
- 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:
-
Disable via command line:
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' /path/to/iosApp/*.xcodeproj/project.pbxprojIf the setting is not present in the
.pbxproj(Xcode defaults to YES without an explicit entry), open the project in Xcode instead. -
Or disable in Xcode: select app target → Build Settings → Build Options → set "User Script Sandboxing" to NO
-
Important: After changing the setting, stop the Gradle daemon:
./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:
// 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:
-#./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 — 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.*:
// 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 (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):
# 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:
# 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 (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:
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:
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:
- Re-run
integrateLinkagePackage— regeneratesPackage.swiftwithtype: .none(static) - Remove any "Embed Frameworks" copy phase for the KMP framework — static frameworks must NOT be embedded
- Add linker flags previously resolved by the K/N linker (e.g.,
-framework Accelerate,-weak_framework CoreML) toOTHER_LDFLAGSin 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:
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 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:
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):
"${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-BetaFirebaseAppDistribution→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:
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:
- Ensure
GoogleService-Info.plistis in iOS app target - Call
FIRApp.configure()before using any Firebase service - 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:
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 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:
# 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>.frameworkbuild file and file referencePods-<target>.debug.xcconfig/Pods-<target>.release.xcconfigfile referencesPodsgroup andFrameworksgroup (if it only contained the Pods framework)[CP] Check Pods Manifest.lockshell script build phase[CP] Embed Pods Frameworksshell script build phasebaseConfigurationReferencelines pointing to Pods xcconfig files
Manual Xcode Integration Steps
If the automatic integrateEmbedAndSign / integrateLinkagePackage tasks fail, set up the Xcode project manually:
- Open
.xcodeproj(or.xcworkspaceif non-KMP CocoaPods remain) - Add "Compile Kotlin" run script phase BEFORE "Compile Sources":
cd "$SRCROOT/.." ./gradlew :moduleName:embedAndSignAppleFrameworkForXcode - Set
ENABLE_USER_SCRIPT_SANDBOXING = NO(Build Settings → Build Options → User Script Sandboxing) - Run
./gradlew --stopto restart the Gradle daemon after changing sandboxing - Add local package:
../moduleName/KotlinMultiplatformLinkedPackage
When Build Fails After Migration
Do NOT revert the migration as a first response. Instead:
- Read the full error log — identify the actual failure type (Gradle resolution, import not found, linker error, Xcode build phase)
- 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
groupor module name in import namespace (dashes not converted to dots) cocoapods {}block or plugin not fully removed (Phase 6)- Wrong Xcode project file opened (
.xcodeprojwhen non-KMP CocoaPods remain and.xcworkspaceis needed, or vice versa) isStatic = truemissing from framework config (required with dev.gitlive or similar CocoaPods-era wrapper klibs)integrateLinkagePackagenot run- EmbedAndSign disabler code not removed (prevents
integrateEmbedAndSign) embedAndSignAppleFrameworkForXcodecommented out in Xcode build phasecocoapods.*imports replaced that should have been preserved (bundled klib from third-party library)
- Missing JetBrains Maven repo in
- Consult the sections above for specific error patterns
- 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
# 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
plugins {
kotlin("native.cocoapods") // Re-add
}
kotlin {
cocoapods {
// Restore original configuration
}
// Remove swiftPMDependencies block
}
Step 3: Restore Kotlin Imports
Change all imports back:
// FROM:
import swiftPMImport.group.module.ClassName
// TO:
import cocoapods.PodName.ClassName
Step 4: Reinstall CocoaPods
# 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:
-
Check sample projects:
-
Run verbose build:
./gradlew build --info -
Check generated files:
- Look in
moduleName/KotlinMultiplatformLinkedPackage/for Package.swift
- Look in
-
Inspect klib contents using the
klibtool (docs):# 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 klibsThis is particularly useful for verifying which classes are available in the swiftPMImport klib vs. bundled in third-party dependency klibs.