Publishing Multiplatform Kotlin Libraries
Publishing artifacts for multiplatform Kotlin is a bit more complicated than a JVM project. If you are supporting all of Kotlin’s platforms, then need at least 3 build environments: linux, mac, and windows. Luckily there are easy and free options available to do this with CI/CD. There are at least 2 available for free for open source projects: Azure pipelines & Github Actions. This post covers Github Actions. Throughout this post I will be referencing Redux-kotlin as a working example of publishing a Kotlin multiplatform library.
Kotlin Target OS/CPU Architectures
Kotlin multiplatform projects can target various linux, windows, and MacOS targets. Some of these targets require the target OS in order to compile and build the artifacts. Below is a table of which target each OS is capable of building.
Linux | Windows | Macos | |
androidNative32 | ✅ | ✅ | ✅ |
androidNative64 | ✅ | ✅ | ✅ |
jvm | ✅ | ✅ | ✅ |
js | ✅ | ✅ | ✅ |
iosArm32 | ❌ | ❌ | ✅ |
iosArm64 | ❌ | ❌ | ✅ |
iosX64 | ❌ | ❌ | ✅ |
macos64 | ❌ | ❌ | ✅ |
mingwx64 | ❌ | ✅ | ❌ |
mingwx86 | ❌ | ✅ | ❌ |
wasm32 | ✅ | ❌ | ✅ |
linuxArm32Hfp | ✅ | ✅ | ✅ |
linuxArm64Hfp | ✅ | ✅ | ✅ |
linuxMips32 | ✅ | ❌ | ❌ |
linuxMipsel32 | ✅ | ❌ | ❌ |
linuxX64 | ✅ | ✅ | ✅ |
Github Actions
Github actions is a CI/CD service integrated directly with Github. Its very similar to other CI services. It offers a free tier for open source projects and has Macos, Windows, & Linux machines on the free tier. This makes it a perfect fit for an open source Kotlin multiplatform project.
To use Github Actions, all that is needed is a yml config file located in `.github/workflows
`. Below is a snippet of the config for ReduxKotlin:
name: Publish snapshot on: push: branches: [ master, feature/* ] pull_request: branches: [ master ] jobs: publish-snapshot: runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 - name: Publish Snapshot env: SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }} SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} GPG_SECRET: ${{ secrets.GPG_SECRET }} GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }} SNAPSHOT: 'TRUE' run: ./gradlew publish
For leverage different environments we can leverage a feature called build matrix. By specifying the runs-on
field as a build matrix we can easily run on the 3 supported platforms.
Signing
Signing of release artifacts is a must for Maven central. It allows Maven, and clients of your lib, to verify that you are the entity that uploaded the artifact. This requires a GPG RSA key. When publishing from your local machine this is typically stored in ~/.gpg/secring.gpu
or similar. This is a binary format. For CI purposes it is best practice to store this as an environmental variable with the CI system. To get your secret into a text format, the private key will need to be exported from gpg:
gpg --export-secret-keys -a <key-id> > secret.txt
Github actions have a ‘secret’ context available in config yaml files. Use ${{ secrets.name }}
to access.
To access these environmental vars from your gradle scripts you can utilize `System.getenv(‘SONATYPE_NEXUS_USERNAME’)`. An example of this can be found in the publish.gradle file in the Redux-Kotlin project.
Tips for Github Actions:
- PR not needed to test changes to config YAML files – just make a change to the yml and push up to test.
- Versions can not be updated on maven. you will get a 401 response. Snapshot artifacts can be updated however.
- If one build fails in a workflow, the others are cancelled. This error will be
##[error]The operation was canceled.
Publish Plugins
There are several ways to setup publishing in a maven project, which is probably why it can be confusing! There are official maven publish plugins:
– maven
– maven-publish
– signing
There is also gradle-mvn-publish-plugin.
Also this script by Chris Banes which became popular. It makes using this plugins easier by using gradle properties to set all the values. Basically with the Chris Banes script you put your info into a gradle.properties
file. Then there is question of where to put your configuration files. This encompasses env vars, secrets, username/pass, artifact publishing info.
One common approach is putting it in the gradle/
folder in a publish.gradle
file.
This can then be included in modules that need publishing with:
apply from: '../gradle/publish.gradle'
Metadata
Kotlin multiplatform artifacts leverage gradle metadata to allow the clients to fetch the artifacts needed for the project. The client project only needs to specify your project in a common, aka shared, sourceset. The platform specific sourcesets will automatically look at the metadata and fetch the appropriate artifact. Metadata is stored in JSON format with the file extension .module
.
Releases vs Snapshots
Releases are official artifacts for your project. Once they are uploaded and released, they can not be removed or changed (at least not easily). On the other hand, snapshots can be replaced and deleted. They also do not require any ‘release’ action in the Sonatype dashboard. Once they are published they are live. Snapshots are good for alpha, EAP, or WIP releases. Setting up your CI with snapshot publishing will make it much easier to test new features or updates before making a formal release.
Snapshot for you project will be available at the following url:
https://oss.sonatype.org/content/repositories/snapshots/{packagename}
For example;
https://oss.sonatype.org/content/repositories/snapshots/org/reduxkotlin/
To use a snapshot in your project, the snapshot maven url must be included in your repositories.