Gradle plugin that generates a Swift Package Manager manifest and an XCFramework to distribute a Kotlin Multiplatform library for Apple platforms.

Overview

Multiplatform Swift Package

Build Status

This is a Gradle plugin for Kotlin Multiplatform projects that generates an XCFramework for your native Apple targets and creates a matching Package.swift file to distribute it as a binary target.

To distribute the framework a ZIP file containing it will be created and referenced from the Package. You can upload the ZIP file to your package registry so that SPM can download it.

Prerequisites

  • XCode version 12.0+
  • Gradle version 6.0+

Installing

The plugin is published on Maven central. Add it to the plugins block in the Gradle build file.

plugins {
  id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.3"
}

Execution

The plugin adds two Gradle tasks to your project.

  • ./gradlew createSwiftPackage

    Creates the XCFramework, ZIP file and Package.swift

  • ./gradlew createXCFramework

    Creates only the XCFramework

Configuration

Generation of the Package and XCFramework can be configured using the plugin's DSL. Add the configuration to the Gradle build file.

This is a complete example of the required configuration. All available options will be explained in the following sections.

multiplatformSwiftPackage {
    swiftToolsVersion("5.3")
    targetPlatforms {
      iOS { v("13") }
    }
}

Package Name

By default, the name of the Swift package will be the base name of the first framework found in the project. However, you can declare a different name for the package. This might be useful if your frameworks have different base names, and you want your package to have a common name.

packageName("MyAwesomeKit")

Hint: If the cocoapods plugin is applied the name of the package will default to the value assigned to the frameworkName property. Otherwise, the value of the baseName property of the framework configuration will be used.

Output Directory

By default, the plugin will write all files into the swiftpackage folder in the project directory. You can configure the output folder by providing a File object pointing to it.

outputDirectory(File(projectDir, "swiftpackage"))

Swift Tools Version

The first line of every Package.swift file is a header declaring the Swift tools version required by the package. To set the version use the following configuration and provide the version your XCode project supports.

swiftToolsVersion("5.3")

Distribution Mode

Swift packages can distribute binary targets either via the local file system or via a remote ZIP file. Depending on your requirements (e.g. local development or CI) use one of the following configurations.

Local distribution

distributionMode { local() }

Remote distribution

Provide a URL where the ZIP file containing the XCFramework is located. This should point to the root directory and not to the ZIP file itself.

// correct
distributionMode { remote("https://example.com") }

// wrong
distributionMode { remote("https://example.com/MyLib.zip") }

ZIP File Name

By default, the name of the generated ZIP file consists of the package name concatenated with the project's version. You can configure the name by setting a custom value. The .zip file extension will be added during the build and should be omitted here.

zipFileName("MyAwesomeKit")

Build Configuration

Apple frameworks can be built with different configurations. By default, these are release and debug. However, you can also create your own configurations.

Default

buildConfiguration { release() }
// or
buildConfiguration { debug() }

Custom

buildConfiguration { named("staging") }

Target Platforms

The main feature of an XCFramework is packaging multiple architectures for the same platform. This is great since it allows distributing e.g. iOS builds for both the physical device, and the simulator in one package.

Swift packages require declaring the minimum supported version for each platform. Therefore, you need to configure both the architectures for each platform and the version.

You can either declare all target architectures specifically or add all architectures of a platform at once.

targetPlatforms {
  // all iOS targets (== device and simulator) with minimum version 13
  iOS { v("13") }

  // macOS with minimum version 10.0
  targets("macosX64") { v("10.0") }
}

Note: If you are using Groovy for the build script the target names must be passed as a list.

targetPlatforms {
  // the catch-all DSL works the same in Groovy and Kotlin
  iOS { v("13") }

  // however, Groovy requires a list when the targets() DSL is used
  targets(['macosX64']) { v('10.0') }
}

Available platform shortcuts are:

  • iOS { v("xxx") }
  • tvOS { v("xxx") }
  • macOS { v("xxx") }
  • watchOS { v("xxx") }

Further Reading

To learn more about the Swift Package Manager I recommend reading the following resources.

License

Copyright 2020 Georg Dresler

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Error while uploading to AppStore

    Error while uploading to AppStore

    Hey

    I have a setup where, for a new release, I run ./gradlew createXCFramework and commit the .xcframework on my release branch. This works pretty well, however, when building an IPA and trying to submit to the store, I get the following error:

    [Transporter Error Output]: ERROR ITMS-90171: "Invalid Bundle Structure - The binary file 'MyApp.app/Frameworks/mysdk.framework/mysdk' is not permitted. Your app can't contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. Refer to the Bundle Programming Guide at https://developer.apple.com/go/?id=bundle-structure for information on the iOS app bundle structure."
    

    build.gradle:

    multiplatformSwiftPackage {
        swiftToolsVersion("5.3")
        targetPlatforms {
            iOS { v("12") }
        }
        outputDirectory(File(".."))
    }
    

    Any idea what could cause this?

    opened by ThomasDhaen 4
  • Does not work with KMP 1.4.20

    Does not work with KMP 1.4.20

    Hi,

    thanks for this plugin, it looks exactly what I am looking for.

    Unfortunately, I can't get the tasks run successfully with a fresh KMP project version 1.4.20.

    My settings:

    multiplatformSwiftPackage {
        swiftToolsVersion("5.3")
        targetPlatforms {
            iOS { v("13") }
        }
    }
    

    I get 2 errros:

    1. Package name is invalid: null
    * What went wrong:
    A problem occurred configuring root project 'centralized-key-value-library'.
    > Could not create task ':createXCFramework'.
       > * Package name is invalid: null
           Either declare the base name of your frameworks or use a non-empty package name.
    
    

    I can fix this by setting it manually:

    multiplatformSwiftPackage {
        swiftToolsVersion("5.3")
        targetPlatforms {
            iOS { v("13") }
        }
        packageName(project.name)
    }
    
    1. at least one framework or library must be specified.
    > Task :createXCFramework FAILED
    error: at least one framework or library must be specified
    

    I could not fix this one on my own. Maybe some internal structure has been changed?

    bug 
    opened by sebarthel89 4
  • How to use this plugin to generate a pod for iOS project to use

    How to use this plugin to generate a pod for iOS project to use

    Hi, body, as title said, I want to generate a cocoapods for iOS project as our project use cocoapods as module manager, I'v read the README.md, but can't find how to use it to generate a pod, Any step for me to achieve that?

    FYI:Comes from here: https://kotlinlang.slack.com/archives/C3SGXARS6/p1603028341199400?thread_ts=1602236811.143200&cid=C3SGXARS6

    opened by wuseal 3
  • Fat multiplatform framework

    Fat multiplatform framework

    Hey,

    I have another issue bundling a fat framework.

    I added a task called universalFrameworkRelease that bunldes simulator and native ios builds. Is it possible to support as well?

    Not sure, but maybe it's optional to add a bunch of additional paths that are used for more sift packages.

    additionalFrameworkPaths {
       path = "build/bin/universal/release"
       name = "universal"
    }
    

    Here is an example: https://github.com/sebarthel89/example-kmp-swift-package

    opened by sebarthel89 3
  • Allow conditionally applying this plugin

    Allow conditionally applying this plugin

    I'd like to do something like this

    import com.chromaticnoise.multiplatformswiftpackage.MultiplatformSwiftPackagePlugin
    
    val isCI = System.getenv("CI") == "true"
    
    plugins {
      kotlin("multiplatform") version "1.4.20"
      id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.0" apply false
    }
    
    kotlin {
      jvm()
      ios()
    }
    
    if (isCI) {
      apply(plugin = "com.chromaticnoise.multiplatform-swiftpackage")
      configure<MultiplatformSwiftPackagePlugin> {
        multiplatformSwiftPackage {
          packageName("MyFramework")
          swiftToolsVersion("5.3")
          targetPlatforms {
            iOS { v("13.0") }
          }
        }
      }
    }
    

    But , since MultiplatformSwiftPackagePlugin is marked a internal, I'm unable to do this. Because of this anyone working on the jvm part of our library has to chose between removing this plugin, or setting up xcode.

    If it's okay, I can send a pull-request to make the plugin class public.

    opened by netroy 3
  • Enable Customization of Package Name

    Enable Customization of Package Name

    First of all, great work on this plugin! I expect it will grow in popularity as SPM and XCFrameworks become more ubiquitous in the coming months.

    This PR proposes to add an interface to allow the customization of the frameworkName. The result offers similar functionality to the KMP cocoapods plugin (which also allows configuration of this property).

    Because Apple-platform framework names tend not to follow the same naming conventions as their Kotlin/Java counterparts, this will help folks (such as myself) avoid:

    import my_kotlin_library

    ...and instead do:

    import MyKotlinLibrary

    Let me know what you think, and thanks again for making this library!

    opened by JUSTINMKAUFMAN 3
  • import module name

    import module name

    I'm using following config (in https://github.com/joreilly/PeopleInSpace).... PeopleInSpace is being used for name of package but I still need to use import common in Swift code....I'm probably missing something obvious but is there way to change that name (or does gradle module name also need to change?)

    multiplatformSwiftPackage {
        packageName("PeopleInSpace")
        swiftToolsVersion("5.3")
        targetPlatforms {
            iOS { v("13") }
        }
    }
    
    opened by joreilly 2
  • Couldn't import my package with it's name

    Couldn't import my package with it's name

    I used createSwiftPackage command to create swift package and it is successfully created. (Thank you for this great library)

    multiplatformSwiftPackage { packageName("VLShared") swiftToolsVersion("5.3") targetPlatforms { iOS { v("13") } } outputDirectory(File(rootDir, "/")) }

    But in my main swift project I need to import as "import common" to use package, I want to able to import package with my package name.

    How can I solve this problem?

    opened by sahbazali 1
  • Enabling Bitcode for archiving

    Enabling Bitcode for archiving

    Hello, I'm seeing the following error on Xcode Archive:

    ld: bitcode bundle could not be generated because '/xxxx/BuildProductsPath/Release-iphoneos/common.framework/common' was built without full bitcode. All frameworks and dylibs for bitcode must be generated from Xcode Archive or Install build file '/xxxx/BuildProductsPath/Release-iphoneos/common.framework/common' for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)

    AFAIK, SPM requires all frameworks to have bitcode enabled. Is there a way we could do so ? I couldn't find any info in the documentation or implementation (cf. https://github.com/ge-org/multiplatform-swiftpackage/blob/master/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateXCFrameworkTask.kt). I'm not sure if I'm missing something, I think we would need to add some bitcode related flags in the xcodebuild command.

    opened by nicoonguitar 1
  • Issue with Checksum

    Issue with Checksum

    My question is not directly related to this great plugin, but I use it build a framework, the zip and the package.swift. I put that on a gitlab repo, with the download url pointing to a permalink of the zip file.

    When I tried to setup my ios project with this dependency, I get an issue of checksum ! When I download the zip file, the checksum is the good one.

    Any idea ?

    opened by afaucogney 1
  • remote distributionMode

    remote distributionMode

    This is probably more a question rather than issue but wasn't clear from docs how remote distributionMode should work. If I have package hosted for example on Github should this allow uploading generated artifacts to there? btw wrote short post on experience using plugin so far (in case anything you think isn't quite right there or should be clarified ) https://johnoreilly.dev/posts/kotlinmultiplatform-swift-package/

    opened by joreilly 1
  • Build failing: symbol(s) not found for architecture arm64

    Build failing: symbol(s) not found for architecture arm64

    I'm trying to create a SPM from a module which contains a simple object file.

    object Strings {
        val TitleTalks = "Talks"
        val TabTalks = "Talks"
        val TabChannels = "Channel"
        val TabProfile = "Profile"
    }
    

    complete build.gradle looks like this

    plugins {
        kotlin("multiplatform")
        id("com.android.library")
        id("com.chromaticnoise.multiplatform-swiftpackage") version "2.0.3"
    }
    
    kotlin {
        multiplatformSwiftPackage {
            swiftToolsVersion("5.3")
            targetPlatforms {
                iOS { v("13") }
            }
            packageName("AppStrings")
            outputDirectory(File(projectDir, "spm/strings"))
        }
    
        android()
    
        ios {
            binaries {
                framework {
                    baseName = "strings"
                    isStatic = false
                }
            }
        }
    
        iosSimulatorArm64 {
            binaries {
                framework {
                    baseName = "strings"
                    isStatic = false
                }
            }
        }
    
        sourceSets {
            val commonMain by getting {
                dependencies {
                    implementation(libs.moko.resources.core)
                }
            }
            val commonTest by getting {
                dependencies {
                    implementation(kotlin("test"))
                }
            }
            val androidMain by getting {
                dependencies {
                    implementation(libs.moko.resources.compose)
                }
            }
            val androidTest by getting
    
            val iosMain by getting
            val iosTest by getting
            val iosSimulatorArm64Main by getting {
                dependsOn(iosMain)
            }
            val iosSimulatorArm64Test by getting {
                dependsOn(iosTest)
            }
        }
    }
    
    android {..}
    

    But I always get the following build error whenever I try accessing Strings.xx inside my Swift file

    Undefined symbols for architecture arm64:
      "_OBJC_CLASS_$_StringsStrings", referenced from:
          objc-class-ref in ContentView.o
    ld: symbol(s) not found for architecture arm64
    
    スクリーンショット 2022-09-07 8 58 21
    opened by surajsau 3
  • Missing path from XCFramework as defined by 'DebugSymbolsPath' in its `Info.plist` file

    Missing path from XCFramework as defined by 'DebugSymbolsPath' in its `Info.plist` file

    Hello,

    Nice plugin but when adding the Repo link, building the project fails with this error Missing path (/.../Library/Developer/Xcode/DerivedData/Test-eqqqbrehuzabdnegojwzdecqruxe/SourcePackages/checkouts/MyFrameworkName/MyFrameworkName.xcframework/ios-x86_64-simulator/dSYMs) Missing path from XCFramework 'MyFrameworkName.xcframework' as defined by 'DebugSymbolsPath' in itsInfo.plistfile What I'm doing wrong ?

    opened by ahmadmssm 1
  • Middle nativeMain module with specific iosMain and macOSMain fails.

    Middle nativeMain module with specific iosMain and macOSMain fails.

    Not sure if this is expected, do have a project with a middle nativeMain source set for specific iosMain & macOSMain source set

    - commonMain
        - nativeMain
            - iosMain
            - macOSMain
    

    build.gradle.kts

        macosX64("native")
        macosX64("macOS") {
            binaries {
                framework {
                    baseName = "shared"
                }
            }
        }
        ios() {
            binaries {
                framework {
                    baseName = "shared"
                }
            }
        }
    
        ...
    
        val nativeMain by getting {
            dependencies {
                implementation(libs.sqlDelight.native)
                implementation(libs.ktor.client.core)
                implementation(libs.coroutines.core)
                implementation(libs.multiplatformSettings.common)
            }
        }
        val iosMain by getting {
            dependsOn(nativeMain)
            dependencies {
                implementation(libs.ktor.client.ios)
                val coroutineCore = libs.coroutines.core.get()
                implementation("${coroutineCore.module.group}:${coroutineCore.module.name}:${coroutineCore.versionConstraint.displayName}") {
                    version {
                        strictly(libs.versions.coroutines.native.get())
                    }
                }
            }
        }
        val macOSMain by getting {
            dependsOn(nativeMain)
            dependencies {
                implementation("io.ktor:ktor-client-curl:2.0.0-beta-1")
            }
        }
    
    ...
    
    multiplatformSwiftPackage {
        packageName("sharedValkyrie")
        swiftToolsVersion("5.3")
        targetPlatforms {
            iOS { v("13") }
            macOS { v("11") }
        }
    }
    

    Inside commonMain I have multiple expect class ... that are defined only in iosMain and macOSMain and not in nativeMain, such as UserAgent strings, platform name, etc. After gradle sync, those expect classes are solved properly without any errors but when running createSwiftPackage gradle task, I receive that there are no expect declarations in nativeMain module: Expected class 'Platform' has no actual declaration in module <mySharedModule> for Native

    Shouldn't just execute compileKotlinIos and compileKotlinMacOS and skip compileKotlinNative? Is there a workaround to do this?

    opened by checoalejandro 0
  • Adopting XCFramework of new kotlin version

    Adopting XCFramework of new kotlin version

    Is it possible to adopt the assembleXCFramework gradle task instead of the current createXCFramework task in the project? Is it possible to migrate to that, which is available since kotlin 1.6.0?

    I've made a fork which already contains a possible setup.

    The only thing that is changed is that the plugin uses the assembleXCFramework task and then copies the generated files to the outputDirectory.

    Currently I cannot make a pull request due to the lack of M1 support in the latest version. There is already an open pull request for this by @jizoquval.

    Let me know what I can do to help out.

    opened by wesleydonk 0
  • M1 support

    M1 support

    Added

    • new arm simulator targets: macosArm64, iosSimulatorArm64, watchosSimulatorArm64, tvosSimulatorArm64

    Changed

    • kotlin version to 1.6.20
    • gradle 7.4.2

    Fixed

    • desktop (arm64, x64, and x86) binaries need to be packed in a fat framworks building XCFramworks (Thanks to @luca992)
    opened by jizoquval 29
  • Plugin seems to ignore the target version

    Plugin seems to ignore the target version

    Using this plugin in our project with the following setup:

        multiplatformSwiftPackage {
            swiftToolsVersion("5.4")
            packageName("Common")
            zipFileName("Common")
            outputDirectory(File(rootDir, "/app-ios/CommonFramework"))
            distributionMode { local() }
            targetPlatforms {
                iOS { v("13") }
            }
        }
    

    and when invoking the createSwiftPackage gradle task the framework is created successfully and the ios app can be built without problems. BUT as soon as we try to upload the app an error arise:

    ERROR ITMS-90208: "Invalid Bundle. The bundle <AppName> Alpha.app/Frameworks/Common.framework does not support the minimum OS Version specified in the Info.plist
    

    And inspecting the created Info.plist the /app-ios/CommonFramework/Common.xcframework/ios-arm64/Common.framework/Info.plist file contains the following:

    <key>MinimumOSVersion</key>
    <string>9.0</string>
    

    Which is a problem for us because the KMP module uses some native dependencies which compiled against min 13.0 version that's why we are setting the target version with the plugin.


    As a workaround right now we are manually modifying the affected Info.plist file to match the version but it would be better if the plugin would respect the setting and automatically do that for us.

    opened by stumi01 1
Owner
Georg Dresler
Software Engineer iOS & Android. Loves analog photography.
Georg Dresler
Gradle Plugin to enable auto-completion and symbol resolution for all Kotlin/Native platforms.

CompleteKotlin Gradle Plugin to enable auto-completion and symbol resolution for all Kotlin/Native platforms. What this plugin provides This zero-conf

Louis CAD 235 Jan 3, 2023
Gradle plugin to ease Kotlin IR plugin development and usage in multimodule gradle projects

Gradle plugin to ease Kotlin IR plugin development and usage in multimodule gradle projects. Former: kotlin-ir-plugin-adapter-gradle

null 2 Mar 8, 2022
Kmp4free - A Gradle Plugin that allows seamless switching between Kotlin JVM and the Kotlin Multiplatform Plugins

?? kmp4free Allows you to toggle between Kotlin JVM Plugin -> Kotlin Multiplatform with a Gradle Property kmp4free=true. This Gradle Plugin was built

Sam Edwards 61 Oct 14, 2022
Gradle Plugin to automatically upgrade your gradle project dependencies and send a GitHub pull request with the changes

Gradle Plugin to automatically upgrade your gradle project dependencies and send a GitHub pull request with the changes

Dipien 142 Dec 29, 2022
A Gradle plugin to ease testing in Kotlin Multiplatform projects.

Multiplatform Testing Plugin A Gradle plugin for easy testing of Kotlin Multiplatform projects in your CI pipeline. Support for testing android() targ

DeepMedia 6 Oct 19, 2022
Gradle Plugin that determines if modules are Kotlin Multiplatform (KMP) ready.

Gradle Plugin that determines if modules are Kotlin Multiplatform (KMP) ready. KMP Ready means that the code is Kotlin Multiplatform compatible.

Sam Edwards 58 Dec 22, 2022
KMP Ready is a Gradle Plugin that provides actionable advice to make your code Kotlin Multiplatform compatible.

KMP Ready IS ?? UNDER DEVELOPMENT ?? Decisioning Logic Positive Signals ✅ Only Kotlin .kt Source Files Using Kotlin JVM Plugin Uses the Kotlin Multipl

Sam Edwards 58 Dec 22, 2022
Gradm (Gradle dependencies manager) is a new way to manage dependencies easier and more efficient.

Gradm (Gradle dependencies manager) is a new way to manage dependencies easier and more efficient.

null 16 Jan 9, 2023
Gradle Plugin that allows you to decompile bytecode compiled with Jetpack Compose Compiler Plugin into Java and check it

decomposer Gradle Plugin that allows you to decompile bytecode compiled with Jetpack Compose Compiler Plugin into Java and check it How to use Run bui

Takahiro Menju 56 Nov 18, 2022
Klinker is a gradle plugin making it possible to link kotlin native executables with custom linkers and options.

Klinker is a gradle plugin making it possible to link kotlin native executables with custom linkers and options. It does this by creating a static library for kotlin compilation, then generates a c+kotlin wrapper that calls into kotlin to start the app, finally using the specified compiler to compile and link the c code and kotlin library into a binary.

Jason Monk 4 Apr 14, 2022
EasyVersion is a Gradle plugin that manage your app or library version.

EasyVersion EasyVersion is a Gradle plugin that manage your app or library version. Before Downloading Create easy_version.json in your root project d

Kosh Sergani 8 Nov 26, 2022
Grazel is a Gradle plugin to automate generation of valid Bazel files for a given Android/Kotlin/Java project.

Grazel Grazel stands for Gradle to Bazel. It is a Gradle plugin that enables you to migrate Android projects to Bazel build system in an incremental a

Grab 228 Jan 2, 2023
A Gradle plugin to help analyse the dependency between modules and run tasks only on modules impacted by specific set of changes.

Change Tracker Plugin A Gradle plugin to help analyse the dependency between modules and run tasks only on modules impacted by specific set of changes

Ismael Di Vita 110 Dec 19, 2022
Android Gradle Plugin -- Auto Check big image and compress image in building.

McImage I will continue to update, please rest assured to use 中文文档 Android优雅的打包时自动化获取全部res资源 McImage is a Non-invasive plugin for compress all res in

smallSohoSolo 1.1k Dec 28, 2022
Gradle Plugin for publishing artifacts to Sonatype and Nexus

Introduction Due to Sonatype's strict validation rules, the publishing requirement must be satisfied by every artifact which wants to be published to

Johnson Lee 21 Oct 14, 2022
Gradle plugin that parses version updates and assigns them to groups of people.

Notifier Gradle Plugin This gradle plugin serves the need of automating how dependencies are handles in a project. More specifically, it functions usi

Plum 4 Oct 27, 2022
A Gradle plugin enforcing pre-commit and commit-msg Git hooks configuration

A Gradle plugin enforcing pre-commit and commit-msg Git hooks configuration. Conventional-commits-ready.

Danilo Pianini 24 Dec 16, 2022
Gradle plugin to check, if rest-controllers are used by clients and clients have suitable rest-interfaces

Verify-Feign Gradle Plugin This plugin will help you to verify all rest-clients and -controllers in a multimodule spring project. It will check, if al

null 3 May 11, 2022
The core Gradle plugin and associated logic used for Slack's Android app

slack-gradle-plugin This repository contains the core Gradle plugin and associated logic used for Slack's Android app. This repo is effectively read-o

Slack 306 Dec 30, 2022