Publishing your Kotlin Multiplatform library to Maven Central
Tags: kotlin, multiplatform
Published: 2021-03-25 12:40:40

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](https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/#overview) is exhaustive and up to date. You can start by following the first four steps, including "Generating a GPG key pair":
1. Register a Jira account with Sonatype (you can use [my issue](https://issues.sonatype.org/browse/OSSRH-65092) as an example). ✅
2. Verify your ownership of the group ID you want to use to publish your artifact by creating a GitHub repo. ✅
3. Generate a GPG key pair for signing your artifacts. ✅
4. Publish your public key. ✅
5. 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](https://docs.gradle.org/current/userguide/custom_plugins.html#sec:precompiled_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:

1. Add a new gradle project `convention-plugins` inside your library root project by creating a new folder named 
`convention-plugins` with `build.gradle.kts` in it. 
  ![Gradle project structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/icig8sxcu3diab1wktin.png)

2. Put the following in the `build.gradle.kts`:

	```kotlin
	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
	}
	```

3. Create a `convention.publication.gradle.kts` file in the `convention-plugins/src/main/kotlin` directory. This is where all the publication logic will be stored.

4. 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 from `local.properties` or environment variables, do all the required configuration in the ‘publishing’ section, and sign our publications with the signing plugin:

	```kotlin
	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)
	}
	```

5. Go back to your library project. Ask Gradle to prebuild your plugins by adding the following in the root `settings.gradle`:

	```kotlin
	includeBuild("convention-plugins")
	```


6. Now we can apply this logic in our library `build.script`. In the `plugins` section, replace using `maven-publish` with using our `conventional.publication`.

	```kotlin
	plugins {
	   kotlin("multiplatform") version "1.4.30"
	   id("convention.publication")
	}
	```

7. 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. 
![Maven publication Gradle tasks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ku5kppqoqq146zzh3m0.png)
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](https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/#your-first-release). In short, you need to:
1. Go to https://s01.oss.sonatype.org/ and log in using the credentials you used in Sonatype Jira.
2. Find your repository in the ‘Staging repositories’ section.
3. Close it.
4. 🚀 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._
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z06v8kxi0io3b45vso83.png)

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](https://search.maven.org/)!
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jn9h57szxned439ngmy.png)


# 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
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](https://shields.io/category/version) to your project's README! 
* Add your library to [the community-driven list of Kotlin Multiplatform libraries](https://libs.kmp.icerock.dev/) to increase its discoverability.
* Take a look into the possibility [to automate the release process](https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/#automating-sonatype-actions) and share your experience with the community, if you’ve already successfully made it this far!

{% github KaterinaPetrova/mpp-sample-lib no-readme %}