Publishing Multiplatform Kotlin Libraries

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.

In this article I will be talking aboud public the Maven Central repo, not a privately hosted Sonatype, although it may useful for both scenarios.

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.

LinuxWindowsMacos
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.

Macos, Windows, & Linux machines are available on the free tier.

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

Using secrets in cli arguments is not secure. Use Env vars instead.

Github actions have a ‘secret’ context available in config yaml files. Use ${{ secrets.name }} to access.

For Github Actions, env vars are found under ‘Secrets’ in the settings tab.

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.

Metadata uses your Gradle module name as the “module” name, which in turn is used as the Maven artifactId. There are ways to override, however I’ve found best for your Gradle module name match your maven artifactId.

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.

Other useful sites:

Software Engineer, Husband, and father of 2 amazing kids. Android, iOS, Kotlin multiplatform!! Maintainer of ReduxKotlin.org