Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Typesafe Config's ConfigFactory to set key setting in build.sbt?

Tags:

scala

sbt

sbt.version=0.13.1

In build.sbt I am assigning a setting key by calling a piece of my project dependency's code that in turn configures itself via Typesafe Config's ConfigFactory. My dependency has a reference.conf in the root of the jar, and my project itself contains an overriding application.conf in src/main/resources.

The lib/dependency is also my code, btw.

import com.mylib.Finders
import com.myproj.sbt.Keys._

projKeyColorSetting in Compile := Finders.findColor // this calls ConfigFactory.load

seq(projSettings:_*)

The build doesn't even load because it can't find the first conf key I attempt to reference in my lib code.

I've tried a number of combinations of scoping and Classpath manipulation in my build file, but to no avail. I assumed that the jar's reference.conf would have been on the Compile scope's classpath but it doesn't work as I expect.

I spent the majority of yesterday poring over SBT documentation on Classpath, Scopes, Keys, Tasks, and ResourceGenerators - my intention is to execute a custom plugin that relies on the projKeyColorSetting setting in build.sbt as follows:

lazy val projSettings = inConfig(Compile) {
    Seq(
        resourceGenerators in Compile <+= Def.task {
            val fileCreated = createColorFile(projKeyColorSetting.value)
            Seq(fileCreated)
        }
    )
 }
like image 407
bloo Avatar asked Mar 26 '14 13:03

bloo


2 Answers

If you are getting a class from foo.jar, then ConfigFactory.load() should get a reference.conf found in the same jar. If it doesn't, then something is wrong but it's hard to guess what. It could be that reference.conf has some invalid syntax in it possibly; it could be that reference.conf isn't in the jar; it could be that reference.conf is in a subdirectory instead of root of the jar; hard to guess. I'd try -Dconfig.trace=loads to look for problems in there (it should tell you whether config tries to load the reference.conf for example). You could also do your own classLoader.getResources and see if you can find the file without config involved. You could also try ConfigFactory.parseResourcesAnySyntax("reference") and see if your reference settings are in there, and try calling ConfigFactory.load directly and see if your settings are in there. Just in general, double-check all assumptions and see where it goes wrong.

As for how to add src/main/resources, the two basic strategies would be 1) to get it on the classpath somehow (which is probably difficult in this case; you would need it before even launching sbt or would need to do some kind of custom ClassLoader fun) or probably more practical 2) load it manually with ConfigFactory.parseFile().

I would probably grab the resourceDirectory key as a dependency of your task and then do something like (untested):

myTask := {
   val resourceDir = (resourceDirectory in Compile).value
   val appConfig = ConfigFactory.parseFile(resourceDir / "application.conf")
   val config = ConfigFactory.load(appConfig) // puts reference.conf underneath
   Finders.findColor(config)
}

Note that this involves changing findColor to take a Config parameter, or maybe you would prefer to make Finders a non-singleton that can be constructed with a Config; see the example at https://github.com/typesafehub/config/blob/master/examples/scala/simple-lib/src/main/scala/simplelib/SimpleLib.scala#L22 where I was trying to illustrate that when using a Config usually a library should both default to ConfigFactory.load but also have a constructor that allows a custom Config for situations like this.

like image 174
Havoc P Avatar answered Sep 28 '22 08:09

Havoc P


I think it's a bug in sbt.

Here's my understanding of your use case and how sbt ultimately behaved.

project/build.properties

sbt.version=0.13.5-M2

The folder config-only-project is for a project with the following two files - build.sbt and src/main/resources/application.conf. This is to simulate an external dependency on a project with application.conf inside.

build.sbt in config-only-project

libraryDependencies += "com.typesafe" % "config" % "1.2.0"

src/main/resources/application.conf in config-only-project

app-name {
  hello = "Hello from Typesafe Config"
}

The following files configure the default plugins project as well as the build configuration itself (and hence the build for the project under investigation).

project/build.sbt

lazy val configOnlyProject = uri("../config-only-project")

lazy val plugins = project in file(".") dependsOn (configOnlyProject)

project/build.scala

import sbt._
import Keys._
import com.typesafe.config._

object build extends Build {
  lazy val mySetting = taskKey[String]("Setting using Typesafe Config")
  lazy val myS = mySetting := {
    // Compiler issue Config conf???
    println((fullClasspath in Compile).value)
    val conf = ConfigFactory.load()
    conf getString "app-name.hello"
  }
  lazy val configOnlyProject = uri("config-only-project")
  lazy val root = project in file(".") settings (myS) dependsOn (configOnlyProject)
}

This gives the following directory structure:

jacek:~/sandbox/so/setting-typesafe-config
$ tree
.
├── config-only-project
│   ├── build.sbt
│   ├── project
│   └── src
│       └── main
│           └── resources
│               └── application.conf
└── project
    ├── application.conf
    ├── build.properties
    ├── build.sbt
    └── build.scala

6 directories, 6 files

What I couldn't understand was that the setting itself didn't work - neither for the main project nor for the plugins project.

> mySetting
List(Attributed(/Users/jacek/sandbox/so/setting-typesafe-config/target/scala-2.10/classes), Attributed(/Users/jacek/sandbox/so/setting-typesafe-config/config-only-project/target/scala-2.10/classes), Attributed(/Users/jacek/.sbt/boot/scala-2.10.4/lib/scala-library.jar), Attributed(/Users/jacek/.ivy2/cache/com.typesafe/config/bundles/config-1.2.0.jar))
[trace] Stack trace suppressed: run last root/*:mySetting for the full output.
[error] (root/*:mySetting) com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'app-name'
[error] Total time: 0 s, completed Mar 31, 2014 10:24:12 PM

The error was as follows:

> last root/*:mySetting
com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'app-name'
    at com.typesafe.config.impl.SimpleConfig.findKey(SimpleConfig.java:124)
    at com.typesafe.config.impl.SimpleConfig.find(SimpleConfig.java:147)
    at com.typesafe.config.impl.SimpleConfig.find(SimpleConfig.java:159)
    at com.typesafe.config.impl.SimpleConfig.find(SimpleConfig.java:164)
    at com.typesafe.config.impl.SimpleConfig.getString(SimpleConfig.java:206)
    at build$$anonfun$myS$1.apply(build.scala:11)
    at build$$anonfun$myS$1.apply(build.scala:7)
    at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
    at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:42)
    at sbt.std.Transform$$anon$4.work(System.scala:64)
    at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237)
    at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:237)
    at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18)
    at sbt.Execute.work(Execute.scala:244)
    at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237)
    at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:237)
    at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:160)
    at sbt.CompletionService$$anon$2.call(CompletionService.scala:30)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:744)
[error] (root/*:mySetting) com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'app-name'

It did work when I executed the same code in the Scala console:

> console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import com.typesafe.config._
import com.typesafe.config._

scala> ConfigFactory.load()
res0: com.typesafe.config.Config = ...
scala> res0 getString "app-name.hello"
res1: String = Hello from Typesafe Config

When I switched to the plugins project it worked fine, too:

> reload plugins
[info] Loading project definition from /Users/jacek/sandbox/so/setting-typesafe-config/project
> console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import com.typesafe.config._
import com.typesafe.config._

scala> ConfigFactory.load()
res0: com.typesafe.config.Config = ...
scala> res0 getString "app-name.hello"
res1: String = Hello from Typesafe Config

I wish I could explain it, but it seems too much cognitive load for me :(

like image 24
Jacek Laskowski Avatar answered Sep 28 '22 09:09

Jacek Laskowski