sbt / sbt-release

A release plugin for sbt
Apache License 2.0
641 stars 165 forks source link
git mercurial sbt sbt-plugin scala

sbt-release

This sbt plugin provides a customizable release process that you can add to your project.

sbt-release Scala version support

Notice: This README contains information for the latest release. Please refer to the documents for a specific version by looking up the respective tag.

Requirements

Usage

Add the following lines to ./project/plugins.sbt. See the section Using Plugins in the sbt website for more information.

addSbtPlugin("com.github.sbt" % "sbt-release" % "1.3.0")

version.sbt

Since the build definition is actual Scala code, it's not as straight forward to change something in the middle of it as it is with an XML definition.

For this reason, sbt-release won't ever touch your build definition files, but instead writes the new release or development version to a file defined by the setting releaseVersionFile, which is set to file("version.sbt") by default and points to $PROJECT_ROOT/version.sbt.

By default the version is set on the build level (using ThisBuild / version). This behavior can be controlled by setting releaseUseGlobalVersion to false, after which a version like version := "1.2.3" will be written to version.sbt.

Release Process

The default release process consists of the following tasks:

  1. Check that the working directory is a git repository and the repository has no outstanding changes. Also prints the hash of the last commit to the console.
  2. If there are any snapshot dependencies, ask the user whether to continue or not (default: no).
  3. Ask the user for the release version and the next development version. Sensible defaults are provided.
  4. Run clean.
  5. Run test:test, if any test fails, the release process is aborted.
  6. Write ThisBuild / version := "$releaseVersion" to the file version.sbt and also apply this setting to the current build state.
  7. Commit the changes in version.sbt.
  8. Tag the previous commit with v$version (eg. v1.2, v1.2.3).
  9. Run publish.
  10. Write ThisBuild / version := "nextVersion" to the file version.sbt and also apply this setting to the current build state.
  11. Commit the changes in version.sbt.

In case of a failure of a task, the release process is aborted.

Non-interactive release

You can run a non-interactive release by providing the argument with-defaults (tab completion works) to the release command.

For all interactions, the following default value will be chosen:

Set release version and next version as command arguments

You can set the release version using the argument release-version and next version with next-version.

Example:

release release-version 1.0.99 next-version 1.2.0-SNAPSHOT

Skipping tests

For that emergency release at 2am on a Sunday, you can optionally avoid running any tests by providing the skip-tests argument to the release command.

Cross building during a release

Since version 0.7, sbt-release comes with built-in support for cross building and cross publishing. A cross release can be triggered in two ways:

  1. via the setting releaseCrossBuild (by default set to false)
  2. by using the option cross for the release command

    > release cross with-defaults

Combining both ways of steering a cross release, it is possible to generally disable automatic detection of cross release by using releaseCrossBuild := false and running release cross.

Of the predefined release steps, the clean, test, and publish release steps are set up for cross building.

A cross release behaves analogous to using the + command:

  1. If no crossScalaVersions are set, then running release or release cross will not trigger a cross release (i.e. run the release with the scala version specified in the setting scalaVersion).
  2. If the crossScalaVersions setting is set, then only these scala versions will be used. Make sure to include the regular/default scalaVersion in the crossScalaVersions setting as well. Note that setting running release cross on a root project with crossScalaVersions set to Nil will not release anything.

In the section Customizing the release process we take a look at how to define a ReleaseStep to participate in a cross build.

Versioning Strategies

As of version 0.8, sbt-release comes with several strategies for computing the next snapshot version via the releaseVersionBump setting. These strategies are defined in sbtrelease.Version.Bump. By default, the Next strategy is used:

Users can set their preferred versioning strategy in build.sbt as follows:

releaseVersionBump := sbtrelease.Version.Bump.Major

Default Versioning

The default settings make use of the helper class Version that ships with sbt-release.

releaseVersion: The current version in version.sbt, without the "-SNAPSHOT" ending. So, if version.sbt contains 1.0.0-SNAPSHOT, the release version will be set to 1.0.0.

releaseNextVersion: The "bumped" version according to the versioning strategy (explained above), including the -SNAPSHOT ending. So, if releaseVersion is 1.0.0, releaseNextVersion will be 1.0.1-SNAPSHOT.

Custom Versioning

sbt-release comes with two settings for deriving the release version and the next development version from a given version.

These derived versions are used for the suggestions/defaults in the prompt and for non-interactive releases.

Let's take a look at the types:

val releaseVersion     : TaskKey[String => String]
val releaseNextVersion : TaskKey[String => String]

If you want to customize the versioning, keep the following in mind:

Custom VCS messages

sbt-release has built in support to commit/push to Git, Mercurial and Subversion repositories. The messages for the tag and the commits can be customized to your needs with these settings:

val releaseTagComment        : TaskKey[String]
val releaseCommitMessage     : TaskKey[String]
val releaseNextCommitMessage : TaskKey[String]

// defaults
releaseTagComment        := s"Releasing ${(ThisBuild / version).value}",
releaseCommitMessage     := s"Setting version to ${(ThisBuild / version).value}",
releaseNextCommitMessage := s"Setting version to ${(ThisBuild / version).value}",

Publishing signed releases

SBT is able to publish signed releases using the sbt-pgp plugin.

After setting that up for your project, you can then tell sbt-release to use it by setting the releasePublishArtifactsAction key:

releasePublishArtifactsAction := PgpKeys.publishSigned.value

Customizing the release process

Not all releases are created equal

The release process can be customized to the project's needs.

The release process is defined by State transformation functions (State => State), for which sbt-release defines this case class:

case class ReleaseStep (
  action: State => State,
  check: State => State = identity,
  enableCrossBuild: Boolean = false
)

The function action is used to perform the actual release step. Additionally, each release step can provide a check function that is run at the beginning of the release and can be used to prevent the release from running because of an unsatisfied invariant (i.e. the release step for publishing artifacts checks that publishTo is properly set up). The property enableCrossBuild tells sbt-release whether or not a particular ReleaseStep needs to be executed for the specified crossScalaVersions.

The sequence of ReleaseSteps that make up the release process is stored in the setting releaseProcess: SettingKey[Seq[ReleaseStep]].

The state transformations functions used in sbt-release are the same as the action/body part of a no-argument command. You can read more about building commands in the sbt website.

Release Steps

There are basically 2 ways to creating a new ReleaseStep:

Defining your own release steps

You can define your own state tansformation functions, just like sbt-release does, for example:

val checkOrganization = ReleaseStep(action = st => {
  // extract the build state
  val extracted = Project.extract(st)
  // retrieve the value of the organization SettingKey
  val org = extracted.get(Keys.organization)

  if (org.startsWith("com.acme"))
    sys.error("Hey, no need to release a toy project!")

  st
})

We will later see how to let this release step participate in the release process.

Reusing already defined tasks

Sometimes you just want to run an existing task or command. This is especially useful if the task raises an error in case something went wrong and therefore interrupts the release process.

sbt-release comes with a few convenience functions for converting tasks and commands to release steps:

For example:

releaseProcess := Seq[ReleaseStep](
  releaseStepInputTask(testOnly, " com.example.MyTest"),
  releaseStepInputTask(scripted),
  releaseStepTask(publish in subproject),
  releaseStepCommand("sonatypeRelease")
)

I highly recommend to make yourself familiar with the State API before you continue your journey to a fully customized release process.

Can we finally customize that release process, please?

Yes, and as a start, let's take a look at the default definition of releaseProcess:

The default release process

import ReleaseTransformations._

// ...

releaseProcess := Seq[ReleaseStep](
  checkSnapshotDependencies,              // : ReleaseStep
  inquireVersions,                        // : ReleaseStep
  runClean,                               // : ReleaseStep
  runTest,                                // : ReleaseStep
  setReleaseVersion,                      // : ReleaseStep
  commitReleaseVersion,                   // : ReleaseStep, performs the initial git checks
  tagRelease,                             // : ReleaseStep
  publishArtifacts,                       // : ReleaseStep, checks whether `publishTo` is properly set up
  setNextVersion,                         // : ReleaseStep
  commitNextVersion,                      // : ReleaseStep
  pushChanges                             // : ReleaseStep, also checks that an upstream branch is properly configured
)

The names of the individual steps of the release process are pretty much self-describing. Notice how we can just reuse the publish task by utilizing the releaseTask helper function, but keep in mind that it needs to be properly scoped (more info on Scopes).

Note, the commitReleaseVersion step requires that the working directory has no untracked files by default. It will abort the release in this case. You may disable this check by setting the releaseIgnoreUntrackedFiles key to true.

No Git, and no toy projects!

Let's modify the previous release process and remove the Git related steps, who uses that anyway.

import ReleaseTransformations._

// ...

ReleaseKeys.releaseProcess := Seq[ReleaseStep](
  checkOrganization,                // Look Ma', my own release step!
  checkSnapshotDependencies,
  inquireVersions,
  runTest,
  setReleaseVersion,
  publishArtifacts,
  setNextVersion
)

Overall, the process stayed pretty much the same:

Release notes anyone?

Now let's also add steps for posterous-sbt:

import posterous.Publish._
import ReleaseTransformations._

// ...

val publishReleaseNotes = (ref: ProjectRef) => ReleaseStep(
  check  = releaseStepTaskAggregated(check in Posterous in ref),   // upfront check
  action = releaseStepTaskAggregated(publish in Posterous in ref) // publish release notes
)

// ...

ReleaseKeys.releaseProcess <<= thisProjectRef apply { ref =>
  import ReleaseStateTransformations._
  Seq[ReleaseStep](
    checkOrganization,
    checkSnapshotDependencies,
    inquireVersions,
    runTest,
    setReleaseVersion,
    publishArtifacts,
    publishReleaseNotes(ref) // we need to forward `thisProjectRef` for proper scoping of the underlying tasks
    setNextVersion
  )
}

The check part of the release step is run at the start, to make sure we have everything set up to post the release notes later on. After publishing the actual build artifacts, we also publish the release notes.

Credits

Thank you, Jason and Mark, for your feedback and ideas.

Contributors

Johannes Rudolph, Espen Wiborg, Eric Bowman, Petteri Valkonen, Gary Coady, Alexey Alekhin, Andrew Gustafson, Paul Davies, Stanislav Savulchik, Tim Van Laer, Lars Hupel

License

Copyright (c) 2011-2014 Gerolf Seitz

Published under the Apache License 2.0