Skip to content

Commit

Permalink
Implemented separator setting
Browse files Browse the repository at this point in the history
Fixes sbt#5

Added a `dynverSeparator` setting so that it can be overridden
for use with docker images in an easier to understand manner than is
currently possible.
  • Loading branch information
jroper committed Mar 1, 2019
1 parent ed65d94 commit 820ea56
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 37 deletions.
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,19 @@ If you're publishing to Sonatype sonashots then enable `dynverSonatypeSnapshots
changes). This opt-in exists because the Sonatype's snapshots repository requires all versions to end with
`-SNAPSHOT`.

## Custom version string

Sometimes you want to customise the version string. It might be for personal preference, or for compatibility with another tool or spec.

As an example, Docker rejects tags which include `+`'s ([#5](https://github.com/dwijnand/sbt-dynver/issues/5)).
## Docker compatible version strings

A simply way to solve this is to simply post-process the value of `version in ThisBuild` (and optionally `dynver in ThisBuild`), for example by replacing '+' with '-':
The default version string format includes `+` characters, which is not compatible with docker tags. This character can be overridden by setting:

```scala
version in ThisBuild ~= (_.replace('+', '-'))
dynver in ThisBuild ~= (_.replace('+', '-'))
dynverSeparator in ThisBuild := "-"
```

If instead you want to completely customise the string format you can use `dynverGitDescribeOutput`, `dynverCurrentDate` and `sbtdynver.DynVer`, like so:
## Custom version string

Sometimes you want to customise the version string. It might be for personal preference, or for compatibility with another tool or spec.

To completely customise the string format you can use `dynverGitDescribeOutput`, `dynverCurrentDate` and `sbtdynver.DynVer`, like so:

```scala
def versionFmt(out: sbtdynver.GitDescribeOutput): String = {
Expand Down
64 changes: 42 additions & 22 deletions src/main/scala/sbtdynver/DynVerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ object DynVerPlugin extends AutoPlugin {
val dynverGitDescribeOutput = settingKey[Option[GitDescribeOutput]]("The output from git describe")
val dynverSonatypeSnapshots = settingKey[Boolean]("Whether to append -SNAPSHOT to snapshot versions")
val dynverGitPreviousStableVersion = settingKey[Option[GitDescribeOutput]]("The last stable tag")
val dynverSeparator = settingKey[String]("The separator to use between tag and distance, and the hash and dirty timestamp")
val dynverCheckVersion = taskKey[Boolean]("Checks if version and dynver match")
val dynverAssertVersion = taskKey[Unit]("Asserts if version and dynver match")
val dynverAssertTagVersion = taskKey[Unit]("Asserts if the version derives from git tags")
Expand All @@ -35,18 +36,20 @@ object DynVerPlugin extends AutoPlugin {
version := {
val out = dynverGitDescribeOutput.value
val date = dynverCurrentDate.value
if (dynverSonatypeSnapshots.value) out.sonatypeVersion(date)
else out.version(date)
val separator = dynverSeparator.value
if (dynverSonatypeSnapshots.value) out.sonatypeVersion(date, separator)
else out.version(date, separator)
},
isSnapshot := dynverGitDescribeOutput.value.isSnapshot,
isVersionStable := dynverGitDescribeOutput.value.isVersionStable,
previousStableVersion := dynverGitPreviousStableVersion.value.previousVersion,

dynverCurrentDate := new Date,
dynverInstance := DynVer(Some((baseDirectory in ThisBuild).value)),
dynverInstance := DynVer(Some((baseDirectory in ThisBuild).value), dynverSeparator.value),
dynverGitDescribeOutput := dynverInstance.value.getGitDescribeOutput(dynverCurrentDate.value),
dynverSonatypeSnapshots := false,
dynverGitPreviousStableVersion := dynverInstance.value.getGitPreviousStableTag,
dynverSeparator := DynVer.separator,

dynver := {
val dynver = dynverInstance.value
Expand All @@ -72,7 +75,9 @@ object DynVerPlugin extends AutoPlugin {

final case class GitRef(value: String)
final case class GitCommitSuffix(distance: Int, sha: String)
final case class GitDirtySuffix(value: String)
final case class GitDirtySuffix(suffix: String) {
def value: String = if (suffix.isEmpty) "" else s"+$suffix"
}

object GitRef extends (String => GitRef) {
final implicit class GitRefOps(val x: GitRef) extends AnyVal { import x._
Expand All @@ -91,26 +96,32 @@ object GitCommitSuffix extends ((Int, String) => GitCommitSuffix) {
}

object GitDirtySuffix extends (String => GitDirtySuffix) {
final implicit class GitDirtySuffixOps(val x: GitDirtySuffix) extends AnyVal { import x._
def dropPlus: GitDirtySuffix = GitDirtySuffix(value.replaceAll("^\\+", ""))
def mkString(prefix: String, suffix: String): String = if (value.isEmpty) "" else prefix + value + suffix
final implicit class GitDirtySuffixOps(val x: GitDirtySuffix) extends AnyVal {
def dropPlus: GitDirtySuffix = x
def mkString(prefix: String, suffix: String): String = if (x.suffix.isEmpty) "" else prefix + x.suffix + suffix
def asSuffix(separator: String): String = mkString(separator, "")
}
}
final case class GitDescribeOutput(ref: GitRef, commitSuffix: GitCommitSuffix, dirtySuffix: GitDirtySuffix) {
def version: String = {
if (isCleanAfterTag) ref.dropV.value + dirtySuffix.value // no commit info if clean after tag
else if (commitSuffix.sha.nonEmpty) ref.dropV.value + "+" + commitSuffix.distance + "-" + commitSuffix.sha + dirtySuffix.value
else commitSuffix.distance + "-" + ref.value + dirtySuffix.value

def version(separator: String): String = {
val ds = dirtySuffix.asSuffix(separator)
if (isCleanAfterTag) ref.dropV.value + ds // no commit info if clean after tag
else if (commitSuffix.sha.nonEmpty) ref.dropV.value + separator + commitSuffix.distance + "-" + commitSuffix.sha + ds
else commitSuffix.distance + "-" + ref.value + ds
}
def sonatypeVersion(separator: String): String =
if (isSnapshot) version(separator) + "-SNAPSHOT" else version(separator)

def sonatypeVersion: String = if (isSnapshot) version + "-SNAPSHOT" else version
def version: String = version(DynVer.separator)
def sonatypeVersion: String = sonatypeVersion(DynVer.separator)

def isSnapshot(): Boolean = hasNoTags() || !commitSuffix.isEmpty || isDirty()
def previousVersion: String = ref.dropV.value
def isVersionStable(): Boolean = !isDirty()

def hasNoTags(): Boolean = !ref.isTag
def isDirty(): Boolean = dirtySuffix.value.nonEmpty
def isDirty(): Boolean = dirtySuffix.suffix.nonEmpty
def isCleanAfterTag: Boolean = ref.isTag && commitSuffix.isEmpty && !isDirty()
}

Expand All @@ -121,7 +132,7 @@ object GitDescribeOutput extends ((GitRef, GitCommitSuffix, GitDirtySuffix) => G
private val Sha = """([0-9a-f]{8})""".r
private val HEAD = """HEAD""".r
private val CommitSuffix = s"""($Distance-$Sha)""".r
private val TstampSuffix = """(\+[0-9]{8}-[0-9]{4})""".r
private val TstampSuffix = """(?:\+([0-9]{8}-[0-9]{4}))""".r

private val FromTag = s"""^$OptWs$Tag$CommitSuffix?$TstampSuffix?$OptWs$$""".r
private val FromSha = s"""^$OptWs$Sha$TstampSuffix?$OptWs$$""".r
Expand All @@ -141,8 +152,13 @@ object GitDescribeOutput extends ((GitRef, GitCommitSuffix, GitDirtySuffix) => G
implicit class OptGitDescribeOutputOps(val _x: Option[GitDescribeOutput]) extends AnyVal {
def mkVersion(f: GitDescribeOutput => String, fallback: => String): String = _x.fold(fallback)(f)

def version(d: Date): String = mkVersion(_.version, DynVer fallback d)
def sonatypeVersion(d: Date): String = mkVersion(_.sonatypeVersion, DynVer fallback d)
def version(d: Date, separator: String): String =
mkVersion(_.version(separator), DynVer(None, separator) fallback d)
def sonatypeVersion(d: Date, separator: String): String =
mkVersion(_.sonatypeVersion(separator), DynVer(None, separator) fallback d)

def version(d: Date): String = version(d, DynVer.separator)
def sonatypeVersion(d: Date): String = sonatypeVersion(d, DynVer.separator)
def previousVersion: Option[String] = _x.map(_.previousVersion)
def isSnapshot: Boolean = _x.forall(_.isSnapshot)
def isVersionStable: Boolean = _x.exists(_.isVersionStable)
Expand All @@ -153,14 +169,14 @@ object GitDescribeOutput extends ((GitRef, GitCommitSuffix, GitDirtySuffix) => G
}

// sealed just so the companion object can extend it. Shouldn't've been a case class.
sealed case class DynVer(wd: Option[File]) {
def version(d: Date): String = getGitDescribeOutput(d) version d
def sonatypeVersion(d: Date): String = getGitDescribeOutput(d) sonatypeVersion d
sealed case class DynVer(wd: Option[File], separator: String) {
def version(d: Date): String = getGitDescribeOutput(d).version(d, separator)
def sonatypeVersion(d: Date): String = getGitDescribeOutput(d).sonatypeVersion(d, separator)
def previousVersion : Option[String] = getGitPreviousStableTag.previousVersion
def isSnapshot(): Boolean = getGitDescribeOutput(new Date).isSnapshot
def isVersionStable(): Boolean = getGitDescribeOutput(new Date).isVersionStable

def makeDynVer(d: Date): Option[String] = getGitDescribeOutput(d) map (_.version)
def makeDynVer(d: Date): Option[String] = getGitDescribeOutput(d) map (_.version(separator))
def isDirty(): Boolean = getGitDescribeOutput(new Date).isDirty
def hasNoTags(): Boolean = getGitDescribeOutput(new Date).hasNoTags

Expand Down Expand Up @@ -195,15 +211,19 @@ sealed case class DynVer(wd: Option[File]) {
}

def timestamp(d: Date): String = "%1$tY%1$tm%1$td-%1$tH%1$tM" format d
def fallback(d: Date): String = s"HEAD+${timestamp(d)}"
def fallback(d: Date): String = s"HEAD$separator${timestamp(d)}"

private def execAndHandleEmptyOutput(cmd: String): Option[String] = {
Try(Process(cmd, wd) !! impl.NoProcessLogger).toOption
.filter(_.trim.nonEmpty)
}
}

object DynVer extends DynVer(None) with (Option[File] => DynVer)
object DynVer extends DynVer(None, "+")
with ((Option[File], String) => DynVer)
with (Option[File] => DynVer) {
override def apply(wd: Option[File]) = apply(wd, separator)
}

object `package`

Expand Down
53 changes: 53 additions & 0 deletions src/sbt-test/dynver/distance-separator/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import scala.sys.process.stringToProcess

dynverSeparator in ThisBuild := "-"

def tstamp = Def.setting(sbtdynver.DynVer timestamp dynverCurrentDate.value)
def headSha = {
implicit def log2log(log: Logger): scala.sys.process.ProcessLogger = sbtLoggerToScalaSysProcessLogger(log)
Def.task("git rev-parse --short=8 HEAD".!!(streams.value.log).trim)
}

def check(a: String, e: String) = assert(a == e, s"Version mismatch: Expected $e, Incoming $a")

TaskKey[Unit]("checkNotAGitRepo") := check(version.value, s"HEAD-${tstamp.value}")
TaskKey[Unit]("checkNoCommits") := check(version.value, s"HEAD-${tstamp.value}")
TaskKey[Unit]("checkOnCommit") := check(version.value, s"1-${headSha.value}")
TaskKey[Unit]("checkOnCommitDirty") := check(version.value, s"1-${headSha.value}-${tstamp.value}")
TaskKey[Unit]("checkOnTag") := check(version.value, s"1.0.0")
TaskKey[Unit]("checkOnTagDirty") := check(version.value, s"1.0.0-0-${headSha.value}-${tstamp.value}")
TaskKey[Unit]("checkOnTagAndCommit") := check(version.value, s"1.0.0-1-${headSha.value}")
TaskKey[Unit]("checkOnTagAndCommitDirty") := check(version.value, s"1.0.0-1-${headSha.value}-${tstamp.value}")

TaskKey[Unit]("gitInitSetup") := {
implicit def log2log(log: Logger): scala.sys.process.ProcessLogger = sbtLoggerToScalaSysProcessLogger(log)
"git init".!!(streams.value.log)
"git config user.email dynver@mailinator.com".!!(streams.value.log)
"git config user.name dynver".!!(streams.value.log)
}

TaskKey[Unit]("gitAdd") := {
implicit def log2log(log: Logger): scala.sys.process.ProcessLogger = sbtLoggerToScalaSysProcessLogger(log)
"git add .".!!(streams.value.log)
}
TaskKey[Unit]("gitCommit") := {
implicit def log2log(log: Logger): scala.sys.process.ProcessLogger = sbtLoggerToScalaSysProcessLogger(log)
"git commit -am1".!!(streams.value.log)
}
TaskKey[Unit]("gitTag") := {
implicit def log2log(log: Logger): scala.sys.process.ProcessLogger = sbtLoggerToScalaSysProcessLogger(log)
"git tag -a v1.0.0 -m1.0.0".!!(streams.value.log)
}

TaskKey[Unit]("dirty") := {
import java.nio.file._, StandardOpenOption._
import scala.collection.JavaConverters._
Files.write(baseDirectory.value.toPath.resolve("f.txt"), Seq("1").asJava, CREATE, APPEND)
}

def sbtLoggerToScalaSysProcessLogger(log: Logger): scala.sys.process.ProcessLogger =
new scala.sys.process.ProcessLogger {
def buffer[T](f: => T): T = f
def err(s: => String): Unit = log info s
def out(s: => String): Unit = log error s
}
5 changes: 5 additions & 0 deletions src/sbt-test/dynver/distance-separator/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.dwijnand" % "sbt-dynver" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
32 changes: 32 additions & 0 deletions src/sbt-test/dynver/distance-separator/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
> checkNotAGitRepo

> gitInitSetup
> reload
> checkNoCommits

> dirty
> gitAdd
> gitCommit
> reload
> checkOnCommit

> dirty
> reload
> checkOnCommitDirty

> gitCommit
> gitTag
> reload
> checkOnTag

> dirty
> reload
> checkOnTagDirty

> gitCommit
> reload
> checkOnTagAndCommit

> dirty
> reload
> checkOnTagAndCommitDirty
2 changes: 1 addition & 1 deletion src/test/scala/sbtdynver/GH020.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import org.scalacheck._, Prop._
object GH020 extends Properties("GH020") {
property("Handles CF+LF (Windows)") =
GitDescribeOutput.parse("v0.7.0+3-e7a84ebc+20161120-1948\r\n") ?=
GitDescribeOutput(GitRef("v0.7.0"), GitCommitSuffix(3, "e7a84ebc"), GitDirtySuffix("+20161120-1948"))
GitDescribeOutput(GitRef("v0.7.0"), GitCommitSuffix(3, "e7a84ebc"), GitDirtySuffix("20161120-1948"))
}
8 changes: 4 additions & 4 deletions src/test/scala/sbtdynver/GitDescribeOutputSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import org.scalacheck._, Prop._
object GitDescribeOutputSpec extends Properties("GitDescribeOutputSpec") {

test("v1.0.0", "v1.0.0", 0, "", "" )
test("v1.0.0+20140707-1030", "v1.0.0", 0, "", "+20140707-1030")
test("v1.0.0+20140707-1030", "v1.0.0", 0, "", "20140707-1030")
test("v1.0.0+3-1234abcd", "v1.0.0", 3, "1234abcd", "" )
test("v1.0.0+3-1234abcd+20140707-1030", "v1.0.0", 3, "1234abcd", "+20140707-1030")
test("v1.0.0+3-1234abcd+20140707-1030", "v1.0.0", 3, "1234abcd", "20140707-1030")
test("1234abcd", "1234abcd", 0, "", "" )
test("1234abcd+20140707-1030", "1234abcd", 0, "", "+20140707-1030")
test("HEAD+20140707-1030", "HEAD", 0, "", "+20140707-1030")
test("1234abcd+20140707-1030", "1234abcd", 0, "", "20140707-1030")
test("HEAD+20140707-1030", "HEAD", 0, "", "20140707-1030")

def test(v: String, ref: String, dist: Int, sha: String, dirtySuffix: String) = {
val out = GitDescribeOutput(GitRef(ref), GitCommitSuffix(dist, sha), GitDirtySuffix(dirtySuffix))
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/sbtdynver/RepoStates.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object RepoStates {
final case class State() {
val dir = doto(Files.createTempDirectory(s"dynver-test-").toFile)(_.deleteOnExit())
val date = new GregorianCalendar(2016, 8, 17).getTime
val dynver = DynVer(Some(dir))
val dynver = DynVer(Some(dir), "+")

var git: Git = _
var sha: String = "undefined"
Expand Down

0 comments on commit 820ea56

Please sign in to comment.