Publishing Kotlin/Java/Android libraries can be intimidating at first sight with plenty of options to choose from and a lot of different plugins providing different sometimes overlapping features to upload your jar files.
The good news is that the sunsetting of Jcenter brought some welcome clarification in this process and the ecosystem is starting to mature.
I’ll pass quickly over the process of setting up a Sonatype Account and uploading a simple library as there are a ton of nice resources out there to explain the process like this blog post or this other.
Instead, this article focuses on all the little different steps needed for publication like:
- Configuring repositories
- Creating publications
- Configuring sources & javadoc
- Configuring signatures
- Avoiding split repositories
- Publishing artifacts from different CI jobs.
- Automating the release process
Some are optional, some are mandatory. Depending whether you want to publish JVM libraries, Android, Gradle plugins, multiplatform libraries or maybe something else, the use cases can be a little bit different and the configuration matrix quickly becomes huge.
Over time, plugins have been developed to help with this. If you're looking for the best plugin for your needs, skip to the conclusion for a feature matrix of different publishing plugins.
At heart, a maven repository is nothing else that files organized in a certain layout. For an example, you can find okhttp at https://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/4.9.1/. All files are named and organized in a certain way so that maven and Gradle can lookup group
(com.squareup.okhttp3), artifact
(okhttp here) and version
(4.9.1 here). These are called the GAV coordinates (for Group, Artifact, Version) and this is what goes in the dependencies {}
block of your Gradle files:
https://gist.github.com/ccf0a6a68d2673351af54cb89ff6d0dd
Now there's no magic there. You can host the same files in a S3 bucket or on your ftp and point your Gradle script to the url, it'll work exactly the same.
MavenCentral is included in a lot of builds already so it makes sense to upload there for your users to find your artifacts easily. Uploading to MavenCentral is a simple POST request:
https://gist.github.com/a233633f917c531bc7ad4b1fae8eb2af
This is all the maven-publish
plugin does behind the scenes. You still need to tell it where to upload as you usually don't want to upload to localhost:
https://gist.github.com/25883403509acbef066328f6a72fbd40
Once you have configured your repositories, the next step is to create your publications. The default setup is like below:
https://gist.github.com/e6b21c289fd98d566f297c0194f18ee4
The above repositories and publications will create 2 tasks:
https://gist.github.com/3b74e905b2d0083687007c62a6c661b8
Some plugins will create the publications automatically for you like the Kotlin multiplatform plugin or the java-gradle-plugin
plugin. But others will not, leaving you to configure everything.
Also MavenCentral requires that you upload matching sources and javadoc with your artifacts. Here again some plugin will include sources and javadoc but some others will not. Depending the kind of projects you are in, collecting the sources might be slightly different. For an example, this is how the com.vanniktech.maven.publish
plugin does it for Android:
https://gist.github.com/c1102cd7e98a53eaec5caeb925f0f59f
Again, MavenCentral requires signatures: https://gist.github.com/99843d822d723020564b5761afa3b4ae
It's not a lot of code but some plugins like com.vanniktech.maven.publish
automate that for you. They also add some logic to disable signing for SNAPSHOTs where they are not required for example (see here for more details).
Heads up: This is where things become Sonatype specific. While the maven file layout and signatures apply to any maven repository/publication, interacting with a Nexus repository such as the one powering MavenCentral is 100% Sonatype/MavenCentral specific. (In other terms, Maven != MavenCentral)
If you've done everything until here, you can now upload your artifacts to OSS Staging:
https://gist.github.com/9dbf6788020f193246abf0dce9e96f83
Then head to https://oss.sonatype.org/#stagingRepositories to see your files. If you see only one line there, congrats, things worked well! But there is a chance you see two lines there:
This is what I call split staging repository and happens because the sonatype server incorrectly grouped uploaded artifacts. This is problematic because verifications will not be atomic anymore. For an example, if one jar ends up in a repository and the matching signature ends up in anoother, verifications will fail and you will be unable to close (=verify) your repository.
To mitigate this, the io.github.gradle-nexus.publish-plugin
creates an explicit staging repository id where to upload your artifacts and POST your artifact to that specific url, making sure the repository is always used:
https://gist.github.com/fb4267a7e7fab91967c6379733cee7c0
For large multiplatform projects that need to be built on different machines (Windows, Linux, MacOS), split repositories are especially frequent. Using io.github.gradle-nexus.publish-plugin
is an option that will create one staging repository per machine. You can then close them separately and if they are not overlapping things should go well. It makes the release non-atomic though. Assume something is wrong in your windows artifacts. If you already released the linux one, your release is in a weird state with only partial files.
To mitigate that, you'll have to create the repository ahead of time in a separate job and forward that to each platform specific job. The create-nexus-staging-repo Github Action will help with that. Check out Romain Boisselle excellent post about it for more details.
Finally, once everything is uploaded in a single staging repository, you can release your artifacts to MavenCentral. Using the UI, you would:
- close the repository: this triggers the verifications
- release the repository: this moves your files from the staging repo to MavenCentral where they will be publicly available
In order to do everything automatically, both the io.github.gradle-nexus.publish-plugin
and com.vanniktech.maven.publish
plugins offer a closeAndReleaseRepository
task
https://gist.github.com/c66ef7670d63075822f92164308cb8be
As described above, this process will fail if you have multiple open repositories open at the same time so there is still room for improvement but it should work in the canonical case and allow to have a release process 100% automated
Over time, multiple plugins were built to help with the above. I tried to list them, hopefully not forgetting any:
Id | Github | Depends on | Publishes | Configures Repositories | Adds sources | Adds javadoc | Adds signatures | Single Staging Repo | Close And Release | Description |
---|---|---|---|---|---|---|---|---|---|---|
maven-publish |
builtin | nothing | yes | no | no | no | no | no | no | The new builtin gradle solution for publishing to a maven repository |
io.github.gradle-nexus.publish-plugin |
gradle-nexus/publish-plugin | maven-publish |
with maven-publish |
yes | no | no | no | yes | yes | The merge of io.codearte.nexus-staging and de.marcphilipp.nexus-publish |
com.vanniktech.maven.publish |
vanniktech/gradle-maven-publish-plugin | maven-publish , dokka |
with maven-publish |
yes | yes | yes | yes | yes (if a staging repo already exists) | yes | Configures Dokka and the different publications for Android and other projects. |
maven (Deprecated) |
builtin | nothing | yes | no | no | no | no | no | no | The legacy builtin gradle solution |
io.codearte.nexus-staging (Deprecated) |
Codearte/gradle-nexus-staging-plugin/ | maven-publish |
with maven-publish |
yes | no | no | no | no | yes | A plugin to automate the release process |
de.marcphilipp.nexus-publish (Deprecated) |
marcphilipp/nexus-publish-plugin | maven-publish , io.codearte.nexus-staging |
with maven-publish |
no | no | no | no | yes | no | A plugin to automate the release process |
com.jfrog.bintray (Deprecated) |
bintray/gradle-bintray-plugin | nothing | yes | yes | no | no | no | n/a | n/a | The legacy plugin to publish to bintray and jcenter |
com.bmuschko.nexus (Deprecated) |
bmuschko/gradle-nexus-plugin | maven |
with maven |
yes | yes | yes | optional | no | no | A legacy all in one solution for maven publishing |
All in all, you will most likely always need maven-publish
as it is the base plugin on top of which others work. From there you can either:
- configure everything by hand, maybe using libs like Vespene to call the Nexus API.
- add
io.github.gradle-nexus.publish-plugin
to avoid split staging repositories andcloseAndReleaseRepository
- add
com.vanniktech.maven.publish
as a one-stop shop that will configure all your publications, sources and javadoc as well as providecloseAndReleaseRepository
- If you're building multiplatform project, use
create-nexus-staging-repo
to make your release atomic
That's it! It's still a lot of details and I'm hoping this process could be improved. For an example removing the requirement for javadocs, making closeAndRelease only one step or standardizing the signature process. But there's already been a lot of improvements over the past months and since the adoption of maven-publish
. There's also more and more resources online so now is a good time to start publishing your libs!
💙 Many thanks to Romain Boisselle and Louis CAD for their advices and proofreading