This sbt plugin provides a customizable release process that you can add to your project.
Notice: This README contains information for the latest release. Please refer to the documents for a specific version by looking up the respective tag.
- sbt >= 0.11.1 for sbt-release 0.4; sbt 0.11.3 or 0.12.0 for sbt-release >= 0.5
- The version of the project should follow the semantic versioning scheme on semver.org with the following additions:
- The minor and bugfix part of the version are optional.
- The appendix after the bugfix part must be alphanumeric (
[0-9a-zA-Z]
) but may also contain dash characters-
. - These are all valid version numbers:
- 1.2.3
- 1.2.3-SNAPSHOT
- 1.2beta1
- 1.2
- 1
- 1-BETA17
- A publish repository configured. (Required only for the default release process. See further below for release process customizations.)
- git [optional]
Add the following lines to ./project/build.sbt
. See the section Using Plugins in the xsbt wiki for more information.
// This resolver declaration is added by default in SBT 0.12.x
resolvers += Resolver.url(
"sbt-plugin-releases",
new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases/")
)(Resolver.ivyStylePatterns)
addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8")
Important: The settings releaseSettings
need to be mixed into every sub-projects settings
.
This is usually achieved by extracting common settings into a val standardSettings: Seq[Setting[_]]
which is then included in all sub-projects.
Setting/task keys are defined in sbtrelease.ReleasePlugin.ReleaseKeys
.
releaseSettings
import sbtrelease.ReleasePlugin._
object MyBuild extends Build {
lazy val MyProject(
id = "myproject",
base = file("."),
settings = Defaults.defaultSettings ++ releaseSettings ++ Seq( /* custom settings here */ )
)
}
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 release-version-file
, which is set to file("version.sbt")
by default and points to $PROJECT_ROOT/version.sbt
.
The default release process consists of the following tasks:
- 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.
- If there are any snapshot dependencies, ask the user whether to continue or not (default: no).
- Ask the user for the
release version
and thenext development version
. Sensible defaults are provided. - Run
test:test
, if any test fails, the release process is aborted. - Write
version in ThisBuild := "$releaseVersion"
to the fileversion.sbt
and also apply this setting to the current build state. - Commit the changes in
version.sbt
. - Tag the previous commit with
v$version
(eg.v1.2
,v1.2.3
). - Run
publish
. - Write
version in ThisBuild := "nextVersion"
to the fileversion.sbt
and also apply this setting to the current build state. - Commit the changes in
version.sbt
.
In case of a failure of a task, the release process is aborted.
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:
- Continue with snapshots dependencies: no
- Release Version: current version without the qualifier (eg.
1.2-SNAPSHOT
->1.2
) - Next Version: increase the minor version segment of the current version and set the qualifier to '-SNAPSHOT' (eg.
1.2.1-SNAPSHOT
->1.3.0-SNAPSHOT
) - VCS tag: abort if the tag already exists
- VCS push:
- Abort if no remote tracking branch is set up.
- Abort if remote tracking branch cannot be checked (eg. via
git fetch
). - Abort if the remote tracking branch has unmerged commits.
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.
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:
-
via the setting
release-cross-build
which by default automatically triggers a cross release ifcross-scala-versions
contains at least 1 version different thanscala-version
. -
by using the option
cross
for therelease
commandrelease cross with-defaults
Combining both ways of steering a cross release, it is possible to generally disable automatic detection of cross release by using ReleaseKeys.crossBuild := 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:
- If no
cross-scala-versions
are set, then runningrelease
orrelease cross
will not trigger a cross release (i.e. run the release with the scala version specified in the settingscala-version
). - If the
cross-scala-versions
setting is set, then only these scala versions will be used. Make sure to include the regular/defaultscala-version
in thecross-scala-version
setting as well.
In the section Customizing the release process we take a look at how to define a ReleaseStep
to participate in a cross build.
As of version 0.8, sbt-release comes with four strategies for computing the next snapshot version via the release-version-bump
setting. These strategies are, defined in sbtrelease.Version.Bump
. By default, the Next
strategy is used:
Major
: always bumps the major part of the versionMinor
: always bumps the minor part of the versionBugfix
: always bumps the bugfix part of the versionNext
: bumps the last version part (e.g.0.17
->0.18
,0.11.7
->0.11.8
)
Example:
ReleaseKeys.versionBump := Version.Bump.Major
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 : SettingKey[String => String]
val nextVersion : SettingKey[String => String]
The default settings make use of the helper class Version
that ships with sbt-release.
// strip the qualifier off the input version, eg. 1.2.1-SNAPSHOT -> 1.2.1
releaseVersion := { ver => Version(ver).map(_.withoutQualifier.string).getOrElse(versionFormatError) }
// bump the minor version and append '-SNAPSHOT', eg. 1.2.1 -> 1.3.0-SNAPSHOT
nextVersion := { ver => Version(ver).map(_.bumpMinor.asSnapshot.string).getOrElse(versionFormatError) }
If you want to customize the versioning, keep the following in mind:
-
releaseVersion
- input: the current development version
- output: the release version
-
nextVersion
- input: the release version (either automatically 'chosen' in a non-interactive build or from user input)
- output: the next development version
sbt-release has built in support to commit/push to Git and Mercurial repositories. The messages for the tag and the commits can be customized to your needs with these settings:
val tagComment : TaskKey[String]
val commitMessage : TaskKey[String]
// defaults
tagComment <<= (version in ThisBuild) map (v => "Releasing %s" format v),
commitMessage <<= (version in ThisBuild) map (v => "Setting version to %s" format v)
The release process can be customized to the project's needs.
- Not using Git? Then rip it out.
- Want to check for the existance of release notes at the start of the release and then publish it with posterous-sbt at the end? Just add the release step.
The release process is defined by State transformation functions (State => State
), for which sbt-release defines the 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
unsatisified 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 cross-scala-versions
.
The sequence of ReleaseStep
s 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 wiki.
There are basically 2 ways to creating a new ReleaseStep
:
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.
Sometimes you just want to run an already existing task. 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 convenience function
releaseTask[T](task: TaskKey[T]): ReleaseStep
that takes any scoped task and wraps it in a state transformation function, executing the task when an instance of State
is applied to the function.
I highly recommend to make yourself familiar with the State API before you continue your journey to a fully customized release process.
Yes, and as a start, let's take a look at the default definition of releaseProcess
:
import sbtrelease._
import ReleaseStateTransformations._
// ...
releaseProcess := Seq[ReleaseStep](
checkSnapshotDependencies, // : ReleaseStep
inquireVersions, // : 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 scoping and settings).
Let's modify the previous release process and remove the Git related steps, who uses that anyway.
import sbtrelease._
import ReleaseStateTransformations._
// ...
releaseProcess := Seq[ReleaseStep](
checkOrganization, // Look Ma', my own release step!
checkSnapshotDependencies,
inquireVersions,
runTest,
setReleaseVersion,
publishArtifacts,
setNextVersion,
)
}
Overall, the process stayed pretty much the same:
- The Git related steps were left out.
- Our
checkOrganization
task was added in the beginning, just to be sure this is a serious project.
Now let's also add steps for posterous-sbt:
import posterous.Publish._
import sbtrelease._
// ...
val publishReleaseNotes = (ref: ProjectRef) => ReleaseStep(
check = releaseTask(check in Posterous in ref), // upfront check
action = releaseTask(publish in Posterous in ref) // publish release notes
)
// ...
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.
Thank you, Jason and Mark, for your feedback and ideas.
Johannes Rudolph, Espen Wiborg, Eric Bowman, Petteri Valkonen
Copyright (c) 2011, 2012 Gerolf Seitz
Published under the Apache License 2.0