3
votes

As of sbt 0.13, the recommended way to define all configuration is multi-project .sbt build definition. It is absolutely clear how to define settings for each project there; however, how one should define build-wide settings, for example, in ThisBuild or Global scopes?

With Scala build definitions it is clear: we have settings key coming from Build trait:

object MyBuild extends Build {
  override lazy val settings = super.settings ++ Seq(
    someKey := someValue
  )
}

Now someKey's value will be build-scoped by default. Alternatively, it can be defined directly in .sbt file with ThisBuild scope:

someKey in ThisBuild := someValue

However, there is no natural place to define these options in multi-project .sbt builds. I suspect that they should be defined in one of the projects with ThisBuild scope:

val someProject = project
  .settings(someKey in ThisBuild := someValue)

but this is very counterintuitive and not really clear: are such settings really applied globally or do they really belong to the specific project? What if I define the same key in multiple projects?

val project1 = project.settings(someKey in ThisBuild := someValue1)

val project2 = project.settings(someKey in ThisBuild := someValue2)

The documentation does not give any answers on this, unfortunately.

Note that I explicitly do not ask how to share global settings between builds or projects. I ask specifically about defining build-scoped settings.

1
Not really what you want, but you could define an implicit class with a method that adds the common settings to all the project, something like:implicit class RichProject(project: Project) { def baseSettings(project: Project) = project.settings(...)} Ende Neu
@EndeNeu, I'm not looking for sharing common settings between projects (I stated this explicitly). I'm looking for a way to assign a setting in build scope. These are very different things.Vladimir Matveev
I think I don't get the question then, it seems to me that settings can either be applied on a project basis or to all the projects, doesn't build-wide settings means for all the projects in the build?Ende Neu
@EndeNeu, do you know about scopes each setting may have? They are described in SBT documentation. In short, each setting may be scoped by three axes, project, configuration and task. Project axis coordinate may point to a specific project or to entire build, and the latter is exactly what I mean. And no, build-scoped setting does not imply "applied to all projects", at least, not directly.Vladimir Matveev
Just in case, I've also rephrased the title of the question.Vladimir Matveev

1 Answers

5
votes

Well, I've performed some experiments and found out the following. Here is an example build.sbt:

val testKey = settingKey[String]("A key for test")

testKey := "value in build.sbt"
testKey in ThisBuild := "build-scoped value in build.sbt"
testKey in Global := "global value in build.sbt"

lazy val root = (project in file("."))
  .settings(
    testKey := "value in root project",
    testKey in ThisBuild := "build-scoped value in root project",
    testKey in Global := "global value in root project"
  )

lazy val child = project
  .settings(
    testKey := "value in child",
    testKey in ThisBuild := "build-scoped value in child project",
    testKey in Global := "global value in child project"
  )

and here's what I see in SBT console:

[info] Set current project to root (in build file:/private/tmp/sbtt/)
> inspect testKey
[info] Setting: java.lang.String = value in build.sbt
[info] Description:
[info]  A key for test
[info] Provided by:
[info]  {file:/private/tmp/sbtt/}root/*:testKey
[info] Defined at:
[info]  /private/tmp/sbtt/build.sbt:4
[info] Delegates:
[info]  root/*:testKey
[info]  {.}/*:testKey
[info]  */*:testKey
[info] Related:
[info]  */*:testKey
[info]  {.}/*:testKey
[info]  child/*:testKey
> testKey
[info] value in build.sbt
> root/testKey
[info] value in build.sbt
> child/testKey
[info] value in child
> {.}/testKey
[info] global value in child
> reload
[info] Loading global plugins from /Users/netvl/.sbt/0.13/plugins
[info] Set current project to root (in build file:/private/tmp/sbtt/)
> testKey
[info] value in build.sbt
> root/testKey
[info] value in build.sbt
> child/testKey
[info] value in child
> {.}/testKey
[info] build-scoped value in child project
> */testKey
[info] global value in child project

So, the conclusion is as follows.

  • Values defined at the topmost level in a multi-project SBT build seem to be scoped to the project build.sbt belongs to. That is, here build.sbt resides in the root directory, and so defining testKey in the file directly is the same as defining it in (project in file(".")).settings(...) clause. According to SBT output, they also take priority over those defined in .settings(...).
  • Keys in larger scopes like ThisBuild or Global seem to be able to be defined in any project, but "later" projects will then take priority. This is probably related to the order the projects are processed. I have expected that the order of evaluation of lazy vals would affect this, and I've tried adding .aggregate(child) to the root project to force the child project to be evaluated first, but it didn't seem to have any effect: globally scoped values from child project still have priority.

Therefore, it seems fine to define build-scoped or globally scoped values inside the root project or inside common settings list which is then used in every project. However, assigning different values to a key in build or global scope in different projects will likely lead to a weird behavior - only one of these values will take effect, and which one I don't know.

It would be very nice if all this were explained in the documentation, but at the moment I can't find anything on this behavior there.

I'd gladly accept someone else's answer which would provide more insight on how SBT works in this regard.