spm4kmp Documentation

repository·main·Indexed 18 days ago

https://github.com/frankois944/spm4kmp

A Gradle plugin providing a modern alternative to the deprecated CocoaPods plugin for Kotlin Multiplatform (KMP) projects. It enables the integration of Swift Packages and custom Swift code into Apple-targeted KMP projects using the native Swift Package Manager. Key features include the creation of a Swift bridge to expose Swift/Apple SDK code to Kotlin via cinterop, support for remote, local, and binary dependencies, and the ability to export Objective-C compatible packages directly to Kotlin.

Tokens
18.6K
Snippets
79
Records
104
Agent score
63%

What's inside spm4kmp

  1. Overview of spmForKmp

    main

    spmForKmp is a Gradle plugin designed as a modern alternative to the deprecated CocoaPods plugin for Kotlin Multiplatform (KMP) projects targeting Apple platforms.

    It enables the integration of Swift Packages and facilitates Swift↔Kotlin communication by leveraging the native Swift Package Manager (SPM) directly, without requiring third-party dependencies.

    Key Capabilities:

    • Swift-Import to Kotlin: Import your own Swift code to provide functionality that is unavailable in Kotlin.
    • SPM third-party dependencies: Add external Swift packages and utilize them within your bridge.
    • Export to Kotlin: Expose SPM dependencies and Swift code directly in Kotlin (subject to compatibility constraints).
  2. Capabilities of the spmForKmp AI Agent Skill

    main

    The spmForKmp Agent Skill provides the following capabilities for KMP developers:

    • Setup & Configuration: Automatically configures the spmForKmp plugin in your build.gradle.kts.
    • CocoaPods migration: Migrates existing KMP CocoaPods projects to spmForKmp.
    • Dependency Management: Helps add, update, or remove Swift Package dependencies.
    • Bridge Generation: Assists in creating @objcMembers Swift bridge code for improved Kotlin compatibility.
    • Troubleshooting: Diagnoses and fixes common issues related to SPM integration in KMP.
    • Best Practices: Provides guidance on project structure and SPM/KMP interoperability.
  3. How the Swift Bridge works

    main

    The bridge is a directory of Swift files that acts as the glue between Swift/Apple SDK code and Kotlin. The plugin compiles these files and exposes them to Kotlin via cinterop.

    Folder Structure:

    • When cinteropName is set: src/swift/[cinteropName]
    • Default: src/swift/[targetName] (e.g., src/swift/iosArm64)

    Startup Behavior: If the bridge folder is empty, the plugin automatically generates a StartYourBridgeHere.swift template containing examples. To disable this, set spmforkmp.disableStartupFile=true in your gradle.properties.

  4. Avoid issues with Multiple Configurations on the Same Target

    main

    Applying multiple configurations to a single target is supported but can cause stability and performance issues, including:

    • Increased Build Times: Each configuration creates an isolated workspace, prolonging builds.
    • Workspace Conflicts: Simultaneous configurations can cause "missing file" errors due to caching conflicts.

    Best Practice: Consolidate your configurations or use separate targets whenever possible.

  5. Important limitation: Pure Swift Packages

    main

    When using spmForKmp, be aware that Pure Swift packages cannot be exported directly to Kotlin.

    To work around this limitation, the plugin helps you create a bridge to facilitate communication between the two. This is currently the recommended approach until native Swift import is supported directly in KMP.

  6. Requirements for Swift code in local packages

    main

    For your Swift code to be accessible from Kotlin, you must follow these visibility and compatibility rules:

    1. Mark as @objc/@objcMembers: Use the @objc or @objcMembers attribute so the code is compatible with the Objective-C runtime used by Kotlin.
    2. Set visibility to public: All classes and methods you wish to call from Kotlin must be marked as public.
    3. Inherit from NSObject: Classes intended for export should typically inherit from NSObject.
    import StripePaymentSheet
    
    @objcMembers public class MyStripeSDK: NSObject {
    
        private var paymentSheet: PaymentSheet?
        private var paymentIntentClientSecret: String
        private let backendCheckoutUrl = URL(string: "Your backend endpoint/payment-sheet")
    
        public init(paymentIntentClientSecret: String) {
            self.paymentIntentClientSecret = paymentIntentClientSecret
        }
    
        public func doStripeJob() {
            var configuration = PaymentSheet.Configuration()
            configuration.merchantDisplayName = "Example, Inc."
            self.paymentSheet = PaymentSheet(paymentIntentClientSecret: self.paymentIntentClientSecret,
                                             configuration: configuration)
        }
    }
  7. Expose bridge dependencies to your iOS application code

    main

    By default, dependencies added to your bridge are only visible to the bridge's Swift files and are not visible to your main iOS application code.

    To make a dependency available to your app, you must use the includeProduct configuration option. This instructs the plugin to bundle the dependency into a local package that Xcode can pick up.

  8. Understand the limitations of Pure Swift packages in KMP

    main

    A Pure Swift package is written entirely in Swift without Objective-C headers or compatibility layers. Because Kotlin Multiplatform (KMP) relies on the Objective-C runtime for communication between languages, it cannot directly interoperate with Pure Swift code.

    To make Swift code visible to Kotlin, it must be explicitly marked for Objective-C compatibility using @objc or @objcMembers.

    How to identify a Pure Swift package:

    • Check the Languages section on the repository's GitHub page. If it shows only Swift (and no Objective-C), it is likely a Pure Swift package.
    • Older libraries (e.g., Firebase) often maintain Objective-C compatibility and are easier to export directly.
    • Modern libraries often prefer Swift-only APIs and lack this compatibility.
  9. Migrate from CocoaPods to spmForKmp in Gradle

    main

    To migrate from the Kotlin CocoaPods plugin to spmForKmp, you must update your dependency management files and replace the cocoapods {} configuration block with swiftPackageConfig {} for your iOS targets.

    1. Update libs.versions.toml

    Add the spmForKmp version and plugin definition, and remove the kotlinCocoapods plugin.

    2. Update Root build.gradle.kts

    Replace the kotlinCocoapods plugin alias with spmForKmp in the plugins block.

    3. Update Module build.gradle.kts

    • Replace alias(libs.plugins.kotlinCocoapods) with alias(libs.plugins.spmForKmp).
    • Remove the cocoapods {} block.
    • For each iOS target (e.g., iosArm64, iosSimulatorArm64), use swiftPackageConfig to define dependencies via remotePackageVersion.
    # libs.versions.toml
    [versions]
    ++ spmForKmp = "1.0.4"
    
    [plugins]
    -- kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
    ++ spmForKmp = { id = "io.github.frankois944.spmForKmp", version.ref = "spmForKmp" }
    // Module build.gradle.kts
    kotlin {
        listOf(
            iosArm64(),
            iosSimulatorArm64()
        ).forEach {
            it.swiftPackageConfig(cinteropName = "nativeBridge") {
                minIos = "16.6"
                dependency {
                    remotePackageVersion(
                        url = uri("https://github.com/firebase/firebase-ios-sdk.git"),
                        products = {
                            add("FirebaseCore", "FirebaseAnalytics", exportToKotlin = true)
                        },
                        version = libs.versions.firebaseIOS.get(),
                    )
                }
            }
        }
    }
  10. Use custom Swift versions and toolchains

    main

    The plugin uses the system Swift command by default. If you need to support specific Swift toolchains or older Xcode versions, you can specify a custom binary path using swiftBinPath.

    It is recommended to use swiftly to manage multiple Swift versions on macOS.

    spm {
        // ...
        swiftBinPath = "/path/to/.swiftly/bin/swift"
    }
  11. Disable automatic IDE package resolution

    main

    If your project contains a Package.swift manifest, IntelliJ or Android Studio may attempt to resolve it automatically, which can slow down the IDE.

    To disable this, go to IDE Settings and uncheck: Sync Project after changes in the build script.

    After disabling, you can reclaim disk space by deleting: ~/Library/Caches/JetBrains/IntelliJIdea[Version]/DerivedData.