I'm trying to write a plugin for sbt for my project that will process resources. In a nutshell, it is maven profiles made in sbt. When I inspect prod:dictionary I get expected state of this Map, however, when I try prod:expandParameters I get an empty Map. How could I get the value of the dictionary from the scope of the exact configuration that command is run with?
project/ResourceFiltering.scala
import sbt._
import sbt.Keys._
import sbt.internal.util.ManagedLogger
import scala.util.matching.Regex
object ResourceFiltering extends AutoPlugin {
override def trigger = AllRequirements
sealed trait Keys {
lazy val expandParameters = taskKey[Unit]("")
lazy val extensions = settingKey[Seq[String]]("")
lazy val pattern = settingKey[Regex]("")
lazy val dictionary = settingKey[Map[String, String]]("")
}
object autoImport extends Keys
import autoImport._
override val projectSettings: Seq[Def.Setting[_]] = Seq(
Zero / extensions := Seq("conf", "properties", "xml"),
Zero / pattern := """(\$\{()\})""".r,
Zero / dictionary := Map.empty,
expandParameters := {
val log: ManagedLogger = streams.value.log
log.info(s"""|Parameter expansion
|Configuration: $configuration
|Extensions: ${extensions value}
|Pattern: ${pattern value}
|Dictionary: ${dictionary value}
""".stripMargin)
}
)
}
build.sbt
enablePlugins(ResourceFiltering)
lazy val Prod = config("prod") extend Compile describedAs "Scope to build production packages."
lazy val Stage = config("stage") extend Compile describedAs "Scope to build stage packages."
lazy val Local = config("local") extend Compile describedAs "Scope to build local packages."
lazy val root = (project in file("."))
.configs(Prod, Stage, Local)
.settings(sharedSettings)
lazy val sharedSettings =
prodSettings ++ stageSettings ++ localSettings
lazy val defaults = Defaults.configSettings ++ Defaults.configTasks ++ Defaults.resourceConfigPaths
lazy val prodSettings = inConfig(Prod)(defaults ++ Seq(
dictionary ++= Profiles.prod
))
lazy val stageSettings = inConfig(Stage)(defaults ++ Seq(
dictionary ++= Profiles.stage
))
lazy val localSettings = inConfig(Local)(defaults ++ Seq(
dictionary ++= Profiles.local
))
project/Profiles.scala
lazy val default: Map[String, String] = local
lazy val local: Map[String, String] = Map("example" -> "local")
lazy val stage: Map[String, String] = Map("example" -> "stage")
lazy val prod: Map[String, String] = Map("example" -> "prod")
Analysing Plugins Best Practices docs I would make the following recommendations regarding configuration and scoping.
Provide default values in globalSettings instead of projectSettings like so
override lazy val globalSettings = Seq(
dictionary := Map.empty
)
Next collect base configuration of expandParameters into its own sequence like so
lazy val baseResourceFilteringSettings: Seq[Def.Setting[_]] = Seq(
extensions := Seq("conf", "properties", "xml"),
pattern := """(\$\{()\})""".r,
expandParameters := {
val log: ManagedLogger = streams.value.log
log.info(
s"""|Parameter expansion
|Configuration: $configuration
|Extensions: ${extensions value}
|Pattern: ${pattern value}
|Dictionary: ${dictionary value}
""".stripMargin
)
}
)
Note how dictionary is not initialised in baseResourceFilteringSettings, instead by default it will come from globalSettings.
Now we have taken care of our defaults and we have our base configuration, so we can proceed to "specialise" it by configuration scope using inConfig like so
lazy val localSettings = inConfig(Local)(defaults ++ Seq(
dictionary ++= Profiles.local
) ++ baseResourceFilteringSettings)
Note how we have scoped baseResourceFilteringSettings to Local config, as well as dictionary ++= Profiles.local.
Now executing ;reload;local:expandParameters should output
[info] Parameter expansion
[info] Configuration: SettingKey(This / This / This / configuration)
[info] Extensions: List(conf, properties, xml)
[info] Pattern: (\$\{()\})
[info] Dictionary: Map(example -> local)
where we see Dictionary: Map(example -> local) as required.
Here is the complete code of ResourceFiltering
object ResourceFiltering extends AutoPlugin {
override def trigger = AllRequirements
sealed trait Keys {
lazy val expandParameters = taskKey[Unit]("")
lazy val extensions = settingKey[Seq[String]]("")
lazy val pattern = settingKey[Regex]("")
lazy val dictionary = settingKey[Map[String, String]]("")
lazy val baseResourceFilteringSettings: Seq[Def.Setting[_]] = Seq(
extensions := Seq("conf", "properties", "xml"),
pattern := """(\$\{()\})""".r,
expandParameters := {
val log: ManagedLogger = streams.value.log
log.info(
s"""|Parameter expansion
|Configuration: $configuration
|Extensions: ${extensions value}
|Pattern: ${pattern value}
|Dictionary: ${dictionary value}
""".stripMargin
)
}
)
}
object autoImport extends Keys
import autoImport._
override lazy val globalSettings = Seq(
dictionary := Map.empty
)
}
Also consider moving configuration definitions into plugin like so
object ResourceFiltering extends AutoPlugin {
override def trigger = AllRequirements
sealed trait Keys {
lazy val Prod = config("prod") extend Compile describedAs "Scope to build production packages."
lazy val Stage = config("stage") extend Compile describedAs "Scope to build stage packages."
lazy val Local = config("local") extend Compile describedAs "Scope to build local packages."
lazy val expandParameters = taskKey[Unit]("")
lazy val extensions = settingKey[Seq[String]]("")
lazy val pattern = settingKey[Regex]("")
lazy val dictionary = settingKey[Map[String, String]]("")
lazy val baseResourceFilteringSettings: Seq[Def.Setting[_]] = Seq(
extensions := Seq("conf", "properties", "xml"),
pattern := """(\$\{()\})""".r,
expandParameters := {
val log: ManagedLogger = streams.value.log
log.info(
s"""|Parameter expansion
|Configuration: $configuration
|Extensions: ${extensions value}
|Pattern: ${pattern value}
|Dictionary: ${dictionary value}
""".stripMargin
)
}
)
}
object autoImport extends Keys
import autoImport._
override lazy val globalSettings = Seq(
dictionary := Map.empty
)
override val projectSettings: Seq[Def.Setting[_]] =
inConfig(Stage)(baseResourceFilteringSettings) ++
inConfig(Prod)(baseResourceFilteringSettings) ++
inConfig(Local)(baseResourceFilteringSettings)
}
This way we do not have to remember to add baseResourceFilteringSettings to config scope and can simply write
lazy val localSettings = inConfig(Local)(defaults ++ Seq(
dictionary ++= Profiles.local
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With