Building Kotlin/Multiplatform with Github Actions

Romain Boisselle
Kodein Koders
Published in
6 min readApr 15, 2021

In a previous tutorial, I showed you how to configure your Kotlin/Multiplatform libraries to be published on Maven Central. To go beyond a publication process we now need to automate things with a good CI/CD workflow. Let’s see how to do this with Github Actions.

As the Kodein Open Source Initiative libraries are hosted on Github, we felt that it was the best choice to use the Github Actions pipelines to test, build and deploy our libraries, as it is entirely integrated with every other part of Github.

Let’s start with the snapshot publication process, as it is fairly straightforward. Publishing snapshots on Maven Central has no more constraints than just being authenticated with your Sonatype account, and having your artifacts file names ending with the uppercased word SNAPSHOT. This means that you could literally upload anything in your staging repositories.

Here is a Github Actions script to upload a snapshot of a Kotlin/Multiplatform project:

In that example, we run the task publishAllPublicationToOss on Linux, Mac & Windows, literally saying to publish every available targets onto the oss repository, which is the name we gave to it in our maven-publish configuration in our Gradle build script. This is not optimal, as every platform could upload shared targets like JVM, JS or even the Kotlin Multiplatform Metadata, but it’s simple, and it works!

Let’s now see how to deploy stable releases.

We have to discuss how we will be managing the unique staging repository we need. In fact, this is quite simple, and it is resume by this image:

What we need is to create a staging repository, by using the sonatype API over HTTP. This step will generate a repository ID, that we use in later steps. In the build-upload matrix, we upload every artifact onto the same staging repository. Finally, In the drop-or-release job, depending on the success or failure of the build-upload matrix, we drop or release the staging repository.

Whatever happens in the build-matrix, we will always create a staging repository, and at the end drop or release that same repository, like in this failure scenario:

So, how do we manage consuming the sonatype HTTP API?

You could write Gradle tasks to create, drop or release the staging repositories. You could also use your favorite language / framework to automate everything. But, let me share our experience and choices.

We did consider using Gradle tasks, but Gradle would need to perform the full configuration of your project before being able to make only one or so HTTP requests. Sounds like we could do something more efficient. Our second thought was to use bash scripts, using curl to send HTTP requests, and JQ to parse JSON responses. Here is our very simple script that creates a staging repository with bash:

This script needs 3 informations to create and retrieve a staging repository ID. Your credentials, username and password from your sonatype account, and the profile ID you want to use to publish your library. Note that you can have multiple profiles if you are allowed to send artifacts over different group ID.

If you don’t know your profile ID, you can get it through the sonatype API, here: https://oss.sonatype.org/service/local/staging/profiles

The last command of our script will send our repository ID to the outputs of a Github Action job, to be consumed by future steps in our workflow. We have the same kind of script to drop or release the staging repository, in case of success or failure of our publication steps.

As nice and handy as these scripts are, using them in every project we have means downloading the proper .sh file, giving the system the right to execute it, and then run it with the right parameters, ending with numerous Github Actions jobs like this on every project:

This sounds cumbersome and hard to maintain at a larger scale, though.

Github provides a way to create automated tasks, called Actions. Those Actions can be written with JavaScript, or you can provide a Docker container configuration to be executed when you need. Therefore, we decided to create our own Action, with the help of our very good friend Martin Bonnin (Github). Our goal was to avoid the complexity of managing manual scripts in every project, and of course to share it with the community, giving the ability to anyone to handle their publication over Maven Central in an easy way.

Those Actions are available under the Github organisation nexus-actions, so you can browse the code, copy them, and use them at your own convenience. Here is how to implement your release workflow with them:

This job will be triggered only when a release is created on your Github repository. The first job will be to create the unique staging repository to upload all of our artifacts on, applying the action nexus-actions/create-nexus-staging-repo, with the proper parameters. We can now retrieve the output of the action, attach it to the outputs of our create-staging-repository job, and then use it in a later one.

Now, all we have to do is to publish our Kotlin/Multiplatform project with a simple job, that will grab the needed information from the Github secrets, and from the output of the job previously executed:

As you can see, this job will only be executed after the first one ends, as it needs it to finish. This job will run multiple times, once for every platform defined in the matrix. It is quite easy to consume outputs from an earlier job, we just need to invoke this job from our needed ones, and provide the correct name of the variable we want to use.

This example, is really simple, if you are looking for more advanced use cases, you can check our Kodein Open Source Initiative Github Actions, where we manage things like caching Gradle or Konan across builds, installing specific JDK or Android NDK, or even connected checks on the Android Emulator.

Now, whatever is the result of this job, whether it fails or passes, we need to finalise our publication process, by either discard the already uploaded artifacts, or release the new version of our library.

Therefore, we need one last job that will always be executed:

Depending on the result of the build-upload job, only one step will be executed, drop or release. Both of them are based on a different action: either drop-nexus-staging-repo or release-nexus-staging-repo, waiting for the same parameters : your credentials and the previously created repository ID.

The drop task will just ask to sonatype to delete the uploaded artifacts on that specific staging repository while the release task will close the staging repository and wait for the sonatype checks, checking all the requirements we had to comply with, and finally publish the brand new version to Maven Central, making it available for anyone to download.

This article is brought to you by Kodein Koders, a European service & training provider focused on Kotlin & Kotlin/Multiplatform. Do not hesitate to visit our blog, or our Youtube channel for more contents.

--

--