From 3c7156a01baa36e16bd56b8f19c4255716305a15 Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Sun, 19 Jul 2026 22:41:31 -0500 Subject: [PATCH] Migrate the plugin to support SBT 2. --- README.md | 3 +- build.sbt | 65 +++++------ project/build.properties | 2 +- src/main/scala/gs/BuildType.scala | 43 ++++++++ src/main/scala/gs/Git.scala | 10 +- .../scala/gs/InvalidSemVerException.scala | 8 -- src/main/scala/gs/PluginProperties.scala | 65 ----------- src/main/scala/gs/ReleaseType.scala | 50 ++++++--- src/main/scala/gs/SemVer.scala | 61 +++++++++-- src/main/scala/gs/SemVerCalculator.scala | 19 ++++ src/main/scala/gs/SemVerKeys.scala | 38 ++----- src/main/scala/gs/SemVerPlugin.scala | 103 +++++++++--------- src/main/scala/gs/SemVerProps.scala | 20 ++++ src/main/scala/gs/SetVersion.scala | 103 ++++++++++++++++++ 14 files changed, 366 insertions(+), 224 deletions(-) create mode 100644 src/main/scala/gs/BuildType.scala delete mode 100644 src/main/scala/gs/InvalidSemVerException.scala delete mode 100644 src/main/scala/gs/PluginProperties.scala create mode 100644 src/main/scala/gs/SemVerCalculator.scala create mode 100644 src/main/scala/gs/SemVerProps.scala create mode 100644 src/main/scala/gs/SetVersion.scala diff --git a/README.md b/README.md index 6fa2136..78b45a1 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ values for `release` are: - `patch` ``` -sbt -Drelease=minor publish +sbt setVersion minor release +sbt publish ``` ## Supported Setting Keys diff --git a/build.sbt b/build.sbt index 8a4e692..8869e18 100644 --- a/build.sbt +++ b/build.sbt @@ -1,39 +1,21 @@ +ThisBuild / scalaVersion := "3.8.4" ThisBuild / organizationName := "garrity software" ThisBuild / organization := "gs" ThisBuild / versionScheme := Some("semver-spec") -externalResolvers := Seq( - "Garrity Software Releases" at "https://maven.garrity.co/gs", - "Garrity Software Maven Mirror" at "https://maven.garrity.co/releases" -) +/* externalResolvers := Seq( "Garrity Software Releases" at + * "https://maven.garrity.co/gs", "Garrity Software Maven Mirror" at + * "https://maven.garrity.co/releases" ) */ + +resolvers += Resolver.mavenLocal val ProjectName: String = "sbt-gs-semver" -val Description: String = "SBT 1.9.0+ plugin for Git-based semantic versioning." +val Description: String = "SBT 2.0.0+ plugin for Git-based semantic versioning." -def getProperty[A]( - name: String, - conv: String => A -): Option[A] = - Option(System.getProperty(name)).map(conv) +val CurrentVersion: String = "0.4.0-SNAPSHOT" -val VersionProperty: String = "version" -val ReleaseProperty: String = "release" - -lazy val InputVersion: Option[String] = - getProperty(VersionProperty, identity) - -lazy val IsRelease: Boolean = - getProperty(ReleaseProperty, _.toBoolean).getOrElse(false) - -lazy val Modifier: String = - if (IsRelease) "" else "-SNAPSHOT" - -val DefaultVersion: String = "0.1.0-SNAPSHOT" - -lazy val SelectedVersion: String = - InputVersion - .map(v => s"$v$Modifier") - .getOrElse(DefaultVersion) +def isRelease(): Boolean = + !CurrentVersion.contains("SNAPSHOT") lazy val publishSettings = Seq( publishMavenStyle := true, @@ -47,14 +29,16 @@ lazy val publishSettings = Seq( ), description := Description, licenses := List( - "MIT" -> url("https://garrity.co/MIT.html") + "MIT" -> url( + s"https://git.garrity.co/garrity-software/$ProjectName/LICENSE" + ) ), homepage := Some( url(s"https://git.garrity.co/garrity-software/$ProjectName") ), publishTo := { val repo = "https://maven.garrity.co/" - if (!IsRelease) + if (!isRelease()) Some("Garrity Software Maven" at repo + "snapshots") else Some("Garrity Software Maven" at repo + "gs") @@ -66,21 +50,26 @@ lazy val root = Project(ProjectName, file(".")) .settings(publishSettings) .settings( name := ProjectName, - version := SelectedVersion, + version := CurrentVersion, libraryDependencies ++= Seq( "com.lihaoyi" %% "os-lib" % "0.9.3" ), pluginCrossBuild / sbtVersion := { - scalaBinaryVersion.value match { - // Minimum SBT version compatible with this plugin. - case "2.12" => "1.9.0" - } + "2.0.0" }, scalacOptions := Seq( - "-unchecked", + "-encoding", + "utf8", "-deprecation", "-feature", - "-encoding", - "utf8" + "-explain", + "-unchecked", + "-explain-types", + "-language:strictEquality", + "-Wunused:implicits", + "-Wunused:explicits", + "-Wunused:imports", + "-Wunused:locals", + "-Wunused:privates" ) ) diff --git a/project/build.properties b/project/build.properties index 04267b1..3b9dfab 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.9 +sbt.version=2.0.3 diff --git a/src/main/scala/gs/BuildType.scala b/src/main/scala/gs/BuildType.scala new file mode 100644 index 0000000..baf4621 --- /dev/null +++ b/src/main/scala/gs/BuildType.scala @@ -0,0 +1,43 @@ +package gs + +/** Defines build behavior. + * + * - `snapshot`: Produce a snapshot, appends `-SNAPSHOT` to the version. + * - `release`: Produce a release build. No version string augmentations. + * + * @param name + * The formal name of the build type. + */ +sealed abstract class BuildType(val name: String): + + override def equals(obj: Any): Boolean = + obj match + case other: BuildType => name == other.name + case _ => false + + override def hashCode(): Int = name.hashCode() + + override def toString(): String = name + +object BuildType: + given CanEqual[BuildType, BuildType] = CanEqual.derived + + case object Release extends BuildType("release") + case object Snapshot extends BuildType("snapshot") + + def parse(candidate: String): Option[BuildType] = + candidate.trim().toLowerCase() match { + case Release.name => Some(Release) + case Snapshot.name => Some(Snapshot) + case _ => None + } + + def parse( + candidate: String, + defaultValue: BuildType + ): BuildType = + parse(candidate).getOrElse(defaultValue) + + def isValid(candidate: String): Boolean = + candidate.equalsIgnoreCase(Release.name) + || candidate.equalsIgnoreCase(Snapshot.name) diff --git a/src/main/scala/gs/Git.scala b/src/main/scala/gs/Git.scala index 1aa22a1..7f22ac1 100644 --- a/src/main/scala/gs/Git.scala +++ b/src/main/scala/gs/Git.scala @@ -1,9 +1,6 @@ package gs -import java.io.File -import scala.util.Try - -object Git { +object Git: /** Get the latest tag on this repository and attempt to parse it as a * [[SemVer]]. This value is retrieved using the `git` command. If for some @@ -16,7 +13,7 @@ object Git { * @return * The latest semantic version according to the current Git repository. */ - def getLatestSemVer(): SemVer = { + def getLatestSemVer(): SemVer = // Suppress all error output. Suppress exceptions for the failed process. // Capture the standard output and parse it if the command succeeds. val result = os @@ -38,6 +35,3 @@ object Git { val processOutput = result.out.text().trim() SemVer.parse(processOutput).getOrElse(SemVer.DefaultVersion) } - } - -} diff --git a/src/main/scala/gs/InvalidSemVerException.scala b/src/main/scala/gs/InvalidSemVerException.scala deleted file mode 100644 index 7e792ae..0000000 --- a/src/main/scala/gs/InvalidSemVerException.scala +++ /dev/null @@ -1,8 +0,0 @@ -package gs - -case class InvalidSemVerException(candidate: String) extends RuntimeException { - - override def getMessage(): String = - s"The candidate SemVer '$candidate' is not valid." - -} diff --git a/src/main/scala/gs/PluginProperties.scala b/src/main/scala/gs/PluginProperties.scala deleted file mode 100644 index f00bed2..0000000 --- a/src/main/scala/gs/PluginProperties.scala +++ /dev/null @@ -1,65 +0,0 @@ -package gs - -import sbt._ - -/** Helper for managing all SBT command line properties (`-Dproperty=value`). - */ -object PluginProperties { - - /** Helper to extract the value from `-Dproperty=value`. - * - * @param name - * The property name. - * @param conv - * The conversion function to the output type. - * @return - * The converted value, or `None` if no value exists. - */ - def getProperty[A]( - name: String, - conv: String => A - ): Option[A] = - Option(System.getProperty(name)).map(conv) - - /** Use one of the following to calculate an incremented release version: - * - * - `sbt -Drelease=major` - * - `sbt -Drelease=minor` - * - `sbt -Drelease=patch` - */ - val ReleaseProperty: String = "release" - - /** If true, or no release type is set, this build is treated as a - * pre-release, and the suffix `-SNAPSHOT` is appended to the version. - */ - val SnapshotProperty: String = "snapshot" - - /** The value of `-Drelease=`, parsed and validated. Influences the - * final calculated [[SemVer]]. - * - * @return - * The version passed as input to SBT. - */ - def getReleaseType(): Option[ReleaseType] = - getProperty( - ReleaseProperty, - raw => - ReleaseType.parse(raw) match { - case scala.util.Success(value) => value - case scala.util.Failure(e) => throw e - } - ) - - /** The value of `-Dsnapshot=`, parsed and validated. If parsing - * fails or no value is present, `false` is returned. Influences whether - * the pre-release suffix (`-SNAPSHOT`) is appended to the version. - * - * @return - * `true` if `-Dsnapshot=true` is specified at the command line, `false` - * otherwise. - */ - def isSnapshot(): Boolean = - getProperty(SnapshotProperty, raw => raw.trim().equalsIgnoreCase("true")) - .getOrElse(false) - -} diff --git a/src/main/scala/gs/ReleaseType.scala b/src/main/scala/gs/ReleaseType.scala index 5573b08..18ef8e0 100644 --- a/src/main/scala/gs/ReleaseType.scala +++ b/src/main/scala/gs/ReleaseType.scala @@ -1,25 +1,47 @@ package gs -import scala.util.Failure -import scala.util.Success -import scala.util.Try +/** Defines versioning behavior. + * + * - `major`: Increment the major version. `1.2.3 => 2.0.0` + * - `minor`: Increment the minor version. `1.2.3 => 1.3.0` + * - `patch`: Increment the patch version. `1.2.3 => 1.2.4` + * + * @param name + * The formal name of the release type. + */ +sealed abstract class ReleaseType(val name: String): -sealed abstract class ReleaseType(val name: String) + override def equals(obj: Any): Boolean = + obj match + case other: ReleaseType => name == other.name + case _ => false + + override def hashCode(): Int = name.hashCode() + + override def toString(): String = name + +object ReleaseType: + given CanEqual[ReleaseType, ReleaseType] = CanEqual.derived -object ReleaseType { case object Major extends ReleaseType("major") case object Minor extends ReleaseType("minor") case object Patch extends ReleaseType("patch") - def parse(candidate: String): Try[ReleaseType] = + def parse(candidate: String): Option[ReleaseType] = candidate.trim().toLowerCase() match { - case Major.name => Success(Major) - case Minor.name => Success(Minor) - case Patch.name => Success(Patch) - case _ => - Failure( - new IllegalArgumentException(s"Invalid release type: '$candidate'") - ) + case Major.name => Some(Major) + case Minor.name => Some(Minor) + case Patch.name => Some(Patch) + case _ => None } -} + def parse( + candidate: String, + defaultValue: ReleaseType + ): ReleaseType = + parse(candidate).getOrElse(defaultValue) + + def isValid(candidate: String): Boolean = + candidate.equalsIgnoreCase(Major.name) + || candidate.equalsIgnoreCase(Minor.name) + || candidate.equalsIgnoreCase(Patch.name) diff --git a/src/main/scala/gs/SemVer.scala b/src/main/scala/gs/SemVer.scala index ae3a87b..0133600 100644 --- a/src/main/scala/gs/SemVer.scala +++ b/src/main/scala/gs/SemVer.scala @@ -1,6 +1,8 @@ package gs import scala.util.matching.Regex +import sjsonnew.* +import sjsonnew.BasicJsonProtocol.* /** Simplified representation of SemVer. Only supports major, minor and patch * versions. Not intended to be used as a generalized SemVer implementation. @@ -35,10 +37,10 @@ case class SemVer( * @return * The string rendition of this version. */ - def render(isSnapshot: Boolean): String = { - val version = toString() - if (isSnapshot) version + "-SNAPSHOT" else version - } + def render(buildType: BuildType): String = + buildType match + case BuildType.Release => s"$major.$minor.$patch" + case BuildType.Snapshot => s"$major.$minor.$patch-SNAPSHOT" /** @return * Copy of this SemVer with the major version incremented by 1. @@ -63,18 +65,58 @@ case class SemVer( * Copy of this SemVer with the patch version incremented by 1. */ def incrementPatch(): SemVer = copy(patch = this.patch + 1) + + def applyReleaseType(releaseType: ReleaseType): SemVer = + releaseType match + case ReleaseType.Major => incrementMajor() + case ReleaseType.Minor => incrementMinor() + case ReleaseType.Patch => incrementPatch() + } -object SemVer { - - /** Version 0.1.0 +object SemVer: + /** Version 0.1.0 -- the default version. */ val DefaultVersion: SemVer = SemVer(0, 1, 0) + given CanEqual[SemVer, SemVer] = CanEqual.derived + + // Required for SBT serialization. + given JsonFormat[SemVer] = new JsonFormat[SemVer] { + + override def write[J]( + obj: SemVer, + builder: Builder[J] + ): Unit = + builder.beginObject() + builder.addField("major", obj.major) + builder.addField("minor", obj.minor) + builder.addField("patch", obj.patch) + builder.endObject() + + override def read[J]( + jsOpt: Option[J], + unbuilder: Unbuilder[J] + ): SemVer = + jsOpt match + case Some(js) => + unbuilder.beginObject(js) + val major = unbuilder.readField[Int]("major") + val minor = unbuilder.readField[Int]("minor") + val patch = unbuilder.readField[Int]("patch") + unbuilder.endObject() + SemVer(major, minor, patch) + case None => + deserializationError("Expected JsObject but found None") + + } + private val SemVerPattern: Regex = "^(0|(?:[1-9][0-9]*))\\.(0|(?:[1-9][0-9]*))\\.(0|(?:[1-9][0-9]*))$".r /** Attempt to parse the input string as a [[SemVer]]. + * + * This does not handle or produce snapshot versions. * * @param candidate * The candidate string to parse. @@ -82,11 +124,8 @@ object SemVer { * The parsed [[SemVer]], or `None` if parsing failed. */ def parse(candidate: String): Option[SemVer] = - candidate match { + candidate match case SemVerPattern(major, minor, patch) => Some(SemVer(major.toInt, minor.toInt, patch.toInt)) case _ => None - } - -} diff --git a/src/main/scala/gs/SemVerCalculator.scala b/src/main/scala/gs/SemVerCalculator.scala new file mode 100644 index 0000000..001cc8b --- /dev/null +++ b/src/main/scala/gs/SemVerCalculator.scala @@ -0,0 +1,19 @@ +package gs + +object SemVerCalculator: + + def update( + baseVersion: SemVer, + releaseType: ReleaseType, + buildType: BuildType + ): (SemVer, String) = + val updated = baseVersion.applyReleaseType(releaseType) + updated -> updated.render(buildType) + + def updateGit( + releaseType: ReleaseType, + buildType: BuildType + ): (SemVer, String) = + update(Git.getLatestSemVer(), releaseType, buildType) + +end SemVerCalculator diff --git a/src/main/scala/gs/SemVerKeys.scala b/src/main/scala/gs/SemVerKeys.scala index f2d52a6..5890153 100644 --- a/src/main/scala/gs/SemVerKeys.scala +++ b/src/main/scala/gs/SemVerKeys.scala @@ -1,10 +1,17 @@ package gs -import sbt._ +import sbt.* /** Defines all setting and task keys for the GS SemVer SBT Plugin. */ -object SemVerKeys { +object SemVerKeys: + + /** SBT Setting for the calculated SemVer string, to be used for all project + * versions. + */ + lazy val semVer = settingKey[String]( + "Rendered version calculated by the SemVer plugin." + ) /** SBT Setting for the latest known project version (before this build). * @@ -19,30 +26,9 @@ object SemVerKeys { /** SBT Setting for the **selected** project version. * - * Use this value to set your project version: - * - * {{{ - * version := semVerSelected.value - * }}} - * - * This value should be used as the version for all project artifacts. It is - * automatically derived based on the `semVerCurrent` setting, the value of - * the `release` property, and the value of the `snapshot` property: - * - * Consider the example current version of `1.2.3`: - * - * - `-Drelease=major`: `semVerSelected = 2.0.0` - * - `-Drelease=major`, `-Dsnapshot=true`: `semVerSelected = - * 2.0.0-SNAPSHOT` - * - `-Drelease=minor`: `semVerSelected = 1.3.0` - * - `-Drelease=minor`, `-Dsnapshot=true`: `semVerSelected = - * 1.3.0-SNAPSHOT` - * - `-Drelease=patch`: `semVerSelected = 1.2.4` - * - `-Drelease=patch`, `-Dsnapshot=true`: `semVerSelected = - * 1.2.4-SNAPSHOT` - * - `release` not set: `semVerSelected = 1.2.4-SNAPSHOT` + * Use the `semVer` setting to assign project versions. */ - lazy val semVerSelected = settingKey[String]( + lazy val semVerSelected = settingKey[SemVer]( "Version selected for the current build. Depends on the release type and current version." ) @@ -79,5 +65,3 @@ object SemVerKeys { lazy val semVerWriteVersionToFile = taskKey[Unit]( "Write the selected SemVer to a file." ) - -} diff --git a/src/main/scala/gs/SemVerPlugin.scala b/src/main/scala/gs/SemVerPlugin.scala index 93b3310..bfebc09 100644 --- a/src/main/scala/gs/SemVerPlugin.scala +++ b/src/main/scala/gs/SemVerPlugin.scala @@ -3,76 +3,77 @@ package gs import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Paths -import sbt._ +import sbt.* +import sbt.Keys.* -object SemVerPlugin extends AutoPlugin { +object SemVerPlugin extends AutoPlugin: override def trigger = allRequirements val autoImport = SemVerKeys - import autoImport._ + import autoImport.* - val DefaultOutputFile: String = ".version" + object Defaults: + /** The version is persisted to this file when the + * `semVerWriteVersionToFile` task is invoked. + */ + val VersionOutputFile: String = ".version" + end Defaults - // Perform all version calculations and expose as a variable. - lazy val semVerDefaults: Seq[Setting[_]] = { - // The latest semantic version relevant to this project, calculated by - // looking at the latest Git tag. - val latestSemVer = - Git.getLatestSemVer() + /** Default settings used to initialize projects. + * + * Calculates the default semantic version based on the repository state (git + * tags) and configured release type and build type. + */ + lazy val semVerDefaults: Seq[Setting[?]] = { + val latestSemVer = Git.getLatestSemVer() + val releaseType = SemVerProps.getReleaseType() + val buildType = SemVerProps.getBuildType() + val calculated = SemVerCalculator.update( + baseVersion = latestSemVer, + releaseType = releaseType, + buildType = buildType + ) - // The selected release type (if any), determined by looking at the SBT - // command line and checking if `-Drelease=` was specified. - val releaseType = - PluginProperties.getReleaseType() - - // See whether this is a pre-release build or not. If it is (if this value - // is true), the pre-release suffix `-SNAPSHOT` will be appended to the - // calculated version. - val isSnapshot = - PluginProperties.isSnapshot() || releaseType.isEmpty - - // Calculate the appropriate semantic version for this build of the project. - val selectedSemVer = releaseType match { - case Some(ReleaseType.Major) => latestSemVer.incrementMajor() - case Some(ReleaseType.Minor) => latestSemVer.incrementMinor() - case Some(ReleaseType.Patch) => latestSemVer.incrementPatch() - case None => - // Note: this doesn't play nicely with repositories that have - // pre-release versions before 0.1.0. - if (latestSemVer == SemVer.DefaultVersion) - latestSemVer - else - latestSemVer.incrementPatch() - } - - // Expose the relevant values as setting keys. Seq( + commands += SetVersion.command, + semVer := calculated._2, semVerCurrent := latestSemVer.toString(), - semVerSelected := selectedSemVer.render(isSnapshot), - semVerMajor := selectedSemVer.major, - semVerSnapshot := isSnapshot, - semVerOutputFile := Some(DefaultOutputFile) + semVerSelected := calculated._1, + semVerSnapshot := buildType == BuildType.Snapshot, + semVerMajor := calculated._1.major, + semVerOutputFile := Some(Defaults.VersionOutputFile), + version := calculated._2 ) } - // Automatically exposed globally. - override lazy val globalSettings: Seq[Setting[_]] = semVerDefaults + /** @inheritDocs + */ + override lazy val globalSettings: Seq[Setting[?]] = semVerDefaults - // Add the custom task. - override lazy val buildSettings: Seq[Setting[_]] = Seq( - semVerInfo := { - val log = Keys.streams.value.log + /** @inheritDocs + */ + override lazy val buildSettings: Seq[Setting[?]] = Seq( + // Display information about the build. + // Do not cache this task, so that the updated version is reported each + // time it is called. + semVerInfo := Def.uncached { + val log = streams.value.log log.info(s"[SemVer] Current: ${semVerCurrent.value}") log.info(s"[SemVer] Selected: ${semVerSelected.value}") + log.info(s"[SemVer] Snapshot: ${semVerSnapshot.value}") + log.info(s"[SemVer] Final String: ${semVer.value}") + log.info(s"[SemVer] Project Version: ${version.value}") }, - semVerWriteVersionToFile := { - val outputFile = semVerOutputFile.value.getOrElse(DefaultOutputFile) + // Dump the build information to file. + // Do not cache this task, so that it can run multiple times within the same + // session. + semVerWriteVersionToFile := Def.uncached { + val outputFile = + semVerOutputFile.value.getOrElse(Defaults.VersionOutputFile) Files.write( Paths.get(outputFile), - semVerSelected.value.getBytes(StandardCharsets.UTF_8) + semVerSelected.value.toString().getBytes(StandardCharsets.UTF_8) ) } ) - -} diff --git a/src/main/scala/gs/SemVerProps.scala b/src/main/scala/gs/SemVerProps.scala new file mode 100644 index 0000000..284cdc3 --- /dev/null +++ b/src/main/scala/gs/SemVerProps.scala @@ -0,0 +1,20 @@ +package gs + +object SemVerProps: + + val SemVerReleaseType: String = "semver.release_type" + val SemVerBuildType: String = "semver.build_type" + + def getReleaseType(): ReleaseType = + sys.props + .get(SemVerReleaseType) + .flatMap(ReleaseType.parse) + .getOrElse(ReleaseType.Patch) + + def getBuildType(): BuildType = + sys.props + .get(SemVerBuildType) + .flatMap(BuildType.parse) + .getOrElse(BuildType.Snapshot) + +end SemVerProps diff --git a/src/main/scala/gs/SetVersion.scala b/src/main/scala/gs/SetVersion.scala new file mode 100644 index 0000000..0afc26f --- /dev/null +++ b/src/main/scala/gs/SetVersion.scala @@ -0,0 +1,103 @@ +package gs + +import sbt.* +import sbt.Keys.* + +/** Custom command used to update semantic versions for the project. + * Recalculates versions using the input [[ReleaseType]] and [[BuildType]]. + */ +object SetVersion: + + /** The unique name of the command. Used to invoke the command using SBT. + * + * For example: `sbt:project-name> setVersion patch snapshot` + */ + val commandName: String = "setVersion" + + /** CLI help documentation for this command. + */ + val detailedHelpString: String = + """|setVersion [release-type] [build-type] + | + | Reconfigures the build version using SemVer. Based on the latest + | version as detected from Git tags. If no Git tags exist, the default + | version 0.1.0 is used as a baseline. + | + | - [release-type] one of major|minor|patch (default=patch) + | + major: 1.2.3 becomes 2.0.0 + | + minor: 1.2.3 becomes 1.3.0 + | + patch: 1.2.3 becomes 1.2.4 + | - [build-type] one of release|snapshot (default=snapshot) + | + release: The SemVer is emitted as-is. + | + snapshot: The SemVer has the string "-SNAPSHOT" appended. + | + | Snapshot builds look like 1.2.3-SNAPSHOT. + |""".stripMargin + + val help: Help = Help( + name = commandName, + briefHelp = (commandName, "Reconfigures the build version using SemVer."), + detail = detailedHelpString + ) + + /** The Command implementation. Added to projects via `semVerDefaults` within + * the [[SemVerPlugin]]. + */ + val command: Command = Command.args(commandName, commandName, help) { + ( + state, + args + ) => + val logger = state.log + val (calculated, rendered, rt, bt) = calculateVersion(args) + + logger.info( + s"Calculated new SemVer: '$rendered' [Release Type = $rt, Build Type = $bt]" + ) + + import SemVerKeys.* + + // "Update" the project state by appending new values to the session DAG. + Project + .extract(state) + .appendWithSession( + settings = Seq( + ThisBuild / version := rendered, + ThisBuild / semVer := rendered, + ThisBuild / semVerSelected := calculated, + ThisBuild / semVerMajor := calculated.major, + ThisBuild / semVerSnapshot := bt == BuildType.Snapshot + ), + state = state + ) + } + + private def calculateVersion(args: Seq[String]) + : (SemVer, String, ReleaseType, BuildType) = + // These are the current values cached in state. Depending on the + // arguments to this command, these properties may be overridden. + val currentReleaseType = SemVerProps.getReleaseType() + val currentBuildType = SemVerProps.getBuildType() + + val (newReleaseType, newBuildType) = args match { + case r :: b :: _ => + ( + ReleaseType.parse(r, currentReleaseType), + BuildType.parse(b, currentBuildType) + ) + case r :: Nil => + (ReleaseType.parse(r, currentReleaseType), currentBuildType) + case Nil => (currentReleaseType, currentBuildType) + } + + sys.props(SemVerProps.SemVerReleaseType) = newReleaseType.name + sys.props(SemVerProps.SemVerBuildType) = newBuildType.name + + val calculated = SemVerCalculator.updateGit( + releaseType = newReleaseType, + buildType = newBuildType + ) + + (calculated._1, calculated._2, newReleaseType, newBuildType) + +end SetVersion -- 2.43.0