Now when we've built our first Kotlin Multiplatform library and learned about multiplatform library publishing format nothing can stop us from going public and releasing our library to MavenCentral!
Registering a Sonatype account
If this is your first library, or you’ve only ever used Bintray to do this before, you will need to first register a Sonatype account.
There are many articles describing the registration process on the internet. The one from GetStream is exhaustive and up to date. You can start by following the first four steps, including "Generating a GPG key pair":
- Register a Jira account with Sonatype (you can use my issue as an example). ✅
- Verify your ownership of the group ID you want to use to publish your artifact by creating a GitHub repo. ✅
- Generate a GPG key pair for signing your artifacts. ✅
- Publish your public key. ✅
- Export your private key. ✅
When the Maven repository and signing keys for your library are prepared, we are ready to move forward and set up our build to upload the library artifacts to a staging repository and then release them!
Setting up publication
Now we need to tell Gradle how to publish our libraries. Most of the work is already done by maven-publish
and Kotlin Gradle Plugins – all the required publications are created automatically. We've already seen the result when we published our library to the local Maven repository. But for publishing it to Maven Central we need to take some additional steps:
- Configure public Maven repository URL and credentials.
- Provide a description and
javadocs
for all library components. - Sign publications.
Let's handle all of these tasks by writing some Gradle scripts! I suggest extracting all the publication-related logic from our library module build.script, so you can easily reuse it for other modules in the future.
The most idiomatic and flexible way to do that is to use Gradle’s precompiled script plugins. Our build logic will be provided as a precompiled script plugin and could be applied by plugin ID to every module of our library.
To implement this, we need to put our publication logic into a separate Gradle project:
Add a new gradle project
convention-plugins
inside your library root project by creating a new folder named
convention-plugins
withbuild.gradle.kts
in it.
-
Put the following in the
build.gradle.kts
:
plugins { `kotlin-dsl` // Is needed to turn our build logic written in Kotlin into Gralde Plugin } repositories { gradlePluginPortal() // To use 'maven-publish' and 'signing' plugins in our own plugin }
Create a
convention.publication.gradle.kts
file in theconvention-plugins/src/main/kotlin
directory. This is where all the publication logic will be stored.-
Put all the required logic there. Applying just
maven-publish
is enough for publishing to the local Maven repository, but not to Maven Central. In the provided script we get the credentials fromlocal.properties
or environment variables, do all the required configuration in the ‘publishing’ section, and sign our publications with the signing plugin:
import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.`maven-publish` import org.gradle.kotlin.dsl.signing import java.util.* plugins { `maven-publish` signing } // Stub secrets to let the project sync and build without the publication values set up ext["signing.keyId"] = null ext["signing.password"] = null ext["signing.secretKeyRingFile"] = null ext["ossrhUsername"] = null ext["ossrhPassword"] = null // Grabbing secrets from local.properties file or from environment variables, which could be used on CI val secretPropsFile = project.rootProject.file("local.properties") if (secretPropsFile.exists()) { secretPropsFile.reader().use { Properties().apply { load(it) } }.onEach { (name, value) -> ext[name.toString()] = value } } else { ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID") ext["signing.password"] = System.getenv("SIGNING_PASSWORD") ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE") ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD") } val javadocJar by tasks.registering(Jar::class) { archiveClassifier.set("javadoc") } fun getExtraString(name: String) = ext[name]?.toString() publishing { // Configure maven central repository repositories { maven { name = "sonatype" setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") credentials { username = getExtraString("ossrhUsername") password = getExtraString("ossrhPassword") } } } // Configure all publications publications.withType<MavenPublication> { // Stub javadoc.jar artifact artifact(javadocJar.get()) // Provide artifacts information requited by Maven Central pom { name.set("MPP Sample library") description.set("Sample Kotlin Multiplatform library (jvm + ios + js) test") url.set("https://github.com/KaterinaPetrova/mpp-sample-lib") licenses { license { name.set("MIT") url.set("https://opensource.org/licenses/MIT") } } developers { developer { id.set("KaterinaPetrova") name.set("Ekaterina Petrova") email.set("ekaterina.petrova@jetbrains.com") } } scm { url.set("https://github.com/KaterinaPetrova/mpp-sample-lib") } } } } // Signing artifacts. Signing.* extra properties values will be used signing { sign(publishing.publications) }
-
Go back to your library project. Ask Gradle to prebuild your plugins by adding the following in the root
settings.gradle
:
includeBuild("convention-plugins")
-
Now we can apply this logic in our library
build.script
. In theplugins
section, replace usingmaven-publish
with using ourconventional.publication
.
plugins { kotlin("multiplatform") version "1.4.30" id("convention.publication") }
-
Don’t forget to create a
local.properties
file with all the necessary credentials and make sure you have added it to .gitignore 🗝:
signing.keyId=... signing.password=... signing.secretKeyRingFile=... ossrhUsername=... ossrhPassword=...
Now take a deep breath, make ./gradlew clean
and sync the project.
New Gradle tasks related to the Sonatype repository should appear in the publishing group – that means that everything is ready for you to publish your library!
Publishing your first library to MavenCentral
In the beginning of the article I promised you that you will be able to publish your library in just a one click – and now is the moment of truth! To upload your library to Sonatype Repository just run:
./gradlew publishAllPublicationsToSonatypeRepository
The so-called staging repository will be created, and all the artifacts for all publications will be uploaded to that repository. All that is now needed is to check that all the artifacts you wanted to upload have made it there and to press the “release” button!
These final steps are well-described in the now-familiar article. In short, you need to:
- Go to https://s01.oss.sonatype.org/ and log in using the credentials you used in Sonatype Jira.
- Find your repository in the ‘Staging repositories’ section.
- Close it.
- 🚀 Release it!
Go back to the Jira Issue you created and let them know you released your first component to activate the sync to Maven Central. This step is needed only if it’s your first release.
Soon your library will become available at https://repo1.maven.org/maven2/ and other developers will be able to add it as a dependency. And in a couple of hours, it will become discoverable in Maven Central Repository Search!
Conclusion
Congratulations! 🎉 You’ve just created your first Kotlin Multiplatform Library and published it on Maven Central!
This is how you can now depend on the published version:
kotlin {
android()
ios ()
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.katerinapetrova:mpp-sample-lib:1.0.0")
}
}
}
Pretty simple, right? If you check the external dependencies section, you will see that it is quite clever as your project only downloads the artifacts of the platforms you actually target, not all of them every time. Thanks to the Gradle module metadata feature, you only have to specify dependencies on the library once in the shared source set, even if it is used in platform source sets.
A lot of work has been done, but of course, it’s just the beginning of the journey. The most interesting part is ahead: to support and improve your library continuously, so you can provide your users the best DX and evolve the KMM ecosystem together with other library creators and contributors.
Next useful steps
- Now that you have published your library to Maven Central, it's time to let others know – and what better way than to add a badge to your project's README!
- Add your library to the community-driven list of Kotlin Multiplatform libraries to increase its discoverability.
- Take a look into the possibility to automate the release process and share your experience with the community, if you’ve already successfully made it this far!
Top comments (4)
Is this tutorial up-to-date with " If you're on the new Sonatype infrastructure (happens if you've registered after 2021-02-24 or requested it specifically)" as described in getstream.io/blog/publishing-libra...
Hi Katerina, Do you know how I can include dokka to the mix?
Hi @thanosfisherman, I hope this can be of help. gist.github.com/mcroteau/29dcb181d...
Let me know if it doesn't.
Best
The structure of the conventions-plugin generated by the KMP project wizard seems to look a lot different now? Is this tutorial still up to date?