2
votes

I have a simple multi project, whereby the root aggregates projects a and b. The root project loads this plugin I'm writing that is supposed to allow easy integration with the build system in our company.

lazy val a = project in file("a")

lazy val b = project in file("b")

Now, I'd like to define some Settings in the plugin that don't make sense in the root project, and can have different values for each sub-project. However, if I just define them like

object P extends Plugin {
    val kind = settingKey[Kind]("App or Lib?")
    val ourResolver = settingKey[Resolver]("...")

    override def projectSettings = Seq(
        // I want this to only be defined in a and b, where `kind` is defined
        ourResolver <<= kind { k => new OurInternalResolver(k) }
    )
}

then sbt will complain that ourResolver cannot be defined because kind is not defined in the root project.

Is there a way to specify a scope for this setting (ourResolver), such that the setting would be defined in every aggregated project except the root project?

Or do I have to make it into an SettingKey[Option[_]] and set it to None by default?

Edit: I have quite a large set of settings which progressively depend on kind and then ourResolver and so on, and these settings should only be defined where (read: "in the project scopes where") kind is defined. Added ourResolver to example code to reflect this.

2

2 Answers

1
votes

However, if I just define them like....

There's nothing really magical about setting keys. Keys are just String entry tied with the type. So You can define your key the way you're doing now just fine.

Is there a way to specify a scope for this setting, such that the setting would be defined in every aggregated project except the root project?

A setting consists of the following four things:

  1. scope (project, config, task)
  2. a key
  3. dependencies
  4. an ability to evaluate itself (def evaluate(Settings[Scope]): T)

If you did not specify otherwise, your settings are already scoped to a particular project in the build definition.

lazy val a = (project in file("a")).
  settings(commonSettings: _*).
  settings(
    name := "a",
    kind := Kind.App,
    libraryDependencies ++= aDeps(scalaVersion.value)
  )

lazy val b = (project in file("b")).
  settings(commonSettings: _*).
  settings(
    name := "b",
    kind := Kind.Lib,
    libraryDependencies ++= bDeps(scalaVersion.value)
  )

lazy val root = (project in file(".")).
  settings(commonSettings: _*).
  settings(
    name := "foo",
    publishArtifact := false
  ).
  aggregate(a, b)

In the above, kind := Kind.App setting is scoped to project a. So that would answer your question literally.

sbt will complain that kind is not defined in the root project.

This is the part I'm not clear what's going on. Does this happen when you load the build? Or does it happen when you type kind into the sbt shell? If you're seeing it at the startup that likely means you have a task/setting that's trying to depend on kind key. Don't load the setting, or reimplement it so something that doesn't use kind.

Another way to avoid this issue may be to give up on using root aggregation. Switch into subproject and run tasks or construct explicit aggregation using ScopeFilter.

0
votes

I've managed to ultimately do this by leveraging derive, a method that creates DerivedSettings. These settings will then be automatically expanded by sbt in every scope where their dependencies are defined.

Therefore I can now write my plugin as:

object P extends Plugin {
    val kind = settingKey[Kind]("App or Lib?")
    val ourResolver = settingKey[Resolver]("...")

    def derivedSettings = Seq(
        // I want this to only be defined in a and b, where `kind` is defined
        ourResolver <<= kind { k => new OurInternalResolver(k) }
    ) map (s => Def.derive(s, trigger = _ != streams.key))

    override def projectSettings = derivedSettings ++ Seq( /* other stuff */ )
}

And if I define kind in a and b's build.sbt, then ourResolver will also end up being defined in those scopes.