Get system property can't work well in the debug log level - sbt

I would like to get system property from command line and print it in sbt.
I used followiing code snippet
Depend.scala
val myVar = Option(System.getProperty("myVar")).getOrElse("default")
Build.sbt
val showMesg = settingKey[Unit]("Show message")
showMesg := {
sLog.value.info(myVar)
}
It works well when I use following command:
sbt -DmyVar=abc compile
[info] abc
But if I want to output it in debug log level. It can't get the system property correctly.
val showMesg = settingKey[Unit]("Show message")
showMesg := {
sLog.value.debug(myVar)
}
sbt -DmyVar=abc compile -debug
[debug] default
I just curious why I can't get the property when log level is debug.

Related

Key in Configuration : how to list Configurations and Keys?

The sbt in Action book introduces a concept of Key in Configuration
It then lists the default configurations:
Compile
Test
Runtime
IntegrationTest
Q1) Is it possible to print out a list of all Configurations from a sbt session? If not, can I find information on Configurations in the sbt documentation?
Q2) For a particular Configuration, e.g. 'Compile', is it possible to print out a list of Keys for the Configuration from a sbt session? If not, can I find information on a Configuration's Keys in the sbt documentation?
List of all configurations
For this you can use a setting like so:
val allConfs = settingKey[List[String]]("Returns all configurations for the current project")
val root = (project in file("."))
.settings(
name := "scala-tests",
allConfs := {
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList
.map(_.name)
}
This shows the name of all configurations. You can access more details about each configuration inside the map.
Output from the interactive sbt console:
> allConfs
[info] * provided
[info] * test
[info] * compile
[info] * runtime
[info] * optional
If all you want is to print them you can have a settingKey[Unit] and use println inside the setting definition.
List of all the keys in a configuration
For this we need a task (there might be other ways, but I haven't explored, in sbt I'm satisfied if something works... ) and a parser to parse user input.
All join the above setting in this snippet:
import sbt._
import sbt.Keys._
import complete.DefaultParsers._
val allConfs = settingKey[List[String]]("Returns all configurations for the current project")
val allKeys = inputKey[List[String]]("Prints all keys of a given configuration")
val root = (project in file("."))
.settings(
name := "scala-tests",
allConfs := {
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList
.map(_.name)
},
allKeys := {
val configHints = s"One of: ${
configuration.all(ScopeFilter(inAnyProject, inAnyConfiguration)).value.toList.mkString(" ")
}"
val configs = spaceDelimited(configHints).parsed.map(_.toLowerCase).toSet
val extracted: Extracted = Project.extract(state.value)
val l = extracted.session.original.toList
.filter(set => set.key.scope.config.toOption.map(_.name.toLowerCase)
.exists(configs.contains))
.map(_.key.key.label)
l
}
)
Now you can use it like:
$ sbt "allKeys compile"
If you are in interactive mode you can press tab after allKeys to see the prompt:
> allKeys
One of: provided test compile runtime optional
Since allKeys is a task it's output won't appear on the sbt console if you just "return it" but you can print it.

sbt does not differenciate between flags given to test runners

Currently:
Sbt has multiple test runners: scalaTest, junit-interface, etc..
Each test runner has it's own set of flags (scalaTest flags, junit-interface flags).
You can pass flags through sbt to the test runners, for example:
$ sbt '<project>/test-only * -- -f <out_file>' (-f is a scalaTest flag)
However, the flags seem to be passed to all test runners, even if a flag is not compatible with all test runners.
I'm also experiencing behavior contrary to what I found in the documentation. ScalaTest says the -v flag will "print the ScalaTest version" and junit-interface says it will "Log "test run started" / "test started" / "test run finished" events on log level "info" instead of "debug"." Instead ScalaTest throws an unrecognised flag exception.
$ sbt '<project>/test-only * -- -v'
java.lang.IllegalArgumentException: Argument unrecognized by ScalaTest's Runner: -v
at org.scalatest.tools.ArgsParser$.parseArgs(ArgsParser.scala:425)
at org.scalatest.tools.Framework.runner(Framework.scala:929)
...
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
[error] (elasticSearchDriver/test:testOnly) java.lang.IllegalArgumentException: Argument unrecognized by ScalaTest's Runner: -v
[error] Total time: 1 s, completed Aug 15, 2017 11:12:56 AM
Question:
What is the actual underlying behavior of the flags passed to the test runners through sbt? Is there a bit of documentation that explains what's going on?
By looking at SBT (0.13.x) we eventually get to a part where:
def inputTests(key: InputKey[_]): Initialize[InputTask[Unit]] = inputTests0.mapReferenced(Def.mapScope(_ in key.key))
private[this] lazy val inputTests0: Initialize[InputTask[Unit]] =
{
val parser = loadForParser(definedTestNames)((s, i) => testOnlyParser(s, i getOrElse Nil))
Def.inputTaskDyn {
val (selected, frameworkOptions) = parser.parsed
val s = streams.value
val filter = testFilter.value
val config = testExecution.value
implicit val display = Project.showContextKey(state.value)
val modifiedOpts = Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions: _*) +: config.options
val newConfig = config.copy(options = modifiedOpts)
val output = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value, testForkedParallel.value, javaOptions.value)
val taskName = display(resolvedScoped.value)
val trl = testResultLogger.value
val processed = output.map(out => trl.run(s.log, out, taskName))
Def.value(processed)
}
}
Notice this line: Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions: _*) +: config.options
By reading this I deduce that sbt passes the arguments you pass to it to all the underlying testing frameworks.
Solution
Don't pass test framework flags in your commands. Configure them in your *.sbt files like:
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-f")
Documentation on test framework arguments

sbt run fails in this trivial example

This is a minimal example to show a very weird thing when using sbt run. Trying to make the example any smaller, then the problem does not arises any more. Am I missing something or is this a bug or sbt?
build.sbt
name := "testsbt"
version := "1.0"
scalaVersion := "2.11.8"
project/build.properties
sbt.version = 0.13.13
src/main/scala/application/Test.scala
package application
import java.io._
case class Page(items: List[Item])
case class Item(text: String)
object Test extends App {
val page = Page(List(Item("item1")))
val filename = "./page.obj"
val oos = new ObjectOutputStream(new FileOutputStream(filename))
try oos.writeObject(page)
finally oos.close()
println("read: " + new ObjectInputStream(new FileInputStream(filename)).readObject())
}
executing this with sbt run fails:
$ sbt run
[error] (run-main-0) java.lang.ClassCastException: cannot assign instance of scala.collection.immutable.List$SerializationProxy to field application.Page.items of type scala.collection.immutable.List in instance of application.Page
however, executing sbt package and running from it works:
$ sbt package
$ scala -cp target/scala-2.11/testsbt_2.11-1.0.jar application.Test
read: Page(List(Item(item1)))

SBT Subprojects do not recognize plugin commands

I'm having an issue with getting SBT Subprojects to recognize commands provided by plugins. I have the following plugin source:
object DemoPlugin extends AutoPlugin {
override lazy val projectSettings = Seq(commands += demoCommand)
lazy val demoCommand =
Command.command("demo") { (state: State) =>
println("Demo Plugin!")
state
}
}
Which is used by a project configured as follows:
lazy val root = project in file(".")
lazy val sub = (project in file("sub")).
enablePlugins(DemoPlugin).
settings(
//...
)
The plugin is, of course, listed in project/plugins.sbt. However, when I open up sbt in the project, I see the following:
> sub/commands
[info] List(sbt.SimpleCommand#413d2cd1)
> sub/demo
[error] Expected ':' (if selecting a configuration)
[error] Not a valid key: demo (similar: doc)
[error] sub/demo
Even stranger, using consoleProject, I can see that the command in the project is the one defined by DemoPlugin!
scala> (commands in sub).eval.map { c => c.getClass.getMethod("name").invoke(c) }
res0: Seq[Object] = List(demo)
I'm looking to be able to type sub/demo, and have it perform the demo command. Any help would be much appreciated!
Commands aren't per-project. They only work for the top-level project.
It's also recommended to try and use tasks, or if needed input tasks where you might want to use a command.
If you really need a command, there's a way to have a sort of "holder" task, see the answer to Can you access a SBT SettingKey inside a Command?

How to combine InputKey and TaskKey into a new InputKey?

I have a SBT multi project which includes two sub projects. One is an ordinary Scala web server project and the other is just some web files. With my self written SBT plugin I can run Gulp on the web project. This Gulp task runs asynchronous. So with
sbt "web/webAppStart" "server/run"
I can start the Gulp development web server and my Scala backend server in parallel. Now I want to create a new task, that combines them both. So afterwards
sbt dev
for example should do the same. Here is what I tried so far:
// Build.sbt (only the relevant stuff)
object Build extends sbt.Build {
lazy val runServer: InputKey[Unit] = run in server in Compile
lazy val runWeb: TaskKey[Unit] = de.choffmeister.sbt.WebAppPlugin.webAppStart
lazy val dev = InputKey[Unit]("dev", "Starts a development web server")
// Scala backend project
lazy val server = (project in file("project-server"))
// Web frontend project
lazy val web = (project in file("project-web"))
// Root project
lazy val root = (project in file("."))
.settings(dev <<= (runServer) map { (_) => {
// do nothing
})
.aggregate(server, web)
This works so far. Now I don't have any idea, how to make dev also depend on the runWeb task. If I just add the runWeb task like
.settings(dev <<= (runWeb, runServer) map { (_, _) => {
// do nothing
})
then I get the error
[error] /Users/choffmeister/Development/shop/project/Build.scala:59:value map is not a member of (sbt.TaskKey[Unit], sbt.InputKey[Unit])
[error] .settings(dev <<= (runWeb, runServer) map { (_, _) =>
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
Can anyone help me with this please?
The optimal solution would pass the arguments given to dev to the runServer task. But I could also live with making dev a TaskKey[Unit] and then hard code to run runServer with no arguments.
tl;dr Use .value macro to execute dependent tasks or just alias the task sequence.
Using .value macro
Your case seems overly complicated to my eyes because of the pre-0.13 syntax (<<=) and the use of project/Build.scala (that often confuse not help people new to sbt).
You should just execute the two tasks in another as follows:
dev := {
runWeb.value
runServer.value
}
The complete example:
lazy val server = project
lazy val runServer = taskKey[Unit]("runServer")
runServer := {
println("runServer")
(run in server in Compile).value
}
lazy val runWeb = taskKey[Unit]("runWeb")
runWeb := {
println("runWeb")
}
lazy val dev = taskKey[Unit]("dev")
dev := {
println("dev")
}
dev <<= dev dependsOn (runServer, runWeb)
Using alias command
sbt offers alias command that...
[sbt-learning-space]> help alias
alias
Prints a list of defined aliases.
alias name
Prints the alias defined for `name`.
alias name=value
Sets the alias `name` to `value`, replacing any existing alias with that name.
Whenever `name` is entered, the corresponding `value` is run.
If any argument is provided to `name`, it is appended as argument to `value`.
alias name=
Removes the alias for `name`.
Just define what tasks/command you want to execute in an alias as follows:
addCommandAlias("devAlias", ";runServer;runWeb")
Use devAlias as if it were a built-in task:
[sbt-learning-space]> devAlias
runServer
[success] Total time: 0 s, completed Jan 25, 2015 6:30:15 PM
runWeb
[success] Total time: 0 s, completed Jan 25, 2015 6:30:15 PM

Resources