'Sbt task to load system environment variables
I have some yaml file with system environment variables needed for service run.
system:
  service: "name"
  port: 123
I must have all these variables, loaded to the shell before run service start. But I want to load them by some sbt variable, maybe load-dev-env.
The main problem that I can't import libraries for yaml parsing (circe-yaml) into sbt shell execution and all imports like
import io.circe._
import cats._
are failed due to circe is no the subpackage of io or etc...
- I've tried to put libraryDependencies to the ./project/build.sbt- no success
The last one thing - read all key/values manually but it doesn't look correct
Has someone done smth like this?
Solution 1:[1]
This SBT build definition works:
Dir structure:
build.sbt
src/main/resources/config.yml
project/build.sbt
project/build.properties
build.sbt:
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / organization := "com.example"
lazy val hello = (project in file("."))
  .settings(
    name := "Hello",
    //libraryDependencies += "io.circe" %% "circe-yaml" % "0.14.1"
  )
lazy val loadEnv = TaskKey[Unit]("load-dev-env")
loadEnv := {
  import _root_.io.circe._
  import _root_.io.circe.yaml.parser
  import scala.io.Source
  val filename = "src/main/resources/config.yml"
  val confStr = Source.fromFile(filename).getLines.mkString("\n")
  println(confStr)
  val json: Either[ParsingFailure, Json] = parser.parse(confStr)
  println(json)
}
project/build.sbt:
libraryDependencies += "io.circe" %% "circe-yaml" % "0.14.1"
project/build.properties:
sbt.version=1.6.1
src/main/resources/config.yml:
--- 
system: 
  port: 123
  service: name
Example of running it:
sbt:Hello> loadDevEnv
--- 
system: 
  port: 123
  service: name
Right({
  "system" : {
    "port" : 123,
    "service" : "name"
  }
})
It prints string from file followed by parsed json.
The important details are:
- You need to put library dependency in the other build file in projectdir.
- You need to use _root_because otherwise imports don't resolve if they are not part of core Scala or SBT plugins. At least I didn't find the way yet.
You can checkout the code here: https://github.com/izmailoff/sbt-import-test.
--- UPDATE ---:
I've added the full example in the repo. That required some changes since you can't update SBT settings from a Task you need to do it in a Command. Updated files below:
build.sbt:
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / organization := "com.example"
// This is needed to get new env values from SBT:
fork := true
lazy val hello = (project in file("."))
  .settings(
    name := "Hello",
    //libraryDependencies += "io.circe" %% "circe-yaml" % "0.14.1"
  )
commands += Command.command("loadDevEnv") { state =>
  val env = Config.getEnvFromConf()
  val envStr = Config.prettyPrint(env)
  s"set envVars ++= ${envStr}" :: state
}
project/Build.scala:
case class Service(port: Integer, service: String)
case class System(system: Service)
object Config {
    def getEnvFromConf(): Map[String, String] = {
        val filename = "src/main/resources/config.yml"
        val confStr = readConfig(filename)
        val conf = parseConfig(confStr)
        println(conf)
        val env = toEnv(conf)
        env
    }
    def readConfig(filename: String): String = {
      import scala.io.Source
      Source.fromFile(filename).getLines.mkString("\n") // use better way?
    }
    def parseConfig(conf: String): System = {
      import _root_.io.circe._
      import _root_.io.circe.yaml
      import _root_.io.circe.yaml._
      import _root_.io.circe.yaml.syntax._
      import _root_.io.circe.generic.auto._
      import cats.syntax.either._
      val json: Either[ParsingFailure, Json] = yaml.parser.parse(conf)
      json
        .leftMap(err => err: Error)
        .flatMap(_.as[System])
        .valueOr(throw _)
    }
    def toEnv(conf: System): Map[String, String] = {
      Map(s"service.${conf.system.service}" -> conf.system.port.toString)
    }
    def prettyPrint(x: Map[String, String]): String = {
      "Map(" + (x.map{ case (k, v) => s""""${k}"->"${v}""""}).mkString(", ") + ")"
    }
}
project.build.sbt:
val circeVersion = "0.14.1"
libraryDependencies ++= Seq(
  "io.circe" %% "circe-yaml",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-parser"
).map(_ % circeVersion)
src/main/scala/Main.scala:
object Main extends App {
  println("Found the following environment variable:")
  println(System.getenv("service.name"))
}
EXAMPLE:
Run SBT from your OS shell:
> sbt
Run the app to check if environment variables are set:
sbt:Hello> run
[info] running (fork) Main 
[info] Found the following environment variable:
[info] null
Getting null. Load env:
sbt:Hello> loadDevEnv
[info] Defining envVars
[info] The new value will be used by Compile / bspBuildTargetRun, Compile / bspScalaMainClassesItem and 6 others.
[info]  Run `last` for details.
[info] Reapplying settings...
[info] set current project to Hello (in build file:/home/tvc/repos/sbt_test/)
Run the app to check again:
sbt:Hello> run
[info] running (fork) Main 
[info] Found the following environment variable:
[info] 123
Found value 123.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source | 
|---|---|
| Solution 1 | 
