Deploy sbt projects to Sonatype (Maven Central)
We’re gonna use the following sample project tree:
foo
- src
- main
- scala
core
- src
- main
- scala
build.sbt
You’ll first need to create a Sonatype JIRA account here and then create an issue https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134
NOTE: The issue type will be
New Projectbut this doesn’t necessarily mean you’ll need to do this for every project you want to publish. It all depends on theGroup Idyou set. For example for (circe)[https://circe.github.io/] the group id isio.circeunder which livescirce-parser,circe-core,circe-generic, etc. So to push additional circe related that will live under this group id, the maintainer need not create additional tickets, but would if they started a projectfoothat wil live under the seperate group idio.foo
Once you see a comment on the ticket appear that reads something like this:
Configuration has been prepared, now you can:
Deploy snapshot artifacts into repository https://oss.sonatype.org/content/repositories/snapshots
Deploy release artifacts into the staging repository https://oss.sonatype.org/service/local/staging/deploy/maven2
Promote staged artifacts into repository 'Releases'
Download snapshot and release artifacts from group https://oss.sonatype.org/content/groups/public
Download snapshot, release and staged artifacts from staging group https://oss.sonatype.org/content/groups/staging
please comment on this ticket when you promoted your first release, thanks
You’re ready to deploy your project!
In-project config
You’ll need the following plugins in plugins.sbt:
addSbtPlugin("com.dwijnand" % "sbt-travisci" % "<version>")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "<version>")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "<version>")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "<version>")
Here’s a minimal build.sbt:
import ReleaseTransformations._
organization in ThisBuild := "group.id.from.ticket"
lazy val root = project.in(file("."))
.settings(noPublishSettings: _*)
.aggregate(foo, core)
lazy val core = project.in(file("core"))
.settings(releasePublishSettings: _*)
.settings(name := "core")
lazy val foo = project.in(file("foo"))
.settings(releasePublishSettings: _*)
.settings(name := "foo")
.dependsOn(core % "compile->compile;test->test")
/** We dont want to publish the `root` module */
lazy val noPublishSettings = Seq(
publish := {},
publishLocal := {},
publishArtifact := false
)
lazy val releasePublishSettings = Seq(
releaseCrossBuild := true,
releasePublishArtifactsAction := PgpKeys.publishSigned.value,
releaseProcess := Seq[ReleaseStep](
/** This is my specific list of tasks to tell the `sbt-release` plugin to run.
You can obviously configure this differently however if you don't care particularly,
this particular configuration works well
*/
checkSnapshotDependencies,
inquireVersions,
runClean,
runTest,
setReleaseVersion,
commitReleaseVersion,
tagRelease,
publishArtifacts,
setNextVersion,
commitNextVersion,
ReleaseStep(action = Command.process("sonatypeReleaseAll", _)),
pushChanges
),
homepage := Some(url("https://github.com/yourUsername/thisProject")),
licenses := Seq("Apache 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
// or GitLabHosting
sonatypeProjectHosting := Some(GitHubHosting("yourUsername", "thisProject", "yourEmail"))
publishMavenStyle := true,
publishArtifact in Test := false,
pomIncludeRepository := { _ => false },
// if not set, will be the same as `organization` above
sonatypeProfileName := "group.id.from.ticket",
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
},
scmInfo := Some(
ScmInfo(
url("https://github.com/yourUsername/thisProject"),
"scm:git@github.com:yourUsername/thisProject.git"
)
),
developers := List(
Developer("yourSonatypeUsername", "FirstName LastName", "yourEmail", url("yourWebsiteUrl"))
)
)
TIP:
dependsOn(core % "compile->compile;test->test")allows you to use common test code utilities defined in thecoremodule in thefoomodule
sbt-release by default will read the version to release from a version.sbt file in the root
of your project:
version in ThisBuild := "0.1.0"
If you add -SNAPSHOT to the end of your version sbt-release will put the packaged artifacts
in the Snapshots repo in maven central. More on how the repos in maven central are set up in a second
Familiarizing yourself with the sonatype online explorer
Here, log in. Click on Repositories in left hand pane and search in the upper right corner for repo
with Repository name just Snapshots. This is maven’s snapshot repo, where versions ending with -SNAPSHOT will go
The repo named just Releases is where versions of the form <major>.<minor>.<patch> will go. When you enter +publishSigned
in the sbt shell your artifacts will first be placed in a staging repository. You can find it towards the bottom of the list when
you click on Staging Repositories in the left hand pane. When you are doing a release to the Releases repo, maven promotes those
artifacts you’ve placed in the staging repo to the Releases repo. It takes around 10 minutes for you to be able to pull in said
artifact(s) in a project and up to 2 hrs for it to be searchable on http://mvnrepository.com
sbt-travisci and multiple Scala major-minor artifacts
In a .travis.yml file in your project we can declare various Scala version to build/release our project
against. By default when you do say a sbt compile the last version in the list is chosen:
language: scala
scala:
- 2.11.12
- 2.12.4
jdk:
- oraclejdk8
Out-of-project config
Generate PGP private/public key
You’ll need this to sign your artifacts when publishing them to the staging repo. In your project dir, enter an sbt shell
> set pgpReadOnly := false
> pgp-cmd gen-key
Please enter the name associated with the key: FirstName LastName
Please enter the email associated with the key: yourEmail
Please enter the passphrase for the key: ********
Please re-enter the passphrase for the key: ********
[info] Creating a new PGP key, this could take a long time.
[info] Public key := ~/.sbt/gpg/pubring.asc
[info] Secret key := ~/.sbt/gpg/secring.asc
[info] Please do not share your secret key. Your public key is free to share.
You’ll need to upload this created key to a key server:
> pgp-cmd send-key yourEmail hkp://pool.sks-keyservers.net
Add sonatype credentials
Create a file ~/.sbt/<your-project-sbt-major-minor-version>/sonatype.sbt with the following:
credentials += Credentials(
"Sonatype Nexus Repository Manager",
"oss.sonatype.org",
"<sonatype-username>",
"<sonatype-password>"
)
And now you’re ready to release
In your project sbt prompt:
> +publishSigned
This will publish foo and core artifacts to the staging repo. The + indicates that you’d
like to package and deploy your module(s) against all of the different major-minor Scala versions
declared in your .travis.yml file
Mar 2019 Update: Getting the following exception with
+publishLocaljava.net.ProtocolException: Too many follow-up requests sbtwhich is some issue with gigahorse not being able to parse yoursonatype.sbtremote host. Work around is to addupdateOptions := updateOptions.value.withGigahorse(false)to your project’s settings (don’t just dump it inbuild.sbt). See this issue for more details/status
To promote these artifacts to the Releases repo:
> sonatypeReleaseAll
TIP: If
+publishSignedfails for some reason it is best to drop, not close, the staging repo opened as a result viasonatypeDrop(if you have mutliple open this operation will fail withe something like:[error] Multiple repositories are found: [error] [yourorg-XXXX] status:open, profile:yourorg(somehash) [error] [yourorg-XXXY] status:open, profile:yourorg(somehash) [error] Specify one of the repository ids in the command lineso you’ll need to specify which one you want to drop via
sonatypeDrop XXXX(the repo id from above)) if you only close a staging repo, push a new one up and attempt to promote, Nexus will oddly try to promote the earliest repo found, i.e. the closed one
And you’re done!
Comments