24

I want a program I'm building to be able to report its own version at runtime (e.g. scala myprog.jar --version). Traditionally in a maven project, I'd use resource filtering (pom.xml -> file.properties -> read value at runtime). I know there's sbt-filter-plugin to emulate this functionality, but I'm curious if there's a more standard / preferred / clever way of doing this in SBT.

tl;dr how can I read the version number defined in build.sbt at runtime?

Chris Eberle
  • 44,989
  • 12
  • 77
  • 112

4 Answers4

27

Update...

https://github.com/ritschwumm/xsbt-reflect (mentioned above) is Obsolete, but there is this cool SBT release tool that can automatically manage versions and more: https://github.com/sbt/sbt-release.

Alternatively, if you want a quick fix you can get version from manifest like this:

val version: String = getClass.getPackage.getImplementationVersion

This value will be equal to version setting in your project which you set either in build.sbt or Build.scala.

Another Update ...

Buildinfo SBT plugin can generate a class with version number based on build.sbt:

/** This object was generated by sbt-buildinfo. */
case object BuildInfo {
  /** The value is "helloworld". */
  val name: String = "helloworld"
  /** The value is "0.1-SNAPSHOT". */
  val version: String = "0.1-SNAPSHOT"
  /** The value is "2.10.3". */
  val scalaVersion: String = "2.10.3"
  /** The value is "0.13.2". */
  val sbtVersion: String = "0.13.2"
  override val toString: String = "name: %s, version: %s, scalaVersion: %s, sbtVersion: %s" format (name, version, scalaVersion, sbtVersion)
}

See the docs on how to enable it here: https://github.com/sbt/sbt-buildinfo/.

yǝsʞǝla
  • 15,379
  • 1
  • 39
  • 63
  • 4
    Unfortunately, this gives a `null` for me. sbt 0.13.8, JDK 8, Scala 2.11.7 – akauppi Jul 29 '15 at 06:44
  • Yes, it shows in the output jar filename and so forth. But the project is not making any Maven pom, it's a standalone executable. I assume this is the reason it wouldn't get these implementation / specification versions etc. Any pointers are welcome - I'm not from a Java background so these feel a bit overengineered to me. :) – akauppi Jul 30 '15 at 16:46
  • @akauppi I'm puzzled by your comments ;). Is your build based on SBT, could you paste the filename and the bit of code that sets the version? What do you mean by "standalone executable" and "making pom"? Thanks – yǝsʞǝla Jul 31 '15 at 02:00
  • I could discuss this on email. Do you mean '.getImplementationVersion' should work on a bare sbt project, without any other packaging? – akauppi Jul 31 '15 at 13:28
  • 4
    I don't know what @akauppi's problem ended up being, but for me `null` was printed because I ran my app with `sbt run`, which obviously doesn't use the metadata from the .jar file. If run using `java -cp somepath my.main.Class` or any other way that runs the actual compiled .jar, the version is printed as expected. – dskrvk Jul 29 '16 at 23:27
  • @dskrvk You probably guessed my problem just right! – akauppi Jul 30 '16 at 20:17
  • 1
    Can you please add a comment mentioning that the idiomatic way of doing this in sbt/sbt is via https://github.com/sbt/sbt-buildinfo/? @yǝsʞǝlA – Jorge Vicente Cantero Dec 19 '17 at 10:43
7

Use the xsbt-reflect plugin. It will generate a source file that contains, among other things, the project version number.

Michael Ekstrand
  • 26,173
  • 8
  • 54
  • 88
3

In general, without any plugins, you can do something like this:

sourceGenerators in Compile += Def.task {
  val file = (sourceManaged in Compile).value / "foo" / "bar" / "BuildInfo.scala"

  IO.write(
    file,
    s"""package foo.bar
       |object BuildInfo {
       |  val Version = "${version.value}"
       |}""".stripMargin
  )

  Seq(file)
}.taskValue

And then do with foo.bar.BuildInfo.Version constant whatever you like.

Or more general:

def generateBuildInfo(packageName: String,
                      objectName: String = "BuildInfo"): Setting[_] =
  sourceGenerators in Compile += Def.task {
    val file =
      packageName
        .split('.')
        .foldLeft((sourceManaged in Compile).value)(_ / _) / s"$objectName.scala"

    IO.write(
      file,
      s"""package $packageName
         |object $objectName {
         |  val Version = "${version.value}"
         |}""".stripMargin
    )

    Seq(file)
  }.taskValue

Example:

settings(generateBuildInfo("foo.bar"))

You can even change this to pass object properties as a Map[String, String] and generate the object appropriately.

Tvaroh
  • 6,255
  • 4
  • 45
  • 54
1

I ended up making the build system (I use a Makefile on top of sbt) prepare a src/main/resources/version.txt file for Scala to read.

In the Makefile:

$(RESOURCES_VERSION): build.sbt
    grep "^version := " $< | cut -f2 -d\" > $@

In Scala:

val version: String = {
  val src = Source.fromURL( getClass.getResource("/version.txt") )
  src.getLines.next   // just take the first line 
}

This works for me.

It's curious that such a needed feature (I would think) is not easily available in Scala. A very simple sbt plugin just for this would be welcome.

akauppi
  • 14,244
  • 12
  • 73
  • 94