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 Project
but this doesn’t necessarily mean you’ll need to do this for every project you want to publish. It all depends on theGroup Id
you set. For example for (circe)[https://circe.github.io/] the group id isio.circe
under 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 projectfoo
that 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 thecore
module in thefoo
module
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
+publishLocal
java.net.ProtocolException: Too many follow-up requests sbt
which is some issue with gigahorse not being able to parse yoursonatype.sbt
remote 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
+publishSigned
fails 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 line
so 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