Kotlin Multiplatform Help

Adding Swift packages as dependencies to KMP modules

Kotlin Gradle plugin with SwiftPM import integration allows you to import Objective-C APIs from Objective-C and Swift code using SwiftPM dependencies declared for your Apple targets.

For transitive dependencies (projects that depend on those that use SwiftPM import), the Kotlin Gradle plugin automatically provides the necessary machine code from SwiftPM dependencies. For example, you don't need to do any additional configuration when running Kotlin/Native tests or linking a framework.

To configure your project:

  1. Set up your development environment

  2. Add and use the SwiftPM dependencies in your KMP module

Set the Kotlin Multiplatform Gradle plugin version

To try out the SwiftPM import functionality, make sure that you are using the 2.4.0-Beta1 version of the Kotlin Multiplatform Gradle plugin. Example for a gradle/libs.versions.toml file:

[versions] kotlin = "2.4.0-Beta1" [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }

Add and use SwiftPM dependencies

Configure the build

Specific SwiftPM dependencies can be added in the swiftPMDependencies {} block of the build.gradle.kts file, where your Apple targets are declared. For example, for Firebase:

kotlin { iosArm64() iosSimulatorArm64() iosX64() swiftPMDependencies { // Import FirebaseAnalytics into your Kotlin code swiftPackage( url = url("https://github.com/firebase/firebase-ios-sdk.git"), version = from("12.5.0"), products = listOf(product("FirebaseAnalytics")), ) // swift-protobuf is a transitive Firebase dependency, // so you only need to include it // if you want to use a specific version swiftPackage( url = url("https://github.com/apple/swift-protobuf.git"), version = exact("1.32.0"), products = listOf(), ) } }

SwiftPM integration is based on importing Clang modules. By default, the import mechanism automatically discovers Clang modules in specified Swift packages and makes all available modules accessible to Kotlin code — similar to how API visibility works in Swift and Objective-C.

To disable the default behavior and automatic module discovery, set the discoverClangModulesImplicitly to false. When module discovery is disabled, SwiftPM import uses product names as Clang module names.

To import Clang modules whose names differ from product names, use the importedClangModules parameter, for example:

kotlin { swiftPMDependencies { // If 'discoverClangModulesImplicitly' was set to 'true', // the 'importedClangModules' parameter below would be ignored discoverClangModulesImplicitly = false // Imported packages, their products, and Clang modules swiftPackage( url = url("https://github.com/firebase/firebase-ios-sdk.git"), version = from("12.5.0"), products = listOf( product("FirebaseAnalytics"), product("FirebaseFirestore") ), importedClangModules = listOf( "FirebaseAnalytics", // Objective-C APIs of FirebaseFirestore are located // in the 'FirebaseFirestoreInternal' Clang module "FirebaseFirestoreInternal" ), ) } }

Set platform constraints

Some SwiftPM dependencies may not compile or provide valid APIs for all targets in your build script. For example, the Google Maps SDK currently only supports iOS targets.

If your project only targets iOS, you don't need to declare platforms explicitly. But as soon as you add another target (for example, macOS), you need to specify the platform constraint for each dependency.

To make sure that a dependency is applied only for relevant compilations, specify the correct targets in the platforms parameter of a product specification:

kotlin { iosArm64() iosSimulatorArm64() iosX64() macosArm64() swiftPMDependencies { swiftPackage( url = url("https://github.com/googlemaps/ios-maps-sdk.git"), version = exact("10.3.0"), products = listOf( product( "GoogleMaps", platforms = setOf( // The `GoogleMaps` package will be visible // only to iOS compilations iOS() ) ) ) ) } }

Run the SwiftPM integration task

SwiftPM import tooling generates an intermediary package to keep track of the list of the current SwiftPM dependencies. When you add a SwiftPM dependency to your project for the first time, you need to link your Xcode project with the generated package.

To do that, run the special Gradle task with the following command in your project's directory:

XCODEPROJ_PATH='/path/to/project/iosApp/iosApp.xcodeproj' ./gradlew :kotlin-library:integrateLinkagePackage

The command will generate the SwiftPM package and perform the necessary changes in the Xcode project. Make sure to commit the generated package, as well as the updated Xcode project, to your repository.

After the initial integration, the synthetic package will be updated automatically every time you change the set of SwiftPM dependencies or their versions.

Use imported APIs

Imported Objective-C APIs are contained in namespaces that start with the swiftPMImport prefix and end with Gradle names of the project and its group.

For example, the Kotlin build script specifies the group name as follows:

// subproject/build.gradle.kts group = "groupName"

Here, groupName is the Gradle group name of the project, and subproject is the project name. Now you can import Firebase APIs in the iosMain source set of that module, for example:

// subproject/src/iosMain/kotlin/useFirebaseAnalytics.kt import swiftPMImport.groupName.subproject.FIRAnalytics import swiftPMImport.groupName.subproject.FIRApp

Generated Package.resolved file

To make builds that depend on Swift packages more stable, SwiftPM import tooling introduces a locking mechanism: the Package.resolved file generated during the initial package resolution is copied into the project's directory and reused for subsequent builds.

The lock file is updated automatically when you change the set or versions of SwiftPM dependencies in your build script.

If you want to force an update of the lock file manually:

  1. Delete the build directory and the existing Package.resolved file.

  2. Run the dependency resolution task again: ./gradlew :yourModuleName:fetchSyntheticImportProjectPackages.

Additional import options

Importing local Swift packages

The SwiftPM import mechanism also allows importing Swift packages from the local file system.

Let's consider a Swift package with the following manifest, located in the /path/to/ExamplePackage directory:

// /path/to/ExamplePackage/Package.swift let package = Package( name: "ExamplePackage", platforms: [.iOS("15.0")], products: [ .library(name: "ExamplePackage", targets: ["ExamplePackage"]), ], dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", exact: "1.27.0",), ], targets: [ // This target can be implemented in Swift with @objc API or in Objective-C .target(name: "ExamplePackage", dependencies: [.product(name: "GRPC", package: "grpc-swift")]), ] )

To import it in your Kotlin build script, use the localSwiftPackage API:

// <projectDir>/shared/build.gradle.kts kotlin { swiftPMDependencies { localSwiftPackage( directory = project.layout.projectDirectory.dir("/path/to/ExamplePackage/"), products = listOf("ExamplePackage") ) } }

Sync the Gradle files to perform SwiftPM import, then use the imported APIs in your Kotlin code:

// /path/to/shared/src/appleMain/kotlin/useExamplePackage.kt @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) fun useExamplePackage() { // If the Swift package is successfully imported, // the IDE suggests the correct import for the class HelloFromExamplePackage().hello() }

Specific deployment versions

If your dependencies require a higher deployment version, specify it in the *MinimumDeploymentTarget parameter. For example, for iOS:

kotlin { swiftPMDependencies { iosMinimumDeploymentTarget.set("16.0") } }

Location and version of Swift packages

Similar to Package.swift manifest files, you can specify the location and version of your Swift package in the swiftPackage() call. Both have a couple of mutually exclusive options.

To set the location, you can use a URL or a SwiftPM registry ID:

swiftPackage( // Option 1, URL string // Points to the Git repository of the package url = url("https://github.com/firebase/firebase-ios-sdk.git") // Option 2, Swift Package Registry ID // See Apple documentation on using a package registry linked above repository = id("...") )

To specify the version, use the following Gradle- and Git-style version specifications:

swiftPackage( // Similar to the Gradle 'require' version constraint, // starting with the specified version version = from("1.0") // Similar to the Gradle 'strict' version constraint, // exactly matching the specified version version = exact("2.0") // Git-specific version specification, // matching the specified branch or revision version = branch("master") // Or version = revision("e74b07278b926c9ec6f9643455ea00d1ce04a021") )

Known limitations with dynamic Kotlin/Native frameworks

Currently, SwiftPM import integration doesn't support all edge cases that might arise when producing a dynamic Kotlin/Native framework. You might encounter issues during the build in Xcode or see warnings at runtime, for example:

  • Undefined symbols for architecture ...: "...", referenced from: ld: symbol(s) not found ...

  • dyld: Symbol not found: ...

  • objc[...]: Class _Foo is implemented in both /path/to/Shared and /path/to/Bar. This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.

A general fix for these issues is to change the linkage mode of your framework by setting the isStatic property to true:

// shared/build.gradle.kts kotlin { listOf( iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> iosTarget.binaries.framework { baseName = "Shared" // Set this property to "true" isStatic = true } } }

If you encountered any of these issues, need to keep isStatic=false, or if changing this property didn't help resolve build failures, let us know in our Slack channel. Get an invite and join #kmp-swift-package-manager.

What's next?

Learn more about switching from CocoaPods to SwiftPM dependencies in your KMP project.

01 April 2026