Intro
Say what you will about JavaScript, but npm has made it incredibly easy for anyone to publish code. In just a few clicks, you can share a library to make others' lives easier, or publish a little snippet like left-pad
to cause endless headaches.
Java has long had the Maven repository (and accompanying build tool of the same name), which serves a similar purpose: sharing packaged code.
But it can be intimidating for a newbie to publish to Maven -- that's where Sonatype comes in.
Sonatype, Inc.'s Central Repository (a complement to Apache's Maven repository) makes it super easy to share code. With just a few lines and a few manual steps, you can publish your package to Maven, and others can easily import it and use it.
sbt's Using Sonatype guide and Sonatype's Deployment Guide cover probably 95% of what you need to know for this process, but there are a few gotchas that I'll highlight below using the ❗ exclamation point emoji.
Note: this guide assumes that your source code is hosted in a GitHub or GitLab repository.
Steps
1. Create a Sonatype Jira issue
Sonatype syncs with Maven, and so to publish to Maven, you can publish via Sonatype. To do so, you'll need a Sonatype Jira account to create an issue. Do that by clicking here to create an account and then clicking here to create a Jira issue.
As an example, here is the ticket I opened to publish a project of mine to Maven. If you want to publish multiple packages to Maven using Sonatype, you'll need to create multiple Jira tickets, but you only need the one user account.
The fields in the Jira are easy to fill out:
Summary
This should include the name of the project. In my case, I'm publishing a small project hosted at https://github.com/awwsmm/zepto, so I've included the name "zepto" in the Jira summary.
Group Id
If your project is hosted on GitHub, this should be something like io.github.awwsmm
, or similar for your GitHub username. See this guide for more information on package coordinates.
❗ GOTCHA: this should not be something like
com.github.awwsmm
.com.github
coordinates are no longer supported. It can be your personal website, likecom.awwsmm
, but a bot will comment on your Jira asking you to add a DNS record to prove that you own that website, like here on another one of my Jiras.
Project URL
This is where users can go on the web to find information about your project. For me, that's https://github.com/awwsmm/zepto
, but it could be a github.io
page or a page on your personal website, or whatever.
SCM url
This is the URL that your source code is hosted at. For me, that's https://github.com/awwsmm/zepto.git
.
2. Generate and distribute a PGP key
On macOS
On macOS, I recommend that you use https://gpgtools.org/ to create and distribute your PGP key. Simply download the installer and follow the instructions there.
To run sbt publishSigned
later in this guide, you will also need gpg2
and pinentry
installed. You can get those on macOS via brew
with
$ brew install gpg2
$ brew install pinentry-mac
Note that you need gpg2
here, not gpg
. You should check if you have both installed and brew uninstall gpg
if it exists
$ which gpg gpg2
Once you've done that, check that gpg-agent
is correctly configured, and restart it by --kill
ing it, per these instructions.
On Linux
We'll use a PGP key to sign this package, so users can be confident that you are the person who created it. To do this, you can mostly follow the instructions from scala-sbt.org...
Install GnuPG (on macOS, you can do this via brew install gnupg
) and verify that it's installed by checking the version
$ gpg --version
gpg (GnuPG) 2.3.1
...
Next, generate a key...
$ gpg --gen-key
...list your keys...
$ gpg --list-keys
gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2023-07-11
/Users/andrew.watson/.gnupg/pubring.kbx
---------------------------------------
pub ed25519 2021-07-11 [SC] [expires: 2023-07-11]
1234517530FB96F147C6A146A326F592D39AAAAA
uid [ultimate] Andrew Watson <my@email.com>
sub cv25519 2021-07-11 [E] [expires: 2023-07-11]
...and distribute the key to some GPG keyserver, like
$ gpg --keyserver keyserver.ubuntu.com --send-keys
1234517530FB96F147C6A146A326F592D39AAAAA
gpg: sending key A326F592D39AAAAA to hkp://keyserver.ubuntu.com
❗ GOTCHA: If you get an error like
keyserver send failed: No name
from the command above, it means the keyserver you're trying to send to is no longer active. You can instead use any other major GPG keyserver, like the Ubuntu one I used above.
Leonard notes here that synchronising these keys around the various keyservers can take some time, but for me it worked almost instantly.
Once you publish your key, you can check that it's visible on the public keyservers by searching for it here: https://keys.openpgp.org/.
3. Enable sbt-pgp
As outlined here, you'll next want to add the sbt-pgp
plugin to your project. You can add it to your project's plugin.sbt
file, or you can add it to ~/.sbt/1.0/plugins/gpg.sbt
to enable it for all sbt projects. The line you should add is
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
The latest version of this plugin can be found at the GitHub page for the project.
4. Add your Sonatype credentials to your project
Next, add your credentials to your project by following the instructions in Step 3 here. I'll reproduce them to be clear...
Create a file at
$HOME/.sbt/1.0/sonatype.sbt
And add the following line to it
credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials")
Then, create the credentials file at
~/.sbt/sonatype_credentials
Add the following information to that file
realm=Sonatype Nexus Repository Manager
host=s01.oss.sonatype.org
user=<your username>
password=<your password>
The user
and password
here are the same username and password you used when creating your account on Sonatype's Jira server in Step 1. You don't need to worry about putting anything in quotes here -- spaces are fine. But...
❗ GOTCHA: ...be careful! Some older guides say to set
host=oss.sonatype.org
, but this has recently changed tohost=s01.oss.sonatype.org
. More information about this change can be found here.
5. Set up your publish.sbt
The penultimate step in this process is to configure your project for publishing, by adding a bunch of information to either your project's build.sbt
(or, as I recommend) publish.sbt
file.
Replace username
with your GitHub / GitLab username below, project
with the name of the project (in my case, these are awwsmm
and zepto
, respectively), and all the other obvious fill-in bits, and paste it into a publish.sbt
file at the root of your project
ThisBuild / organization := "io.github.username"
ThisBuild / organizationName := "username"
ThisBuild / organizationHomepage := Some(url("https://www.yourwebsite.com"))
ThisBuild / scmInfo := Some(
ScmInfo(
url("https://github.com/username/project"),
"scm:git@github.username/project.git"
)
)
ThisBuild / developers := List(
Developer(
id = "username",
name = "Your Name Goes Here",
email = "youremail@address.com",
url = url("https://www.yourwebsite.com")
)
)
ThisBuild / description := "Describe your project here..."
ThisBuild / licenses := List("The Unlicense" -> new URL("https://unlicense.org/"))
ThisBuild / homepage := Some(url("https://github.com/username/project"))
// Remove all additional repository other than Maven Central from POM
ThisBuild / pomIncludeRepository := { _ => false }
ThisBuild / publishTo := {
val nexus = "https://s01.oss.sonatype.org/"
if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
ThisBuild / publishMavenStyle := true
ThisBuild / versionScheme := Some("early-semver")
You can set the licenses
to whatever you feel is appropriate. Here is GitHub's guide to choosing a license for your repo -- though be aware that they probably ignore it anyway.
❗ GOTCHA: Notice that we've also used
s01.oss.sonatype.org
above!
6. Publish!
Once you've got all of that set up, you should be able to publish your project from the sbt
shell with the publishSigned
command
sbt> publishSigned
On macOS, this should open up the GPGTools UI, where you can enter the password you associated with your PGP key. The output will then look something like
[info] Wrote /local/path/to/zepto_2.13-0.4.0.pom
[info] published zepto_2.13 to https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/awwsmm/zepto_2.13/0.4.0/zepto_2.13-0.4.0-sources.jar
[info] published zepto_2.13 to https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/awwsmm/zepto_2.13/0.4.0/zepto_2.13-0.4.0-javadoc.jar
[info] published zepto_2.13 to https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/awwsmm/zepto_2.13/0.4.0/zepto_2.13-0.4.0-javadoc.jar.asc
...
You can use your Sonatype username and password to log in to https://s01.oss.sonatype.org/ where you should now see your (pre-release) project!
Note that if you try to click on one of the .jar
links above, you may get an error like
Staging of Repository within profile ID='...' is not yet started!
You can follow along with Sonatype's guide here to release the artifacts to the public.
Select the project, click Close
from the menu, and then Release
. You should see your project on Maven almost immediately, though it will take a few hours to show up in the search engine.
...and that's it!
You should now be able to import your published library into other projects by adding
"io.github.awwsmm" %% "zepto" % "0.4.0"
(or whatever is appropriate) to the libraryDependencies
in the build.sbt
of another project.
I wrote this guide mostly as a way to remember the GOTCHAs I encountered above, but hopefully it makes it easier for someone else to publish their Scala project to Maven. It's something I've wanted to try for a while but didn't get around to until recently.
Feel free to comment below with any questions or suggestions you may have. And thanks for reading!