Blog Infos
Author
Published
Topics
, , , ,
Published
JetBrains Kotlin Multiplatform + BoM (Bill Of Materials) + Sonatype Maven Central Repository + GitHub Actions

As a developer working on various Kotlin Multiplatform projects, whether for your job or just for fun, you might find that as your projects grow, you’ll want to create your own libraries. This is a common practice that helps you reuse the same code across different projects, saving time.

While this practice improves your code quality, over time, you might encounter dependency version conflicts, often referred to as “dependency hell”. Here’s an example to illustrate what can happen:

Imagine you are working on two different modules within a project, ModuleA and ModuleB, both depending on the same library, LibraryX. However, ModuleA requires LibraryX version 1.2, while ModuleB needs LibraryX version 1.4 for its additional features.

When you try to build your project, the build system might fetch and use different versions of LibraryX for each module, leading to inconsistent behavior or compilation errors. Worse, if both modules are part of the same build process, the build system might only resolve to one version of LibraryX (say 1.2), causing ModuleB to potentially fail at runtime due to missing features available only in 1.4.

With a BoM (Bill of Materials), you could centrally manage the version of LibraryX, ensuring that all modules use a compatible version, or identify and resolve the version conflict upfront, deciding whether to upgrade ModuleA to use LibraryX 1.4 or find a different solution that suits both modules. This centralized management helps maintain consistencyreduces the risk of runtime issues, and simplifies dependency updates and troubleshooting.

Here are some examples of famous libraries using a BoM:

⚠️ Important: I assume you already have a working setup to automatically publish a KMP library to Maven Central repository. If this is not the case, I recommend you read my article “How to publish your Kotlin Multiplatform library on Maven Central”:

https://vivienmahe.medium.com/how-to-publish-your-kotlin-multiplatform-library-on-maven-central-5340deff7ee5?source=post_page—–b8dd815aa018——————————–

The project structure

A few key elements are required to create a BoM:

  • You need a multi-module project, where each module is a library.
  • An additional module is dedicated to the BoM configuration. This module contains only one file (build.gradle.kts) and does not include any code, resources, or other files. (We will create it later on.)

➡️ By default, every module that sets up a MavenPublication within that multi-module project is automatically included in the BoM.

Here’s what the project structure looks like:

myproject (root)
  ├── build.gradle.kts
  ├── bom (specific module for BoM configuration)
  │     ├── build.gradle.kts
  ├── library1 (module)
  │     ├── build.gradle.kts
  │     ├── src
  ├── library2 (module)
  │     ├── build.gradle.kts
  │     ├── src

In this example, the BoM will include library1 and library2.

For my kmp-bom project, with the default configuration, the BoM will include the following modules: kmp-common , kmp-firebase and kmp-realm.

Project structure of my Tweener/kmp-bom project

If you don’t know how to setup a multi-module project, follow the official Gradle documentation here: https://docs.gradle.org/current/userguide/multi_project_builds.html

The BoM configuration

A BoM is typically a Maven pom.xml file where all the libraries are listed along with their specific version numbers, providing a centralized reference for managing project dependencies:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>bom</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>
  <properties>
    <project1Version>1.0.0</project1Version>
    <project2Version>1.0.0</project2Version>
  </properties>
 
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project1</artifactId>
        <version>${project1Version}</version>
      </dependency>
      <dependency>
        <groupId>com.test</groupId>
        <artifactId>project2</artifactId>
        <version>${project2Version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
 
  <modules>
    <module>parent</module>
  </modules>
</project>

Now, you could create this pom.xml file and update it manually every time one of your libraries has a new version. However, we prefer to automate the process so that this pom.xml file is automatically generated.

➡️ To do so, we use this Gradle plugin that does a pretty good job with minimum configuration:

  • Configure the plugin for the pom.xmlfile generation.
  • Configure publishing to Maven Central and signing with GPG.

In your bom module (the specific module for the BoM configuration), create a build.gradle.kts file and add the following lines:

plugins {
id("io.github.gradlebom.generator-plugin").version("1.0.0.Final")
id("signing")
}
group = "io.github.tweener" // Change here
version = "1.0.0" // Change here
publishing {
publications {
create<MavenPublication>("Bom") {
artifactId = "kmp-bom"
pom {
name.set("My BoM SDK") // Change here
description.set("Bill of Materials (BoM) for my Kotlin Multiplatform libraries") // Change here
url.set("https://github.com/Tweener/kmp-bom") // Change here
licenses {
license {
name.set("The Apache License, Version 2.0") // Change here, if needed
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") // Change here, if needed
}
}
issueManagement {
system.set("GitHub Issues")
url.set("https://github.com/Tweener/kmp-bom/issues") // Change here
}
developers {
developer {
id.set("Tweener") // Change here
name.set("Vivien Mahé") // Change here
email.set("vivien@tweener-labs.com") // Change here
}
}
scm {
connection.set("scm:git:git://github.com:Tweener/kmp-bom.git") // Change here
developerConnection.set("scm:git:ssh://github.com:Tweener/kmp-bom.git")// Change here
url.set("https://github.com/Tweener/kmp-bom") // Change here
}
}
}
}
}
signing {
if (project.hasProperty("signing.gnupg.keyName")) {
println("Signing lib...")
useGpgCmd()
sign(publishing.publications)
}
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Exclude module from the BoM:

If you want to exclude a module from being collected into the BoM, you can add the excludeProject configuration to your bom module’s build.gradle.kts:

bomGenerator {
    excludeProject("excluded-module")
}
Include external dependencies to theBoM:

You can also include external dependencies (from outside this project) to your BoM, by adding the includeDependency configuration to your bom module’s build.gradle.kts:

bomGenerator {
    includeDependency("io.github.tweener:kmp-charts:")
}
name: Publish to Maven Central
permissions:
contents: read
on:
workflow_dispatch:
release:
types: [ released ]
jobs:
build-release:
uses: ./.github/workflows/buildRelease.yml
publish:
needs: build-release
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
OSSRH_STAGING_PROFILE_ID: ${{ secrets.OSSRH_STAGING_PROFILE_ID }}
strategy:
matrix:
include:
- target: publishIosArm64PublicationToSonatypeRepository
os: macos-latest
- target: publishIosSimulatorArm64PublicationToSonatypeRepository
os: macos-latest
- target: publishIosX64PublicationToSonatypeRepository
os: macos-latest
- target: publishAndroidReleasePublicationToSonatypeRepository
os: ubuntu-latest
- target: publishKotlinMultiplatformPublicationToSonatypeRepository
os: ubuntu-latest
- target: publishBomPublicationToSonatypeRepository
os: ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Setup JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: "zulu"
- name: Setup Gradle cache
uses: actions/cache@v3
with:
path: |
~/.konan
key: ${{ runner.os }}-${{ hashFiles('**/.lock') }}
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
passphrase: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
- name: Gradle publish
uses: gradle/gradle-build-action@v3
with:
arguments: |
${{ matrix.target }}
closeAndReleaseSonatypeStagingRepository
-Psigning.gnupg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
-Psigning.gnupg.keyName=${{ secrets.OSSRH_GPG_SECRET_KEY_ID }}
-PsonatypeUsername=${{ secrets.OSSRH_USERNAME }}
-PsonatypePassword=${{ secrets.OSSRH_PASSWORD }}
-PsonatypeStagingProfileId=${{ secrets.OSSRH_STAGING_PROFILE_ID }}
view raw publish.yml hosted with ❤ by GitHub
Check your BoM library on Maven Central

With this, your library is now fully set up for automated publishing on Maven Central. After creating a release version of your library, it will be accessible via a URL provided by Sonatype.

For example, in my case, my BoM has 3 libraries: kmp-commonkmp-firebasekmp-realm. The URL is:

https://repo1.maven.org/maven2/io/github/tweener/

For a full example and source code, check out my kmp-bom repository:

https://github.com/Tweener/kmp-bom?source=post_page—–b8dd815aa018——————————–

That’s all for this article! I hope you found it helpful, I would greatly appreciate your feedback! 🙏

Feel free to leave a comment below or reach out to me directly:

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
With JCenter sunsetted, distributing public Kotlin Multiplatform libraries now often relies on Maven Central…
READ MORE
blog
With the rise of Kotlin Multiplatform (KMP), it gave birth to a cross-platform UI…
READ MORE
blog
With Compose Multiplatform 1.6, Jetbrains finally provides an official solution to declare string resources…
READ MORE
blog
There are already so many app-level architecture and presentation layer patterns (MVC/MVP/MVVM/MVI/MVwhatever) that exist…
READ MORE
Menu