-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify integration with sbt-release #187
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
sbt-version-policy/src/main/scala/sbtversionpolicy/withsbtrelease/ReleaseVersion.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package sbtversionpolicy.withsbtrelease | ||
|
||
import sbtversionpolicy.Compatibility | ||
import sbtversionpolicy.SbtVersionPolicyPlugin.aggregatedAssessedCompatibilityWithLatestRelease | ||
import sbtversionpolicy.SbtVersionPolicyPlugin.autoImport.versionPolicyAssessCompatibility | ||
import sbt.* | ||
import sbtversionpolicy.SbtVersionPolicyMima.autoImport.versionPolicyPreviousVersions | ||
|
||
/** Convenient methods to integrate with the plugin sbt-release */ | ||
object ReleaseVersion { | ||
|
||
private val qualifierVariableName = "VERSION_POLICY_RELEASE_QUALIFIER" | ||
|
||
/** | ||
* @return a [release version function](https://github.com/sbt/sbt-release?tab=readme-ov-file#custom-versioning) | ||
* that bumps the patch, minor, or major version number depending on the provided | ||
* compatibility level. | ||
* @param qualifier Optional qualifier to append to the version (e.g. `"-RC1"`). By default, it tries to read | ||
* it from the environment variable VERSION_POLICY_RELEASE_QUALIFIER. | ||
*/ | ||
def fromCompatibility(compatibility: Compatibility, qualifier: String = sys.env.getOrElse(qualifierVariableName, "")): String => String = { | ||
val maybeBump = | ||
compatibility match { | ||
case Compatibility.None => Some(Version.Bump.Major) | ||
case Compatibility.BinaryCompatible => Some(Version.Bump.Minor) | ||
case Compatibility.BinaryAndSourceCompatible => None // No need to bump the patch version, because it has already been bumped when sbt-release set the next release version | ||
} | ||
{ (currentVersion: String) => | ||
val versionWithoutQualifier = | ||
Version(currentVersion) | ||
.getOrElse(Version.formatError(currentVersion)) | ||
.withoutQualifier | ||
val bumpedVersion = | ||
(maybeBump match { | ||
case Some(bump) => versionWithoutQualifier.bump(bump) | ||
case None => versionWithoutQualifier | ||
}).string | ||
bumpedVersion + qualifier | ||
} | ||
} | ||
|
||
private def fromAssessedCompatibility(qualifier: String)(assessCompatibility: Def.Initialize[Task[Compatibility]]): Def.Initialize[Task[String => String]] = | ||
Def.ifS(Def.task { | ||
versionPolicyPreviousVersions.value.isEmpty | ||
})(Def.task { | ||
// If there are no previous versions to assess the compatibility with, | ||
// fallback to the default release version function, which drops the qualifier | ||
// from the version set in the file `version.sbt` | ||
// (e.g., "1.0.0-SNAPSHOT" => "1.0.0") | ||
(version: String) => | ||
Version(version) | ||
.map(_.withoutQualifier.string + qualifier) | ||
.getOrElse(Version.formatError(version)) | ||
})(Def.task { | ||
val log = Keys.streams.value.log | ||
val compatibility = assessCompatibility.value | ||
log.debug(s"Compatibility level is ${compatibility}") | ||
fromCompatibility(compatibility, qualifier) | ||
}) | ||
|
||
/** | ||
* Task returning a [release version function](https://github.com/sbt/sbt-release?tab=readme-ov-file#custom-versioning) | ||
* based on the assessed compatibility level of the project. | ||
* | ||
* Use it in your `.sbt` build definition as follows: | ||
* | ||
* {{{ | ||
* import sbtversionpolicy.withsbtrelease.ReleaseVersion | ||
* | ||
* releaseVersion := ReleaseVersion.fromAssessedCompatibilityWithLatestRelease().value | ||
* }}} | ||
* | ||
* sbt-release uses the `releaseVersion` function to set the version before publishing a release (at step | ||
* `setReleaseVersion`). It reads the current `version` (usually defined in a file `version.sbt`, and looking | ||
* like `"1.2.3-SNAPSHOT"`), and applies the function to it. | ||
* | ||
* @param qualifier Optional qualifier to append to the version (e.g. `"-RC1"`). By default, it tries to read | ||
* it from the environment variable VERSION_POLICY_RELEASE_QUALIFIER. | ||
*/ | ||
def fromAssessedCompatibilityWithLatestRelease( | ||
qualifier: String = sys.env.getOrElse(qualifierVariableName, "") | ||
): Def.Initialize[Task[String => String]] = | ||
fromAssessedCompatibility(qualifier)(Def.task { | ||
val compatibilityResults = versionPolicyAssessCompatibility.value | ||
val compatibilityWithLatestRelease = | ||
compatibilityResults.headOption | ||
.getOrElse(throw new MessageOnlyException("Unable to assess the compatibility level of this project.")) | ||
val (_, compatibility) = compatibilityWithLatestRelease | ||
compatibility | ||
}) | ||
|
||
/** | ||
* Task returning a [release version function](https://github.com/sbt/sbt-release?tab=readme-ov-file#custom-versioning) | ||
* based on the assessed compatibility level of the project (ie, the highest level of compatibility | ||
* satisfied by all the sub-projects aggregated by this project). | ||
* | ||
* Use it in the root project of your `.sbt` build definition as follows: | ||
* | ||
* {{{ | ||
* import sbtversionpolicy.withsbtrelease.ReleaseVersion | ||
* | ||
* val `my-project` = | ||
* project | ||
* .in(file(".")) | ||
* .aggregate(mySubproject1, mySubproject2) | ||
* .settings( | ||
* releaseVersion := ReleaseVersion.fromAggregatedAssessedCompatibilityWithLatestRelease().value | ||
* ) | ||
* }}} | ||
* | ||
* sbt-release uses the `releaseVersion` function to set the version before publishing a release (at step | ||
* `setReleaseVersion`). It reads the current `version` (usually defined in a file `version.sbt`, and looking | ||
* like `"1.2.3-SNAPSHOT"`), and applies the function to it. | ||
* | ||
* @param qualifier Optional qualifier to append to the version (e.g. `"-RC1"`). By default, it tries to read | ||
* it from the environment variable VERSION_POLICY_RELEASE_QUALIFIER. | ||
*/ | ||
def fromAggregatedAssessedCompatibilityWithLatestRelease( | ||
qualifier: String = sys.env.getOrElse(qualifierVariableName, "") | ||
): Def.Initialize[Task[String => String]] = | ||
fromAssessedCompatibility(qualifier)(aggregatedAssessedCompatibilityWithLatestRelease) | ||
|
||
} |
77 changes: 77 additions & 0 deletions
77
sbt-version-policy/src/main/scala/sbtversionpolicy/withsbtrelease/Version.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package sbtversionpolicy.withsbtrelease | ||
|
||
// All the code below has been copied from https://github.com/sbt/sbt-release/blob/master/src/main/scala/Version.scala | ||
|
||
import util.control.Exception._ | ||
|
||
private[withsbtrelease] object Version { | ||
sealed trait Bump { | ||
def bump: Version => Version | ||
} | ||
|
||
object Bump { | ||
case object Major extends Bump { def bump = _.bumpMajor } | ||
case object Minor extends Bump { def bump = _.bumpMinor } | ||
case object Bugfix extends Bump { def bump = _.bumpBugfix } | ||
case object Nano extends Bump { def bump = _.bumpNano } | ||
case object Next extends Bump { def bump = _.bump } | ||
|
||
val default = Next | ||
} | ||
|
||
val VersionR = """([0-9]+)((?:\.[0-9]+)+)?([\.\-0-9a-zA-Z]*)?""".r | ||
val PreReleaseQualifierR = """[\.-](?i:rc|m|alpha|beta)[\.-]?[0-9]*""".r | ||
|
||
def apply(s: String): Option[Version] = { | ||
allCatch opt { | ||
val VersionR(maj, subs, qual) = s | ||
// parse the subversions (if any) to a Seq[Int] | ||
val subSeq: Seq[Int] = Option(subs) map { str => | ||
// split on . and remove empty strings | ||
str.split('.').filterNot(_.trim.isEmpty).map(_.toInt).toSeq | ||
} getOrElse Nil | ||
Version(maj.toInt, subSeq, Option(qual).filterNot(_.isEmpty)) | ||
} | ||
} | ||
|
||
def formatError(version: String) = sys.error(s"Version [$version] format is not compatible with " + Version.VersionR.pattern.toString) | ||
} | ||
|
||
private[withsbtrelease] case class Version(major: Int, subversions: Seq[Int], qualifier: Option[String]) { | ||
def bump = { | ||
val maybeBumpedPrerelease = qualifier.collect { | ||
case Version.PreReleaseQualifierR() => withoutQualifier | ||
} | ||
def maybeBumpedLastSubversion = bumpSubversionOpt(subversions.length-1) | ||
def bumpedMajor = copy(major = major + 1) | ||
|
||
maybeBumpedPrerelease | ||
.orElse(maybeBumpedLastSubversion) | ||
.getOrElse(bumpedMajor) | ||
} | ||
|
||
def bumpMajor = copy(major = major + 1, subversions = Seq.fill(subversions.length)(0)) | ||
def bumpMinor = maybeBumpSubversion(0) | ||
def bumpBugfix = maybeBumpSubversion(1) | ||
def bumpNano = maybeBumpSubversion(2) | ||
|
||
def maybeBumpSubversion(idx: Int) = bumpSubversionOpt(idx) getOrElse this | ||
|
||
private def bumpSubversionOpt(idx: Int) = { | ||
val bumped = subversions.drop(idx) | ||
val reset = bumped.drop(1).length | ||
bumped.headOption map { head => | ||
val patch = (head+1) +: Seq.fill(reset)(0) | ||
copy(subversions = subversions.patch(idx, patch, patch.length)) | ||
} | ||
} | ||
|
||
def bump(bumpType: Version.Bump): Version = bumpType.bump(this) | ||
|
||
def withoutQualifier = copy(qualifier = None) | ||
def asSnapshot = copy(qualifier = Some("-SNAPSHOT")) | ||
|
||
def string = "" + major + mkString(subversions) + qualifier.getOrElse("") | ||
|
||
private def mkString(parts: Seq[Int]) = parts.map("."+_).mkString | ||
} |
3 changes: 3 additions & 0 deletions
3
...rsion-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
target/ | ||
.bsp/ | ||
global/ |
56 changes: 56 additions & 0 deletions
56
...ersion-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/build.sbt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import sbtversionpolicy.withsbtrelease.ReleaseVersion | ||
import sbtrelease._ | ||
import ReleaseTransformations._ | ||
|
||
inThisBuild(List( | ||
organization := "org.organization", | ||
homepage := Some(url("https://github.com/organization/project")), | ||
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), | ||
developers := List( | ||
Developer( | ||
"julienrf", | ||
"Julien Richard-Foy", | ||
"[email protected]", | ||
url("https://github.com/julienrf") | ||
) | ||
), | ||
scalaVersion := "3.0.1" | ||
)) | ||
|
||
val module = | ||
project | ||
.settings( | ||
name := "sbt-release-unconstrained-test" | ||
) | ||
|
||
val root = project.in(file(".")) | ||
.aggregate(module) | ||
.settings( | ||
// Tell sbt-release to set the release version based on the level of compatibility with the previous release | ||
releaseVersion := ReleaseVersion.fromAggregatedAssessedCompatibilityWithLatestRelease().value, | ||
// Custom release process for testing purpose only: the artifacts are locally published, | ||
// and we don’t push to the remote repository. | ||
releaseProcess := Seq[ReleaseStep]( | ||
checkSnapshotDependencies, | ||
inquireVersions, | ||
runClean, | ||
runTest, | ||
setReleaseVersion, | ||
commitReleaseVersion, | ||
tagRelease, | ||
releaseStepTask(module / publishLocal), // Publish locally for our tests only, in practice you will publish to Sonatype | ||
setNextVersion, | ||
commitNextVersion, | ||
// pushChanges // Disable pushing the changes to the remote repository for our tests only | ||
) | ||
) | ||
|
||
TaskKey[Unit]("checkTag_1_0_0") := { | ||
import scala.sys.process._ | ||
assert("git describe --tags".lineStream.exists(_.contains("v1.0.0"))) | ||
} | ||
|
||
TaskKey[Unit]("checkTag_1_1_0") := { | ||
import scala.sys.process._ | ||
assert("git describe --tags".lineStream.exists(_.contains("v1.1.0"))) | ||
} |
3 changes: 3 additions & 0 deletions
3
...cy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/file-to-add.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package org.organization.module | ||
|
||
class NewAPI |
3 changes: 3 additions & 0 deletions
3
...ample-sbt-release-unconstrained/module/src/main/scala/org/organization/module/Dummy.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package org.organization.module | ||
|
||
class Dummy |
2 changes: 2 additions & 0 deletions
2
...icy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/project/plugins.sbt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % sys.props("plugin.version")) | ||
addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") |
23 changes: 23 additions & 0 deletions
23
sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Setup repository | ||
$ exec git init | ||
$ exec git config user.email "email" | ||
$ exec git config user.name "name" | ||
|
||
$ exec git add . | ||
$ exec git commit -m "Initial commit" | ||
> reload | ||
|
||
# First release | ||
> release with-defaults | ||
> checkTag_1_0_0 | ||
> reload | ||
|
||
# New contributions | ||
$ copy-file file-to-add.template module/src/main/scala/org/organization/module/NewAPI.scala | ||
$ exec git add module/src/main/scala/org/organization/module/NewAPI.scala | ||
$ exec git commit -m "Some hard work" | ||
|
||
# Second release | ||
> release with-defaults | ||
# Check that sbt-version-policy bumped the minor version | ||
> checkTag_1_1_0 |
1 change: 1 addition & 0 deletions
1
...sion-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/version.sbt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ThisBuild / version := "1.0.0-SNAPSHOT" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah clever, I like it! In order to work out the new version number, if there's no previously released version to compare compatibility with, we just read the current snapshot version specified in
version.sbt
(eg "1.0.0-SNAPSHOT") and use it without the qualifier (eg "1.0.0") 👍