1

I have a semi-complicated SBT process because I need to conditionally include a different config file based on what kind of build is needed. I solved this problem through sub-projects:

lazy val app = project
    .in(file("."))
    .enablePlugins(JavaAppPackaging)
    .settings(
      commonSettings // Seq() of settings to be shared between projects
      ,sourceGenerators in Compile += (avroScalaGenerateSpecific in Compile).taskValue
      ,(avroSpecificSourceDirectory in Compile) := new java.io.File("src/main/resources/com/coolCompany/folderName/avro")
    )

lazy val localPackage = project
    .in(file("build/local"))
    .enablePlugins(JavaAppPackaging)
    .settings(
      organization := "com.coolCompany",
      version := "0.1.0-SNAPSHOT",
      scalaVersion := "2.11.8",
      name := "my-neat-project",
      scalacOptions := compilerOptions, //Seq() of compiler flags
      sourceDirectory in Compile := (sourceDirectory in (app, Compile)).value,
      mappings in Universal += {
        ((sourceDirectory in Compile).value / "../../conf/local/config.properties") -> "lib/config.properties"
      }
    )
    .dependsOn(app)

val buildNumber = inputKey[String]("The version number of the artifact.")

lazy val deployedPackage = project
    .in(file("build/deployed"))
    .enablePlugins(JavaAppPackaging)
    .settings(
      organization := "com.coolCompany",
      buildNumber := {
        val args : Seq[String] = spaceDelimited("<arg>").parsed
        println(s"Input version number is ${args.head}")
        args.head
      },
      version := buildNumber.inputTaskValue + "-SNAPSHOT", //"0.1.0-SNAPSHOT",
      scalaVersion := "2.11.8",
      name := "my-cool-project",
      scalacOptions := compilerOptions,
      sourceDirectory in Compile := (sourceDirectory in (app, Compile)).value,
      mappings in Universal += {
        ((sourceDirectory in Compile).value / "../../conf/deployed/config.properties") -> "lib/config.properties"
      }
    )
    .dependsOn(app)

Now I need to allow the version number to be passed in by a build tool when building. You can see what I've attempted to do already: I created an inputKey task called buildNumber, then tried to access that in the version := definition. I can run the buildNumber task itself just fine:

$ sbt 'deployedPackage/buildNumber 0.1.2'
Input version number is 0.1.2

So I can at least verify that my input task works as expected. The issue is that I can't figure out how I actually get to that input value when running the actual packageBin step that I want.

I've tried the following:

$ sbt 'deployedPackage/universal:packageBin 0.1.2'
[error] Expected key
[error] Expected '::'
[error] Expected end of input.
[error] deployedPackage/universal:packageBin 0.1.2

So it clearly doesn't understand what to do with the version number. I've tried a bunch of different input variations, such as [...]packageBin buildNumber::0.1.2, [...]packageBin -- buildNumber 0.1.2, or [...]packageBin -- 0.1.2, and all of them give that error or something similar indicating it doesn't understand what I'm trying to pass in.

Now, ultimately, these errors make sense. buildNumber, the task, is what knows what to do with the command line values, but packageBin does not. How do I set up this task or these set of tasks to allow the version number to be passed in?

I have seen this question but the answers link to an sbt plugin that seems to do about 100 more things than I want it to do, including quite a few that I would need to find a way to explicitly disable. I only want the version number to be able to be passed in & used in the artifact.

Edit/Update: I resolved this issue by switching back to Maven.

Max
  • 849
  • 9
  • 24

3 Answers3

4

I think you're a bit confused about the way sbt settings work. Settings are set when sbt loads and then cannot be changed until you reload the session. So whatever you set version to, it will be fixed, it cannot be dynamic and depend on user input or a task (which on the contrast is dynamic and is evaluated every time you call it).

This is true. However you can still compute a SettingKey value in the first place. We use environment variables to set our build version.

version := "1." + sys.env.getOrElse("BUILD_NUMBER", "0-SNAPSHOT")

Explanation

  • 1. is the major version. Increment this manually as you like
  • BUILD_NUMBER is an environment variable that is typically set by a CI, e.g. Jenkins. If not set, use 0-SNAPSHOT as a version suffix.

We use this in our company for our continuous deployment pipeline.

Hope that helps, Muki

Muki
  • 3,513
  • 3
  • 27
  • 50
  • If you are using git flow it is useful to use `https://github.com/sbt/sbt-release` or `https://github.com/sritchie/sbt-gitflow` too – mgosk Jan 31 '18 at 15:55
0

I think you're a bit confused about the way sbt settings work. Settings are set when sbt loads and then cannot be changed until you reload the session. So whatever you set version to, it will be fixed, it cannot be dynamic and depend on user input or a task (which on the contrast is dynamic and is evaluated every time you call it).

You have an input task buildNumber which depends on user input and is evaluated every time you call it:

> show buildNumber 123
Input version number is 123
[info] 123
[success] Total time: 0 s, completed Dec 24, 2017 3:41:43 PM
> show buildNumber 456
Input version number is 456
[info] 456
[success] Total time: 0 s, completed Dec 24, 2017 3:41:45 PM

It returns whatever you give it and doesn't do anything (besides println). More importantly, it doesn't affect version setting anyhow (and couldn't even in theory).

When you use buildNumber.inputTaskValue, you refer to the input task itself, not its value (which is unknown because it depends on the user input). You can see it by checking the version setting value:

> show version
[info] sbt.InputTask@6c996907-SNAPSHOT

So it's definitely not what you want.


I suggest you to review your approach in general and read a bit more sbt docs, for example about Task graph (and the whole Getting started chapter).

If you still really need to set version on sbt load according to your input, you can do it with a command. It would look like this:

commands += Command.single("pkg") { (state0, buildNumber) =>
  val state1 = Project.extract(state0).append(Seq(version := buildNumber + "-SNAPSHOT"), state0)
  val (state2, result) = Project.extract(state1).runTask(packageBin in (deployedPackage, universal), state1)
  state2
}

But you should be really careful dealing with the state manually. Again, I recommend you to review your approach and change it to a more sbt-idiomatic one.

laughedelic
  • 6,230
  • 1
  • 32
  • 41
  • I'm not surprised I'm doing it wrong. Is there a starting point for this kind of thing? I'm surprised at how few resources I can find for what I thought would be an extremely common use case. – Max Dec 26 '17 at 14:04
  • I've linked sbt documentation in the answer. Unfortunately, there's not much else to read. You can also ask on the [Gitter channel](https://gitter.im/sbt/sbt) to solve some little issues. – laughedelic Dec 29 '17 at 19:37
0

I suggest you just set the version setting only for the project deployedPackage just before you call the task needing the version. This is the simplest way of setting the version, and as settings keys have scopes, this should work as intended.

I used something like this for a big multi module project, where one of the projects had a separate versioning history than the other projects.

Wolfgang Liebich
  • 400
  • 2
  • 12
  • I would reword this as an actual answer or post it as a comment. In it's current look this question is likely to be percieved as not an answer and would therefore be flagged accordingly. – Filnor Mar 16 '18 at 13:51