From 985ace1acd526a54ca0b2f63fd493e4cc4e16dcb Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 30 Nov 2023 19:05:07 +0100 Subject: [PATCH] A couple of improvements - Fallback to the default releaseVersion behavior for the first release - Read the version qualifier from an environment variable --- README.md | 10 ++- .../withsbtrelease/ReleaseVersion.scala | 64 +++++++++++++------ .../build.sbt | 2 + .../example-sbt-release-unconstrained/test | 9 +-- .../version.sbt | 2 +- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 9cd7df7..5694d20 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,9 @@ In this mode, you can use sbt-version-policy to check that incoming pull request import sbtversionpolicy.withsbtrelease.ReleaseVersion releaseVersion := ReleaseVersion.fromCompatibility(versionPolicyIntention.value) ~~~ + The `releaseVersion` function bumps the release version according to the compatibility guarantees defined + by `versionPolicyIntention`. Optionally, you can also define a _qualifier_ to append to the release version + by setting the environment variable `VERSION_POLICY_RELEASE_QUALIFIER` (e.g., `VERSION_POLICY_RELEASE_QUALIFIER=RC1`). 2. Reset `versionPolicyIntention` to `Compatibility.BinaryAndSourceCompatible` after every release. This can be achieved by managing the setting `versionPolicyIntention` in a separate file (like [sbt-release] manages the setting `version` in a separate file, by default), and by adding a step that overwrites the content of that file and commits it. @@ -245,8 +248,13 @@ In this mode, you can use sbt-version-policy to assess the incompatibilities int ReleaseVersion.fromAggregatedAssessedCompatibilityWithLatestRelease().value } ~~~ + In both cases, the `releaseVersion` function sets the release version according to the compatibility level + with the latest release. Optionally, you can also define a _qualifier_ to append to the release version + by setting the environment variable `VERSION_POLICY_RELEASE_QUALIFIER` (e.g., `VERSION_POLICY_RELEASE_QUALIFIER="-RC1"`). -Note that this mode can be enabled only _after_ the first release of the project has already been published. +Note that for the first release you have to set the release version yourself via the file `version.sbt` (e.g., set +`1.0.0-SNAPSHOT` or `0.1.0-SNAPSHOT`). This is because `sbt-version-policy` needs a previous release to exist to be +able to assess the compatibility level of the current state of the project with that release. ##### Example diff --git a/sbt-version-policy/src/main/scala/sbtversionpolicy/withsbtrelease/ReleaseVersion.scala b/sbt-version-policy/src/main/scala/sbtversionpolicy/withsbtrelease/ReleaseVersion.scala index 8181e2e..6ac01b6 100644 --- a/sbt-version-policy/src/main/scala/sbtversionpolicy/withsbtrelease/ReleaseVersion.scala +++ b/sbt-version-policy/src/main/scala/sbtversionpolicy/withsbtrelease/ReleaseVersion.scala @@ -4,17 +4,21 @@ 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"`). Empty by default. + * @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 = ""): String => String = { + def fromCompatibility(compatibility: Compatibility, qualifier: String = sys.env.getOrElse(qualifierVariableName, "")): String => String = { val maybeBump = compatibility match { case Compatibility.None => Some(Version.Bump.Major) @@ -31,10 +35,29 @@ object ReleaseVersion { case Some(bump) => versionWithoutQualifier.bump(bump) case None => versionWithoutQualifier }).string - s"${bumpedVersion}${qualifier}" + 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. @@ -44,26 +67,27 @@ object ReleaseVersion { * {{{ * import sbtversionpolicy.withsbtrelease.ReleaseVersion * - * releaseVersion := ReleaseVersion.fromAssessedCompatibilityWithLatestRelease.value + * 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"`). Empty by default. + * @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 = ""): Def.Initialize[Task[String => String]] = - Def.task { + def fromAssessedCompatibilityWithLatestRelease( + qualifier: String = sys.env.getOrElse(qualifierVariableName, "") + ): Def.Initialize[Task[String => String]] = + fromAssessedCompatibility(qualifier)(Def.task { val compatibilityResults = versionPolicyAssessCompatibility.value - val log = Keys.streams.value.log val compatibilityWithLatestRelease = compatibilityResults.headOption - .getOrElse(throw new MessageOnlyException("Unable to assess the compatibility level of this project. Is 'versionPolicyPreviousVersions' defined?")) + .getOrElse(throw new MessageOnlyException("Unable to assess the compatibility level of this project.")) val (_, compatibility) = compatibilityWithLatestRelease - log.debug(s"Compatibility level is ${compatibility}") - fromCompatibility(compatibility, qualifier) - } + compatibility + }) /** * Task returning a [release version function](https://github.com/sbt/sbt-release?tab=readme-ov-file#custom-versioning) @@ -80,20 +104,20 @@ object ReleaseVersion { * .in(file(".")) * .aggregate(mySubproject1, mySubproject2) * .settings( - * releaseVersion := ReleaseVersion.fromAggregatedAssessedCompatibilityWithLatestRelease.value + * 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 = ""): Def.Initialize[Task[String => String]] = - Def.task { - val log = Keys.streams.value.log - val compatibility = aggregatedAssessedCompatibilityWithLatestRelease.value - log.debug(s"Aggregated compatibility level is ${compatibility}") - fromCompatibility(compatibility, qualifier) - } + def fromAggregatedAssessedCompatibilityWithLatestRelease( + qualifier: String = sys.env.getOrElse(qualifierVariableName, "") + ): Def.Initialize[Task[String => String]] = + fromAssessedCompatibility(qualifier)(aggregatedAssessedCompatibilityWithLatestRelease) } diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/build.sbt b/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/build.sbt index 249b396..8479228 100644 --- a/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/build.sbt +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/build.sbt @@ -26,6 +26,8 @@ val module = 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]( diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/test b/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/test index 07b9299..f787c39 100644 --- a/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/test +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/test @@ -8,7 +8,7 @@ $ exec git commit -m "Initial commit" > reload # First release -> release with-defaults release-version 1.0.0 +> release with-defaults > checkTag_1_0_0 > reload @@ -17,13 +17,6 @@ $ copy-file file-to-add.template module/src/main/scala/org/organization/module/N $ exec git add module/src/main/scala/org/organization/module/NewAPI.scala $ exec git commit -m "Some hard work" -# Configure releaseVersion to bump the patch, minor, or major version number according -# to the assessed compatibility level. -# In practice, you would assign the releaseVersion in your build.sbt. -# Here, we show how to optionally add a qualifier to the release version (e.g. `"-RC1"`) -# via an environment variable RELEASE_VERSION_QUALIFIER -> set releaseVersion := sbtversionpolicy.withsbtrelease.ReleaseVersion.fromAggregatedAssessedCompatibilityWithLatestRelease(qualifier = sys.env.getOrElse("RELEASE_VERSION_QUALIFIER", "")).value - # Second release > release with-defaults # Check that sbt-version-policy bumped the minor version diff --git a/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/version.sbt b/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/version.sbt index a3a28d6..a5f65fc 100644 --- a/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/version.sbt +++ b/sbt-version-policy/src/sbt-test/sbt-version-policy/example-sbt-release-unconstrained/version.sbt @@ -1 +1 @@ -ThisBuild / version := "0.0.1-SNAPSHOT" +ThisBuild / version := "1.0.0-SNAPSHOT"