I want to define a task which runs MyMainClass defined in the same module where task is defined, passing task command line to it:
$ sbt
> project myModule
> myKey2 someArgument
...compiles MyMainClass
...runs MyMainClass.main("someArgument")
Without command line args, this works:
val myKey1 = taskKey[Unit]("myKey1")
lazy val myModule = project.settings(
myKey1 <<= runTask(Compile, "MyMainClass", "mode1"),
myKey1 <<= myKey1.dependsOn(compile in Compile)
)
But I could not make it with command line args. Trying to use Def.spaceDelimited().parsed with taskKey gives me compilation error explicitly saying that I must use inputKey instead; trying to use <<= with inputKey does not compile either; this compiles but does not work:
val myKey2 = inputKey[Unit]("myKey2")
lazy val myModule = project.settings(
...
myKey2 := runTask(
Compile, "MyMainClass", "mode2",
{
val args = Def.spaceDelimited().parsed.head)
// This line is executed, but MyMainClass.main() is not:
System.err.println("***** args=" + args)
args.head
}
),
myKey2 <<= myKey2.dependsOn(compile in Compile)
)
Tried SBT 0.13.7 and 0.13.9. Please help. Thanks. :)
UPD. Or maybe I'm doing this completely wrong (deprecated) way? I could not find SBT 0.13 docs mention <<= at all.
Rewritten in new style (:= instead of <<=).
This worked:
myKey1 := {
// Works without this line, but kept it for clarity and just in case:
val _ = (compile in Compile).value
runTask(Compile, "MyMainClass1", "mode1").value
},
myKey2 := {
val _ = (compile in Compile).value
runInputTask(Compile, "MyMainClass", "mode2").evaluated
}
BTW directly accessing .value in procedural style feels much conceptually simpler than old ways I used to use (I guess that was before SBT has been rewritten using macros).
Related
I have a sbt project that includes code generation.
Part of the build.sbt is
lazy val generator = (project in file("generator")).
settings(mainClass := Some("com.example.Generator"))
lazy val generate = (project in file("generate")).
dependsOn(generator).
settings(runGeneration)
def runGeneration: SettingsDefinition = sourceGenerators in Compile += Def.taskDyn {
val cachedFun = FileFunction.cached(
streams.value.cacheDirectory / "generation"
) { (in: Set[File]) =>
val dir = (sourceManaged in Compile).value
(generator / run in Compile).toTask(" " + dir.getAbsolutePath).value
collectFiles(dir)
}
val dependentFiles = ((generator / fullClasspath in Compile) map { cp => cp.files }).taskValue.value
val genFiles = cachedFun(dependenctFiles).toSeq
Def.task {
genFiles
}
}.taskValue
This seems to work and only generate files when a dependency has changed. However, I expect to have multiple generators. Rather than copy the code, I attempted to pass the generator project to it:
lazy val generate = (project in file("generate")).
dependsOn(generator).
settings(runGeneration(generator))
def runGeneration(p: project): SettingsDefinition =
<same as before but with p instead of generator>
This results in an error parsing the build file:
build.sbt:155: error: Illegal dynamic reference: File
val dependentFiles = ((p / fullClasspath in Compile) map { cp => cp.files }).taskValue.value
^
[error] sbt.compiler.EvalException: Type error in expression
[error] sbt.compiler.EvalException: Type error in expression
I am guessing the problem is that it cannot figure out at compile time if there is a dependency loop, so it convervatively gives an error.
Is there a way to get this to work? Is there an entirely different construct that lets me know if running generator will produce a different result?
The underlying problem is that task definitions in sbt have two components, which look like they can be intermingled, but cannot. If you write code like
Def.task {
val doIt = checkIfShouldDoIt()
if (doIt) {
someTask.value
} else {
()
}
}
this naively looks like it will only run someTask if doIt is true. What actually happens is that someTask.value declares a dependency of this task on someTask and someTask is run before anything is done for this task. To write the above code in a way that more directly maps to what actually happens, one would write
Def.task {
val someTaskValue = someTask.value
val doIt = checkIfShouldDoIt()
if (doIt) {
someTaskValue
} else {
()
}
}
The attempt to run the task only when the dependencies had changed could not work in a single task.
My working solution does the following. I modified the generator to take an additional argument and do nothing if that argument was false. The two tasks were
// Task to check if we need to generate
def checkGeneration(p: Project) = Def.taskDyn {
var needToGenerate = false
val cachedFunction = FileFunction.cached(someDir) {
(in: Set[File]) =>
needToGenerate = ! in.isEmpty
Set()
}
val dependentFiles = ((p / fullClasspath in Compile) map { cp => cp.files }).taskValue
Def.task {
cachedFun(dependentFiles.value.toSet)
needToGenerate
}
}
// Task to run generation
def runGeneration(p: Project): SettingsDefinition = sourceGenerators in Compile += Def.taskDyn {
val needToGenerate = checkGeneration(p).value
Def.task {
// Run generator as before but pass needToGenerate as additional argument
...
// Used FileFunction.cached as before to find the generated files (but not run the generator)
...
}
}
It is possible that I have more dynamic tasks than I need, but this works.
I want my scalacSettings to be more strict (more linting) when I issue my own command validate.
What is the best way to achieve that?
A new scope (strict) did work, but it requires to compile the project two times when you issue test. So that's not a option.
SBT custom command allows for temporary modification of build state which can be discarded after command finishes:
def validate: Command = Command.command("validate") { state =>
import Project._
val stateWithStrictScalacSettings =
extract(state).appendWithSession(
Seq(Compile / scalacOptions ++= Seq(
"-Ywarn-unused:imports",
"-Xfatal-warnings",
"...",
))
,state
)
val (s, _) = extract(stateWithStrictScalacSettings).runTask(Test / test, stateWithStrictScalacSettings)
s
}
commands ++= Seq(validate)
or more succinctly using :: convenience method for State transformations:
commands += Command.command("validate") { state =>
"""set scalacOptions in Compile := Seq("-Ywarn-unused:imports", "-Xfatal-warnings", "...")""" ::
"test" :: state
}
This way we can use sbt test during development, while our CI hooks into sbt validate which uses stateWithStrictScalacSettings.
Since SBT 0.13.13 this is deprecated (<<= is deprecated):
compile in Compile <<= (compile in Compile).dependsOn(apiDoc)
So the only way to do I found is this:
compile in Compile := {
apiDoc.value
(compile in Compile).value
}
But now I have a warning about a useless expression apiDoc.value.
But this is not useless!
I can't find any documentation about what is the new way to do.
I haven't found docs for this, but you can create a dependsOn like:
compile.in(Compile) := compile.dependsOn(apiDoc).value
Note that if you're doing this for an InputTask, you'll need to use evaluated instead of value:
myInputTask := myInputTask.dependsOn(apiDoc).evaluated
I've struggled with the problem of depending tasks and it all became clear after I read this page: http://www.beyondthelines.net/computing/understanding-sbt/
TLD;DR: to make a task dependent on another inside a task definition, you have to use Def.sequential (examples from the page):
lazy val A = taskKey[Unit]("Task A")
A in Global := { println("Task A") }
lazy val B = taskKey[Unit]("Task B")
B in Global := { println("Task B") }
lazy val C = taskKey[Unit]("Task C")
C := Def.sequential(A, B).value
So for your case, it would be:
compile in Compile := Def.sequential(apiDoc, compile in Compile).value
Or, if you use the new sbt syntax and have different scoping:
ThisBuild / Compile / compile := Def.sequential(apiDoc, Compile / compile).value
I have a Scala project that is divided into several subprojects:
lazy val core: Seq[ProjectReference] = Seq(common, json_scalaz7, json_scalaz)
I'd like to make the core lazy val conditional on the Scala version I'm currently using, so I tried this:
lazy val core2: Seq[ProjectReference] = scalaVersion {
case "2.11.0" => Seq(common, json_scalaz7)
case _ => Seq(common, json_scalaz7, json_scalaz)
}
Simply speaking, I'd like to exclude json_scalaz for Scala 2.11.0 (when the value of the scalaVersion setting is "2.11.0").
This however gives me the following compilation error:
[error] /home/diego/work/lift/framework/project/Build.scala:39: type mismatch;
[error] found : sbt.Project.Initialize[Seq[sbt.Project]]
[error] required: Seq[sbt.ProjectReference]
[error] lazy val core2: Seq[ProjectReference] = scalaVersion {
[error] ^
[error] one error found
Any idea how to solve this?
Update
I'm using sbt version 0.12.4
This project is the Lift project, which compiles against "2.10.0", "2.9.2", "2.9.1-1", "2.9.1" and now we are working on getting it to compile with 2.11.0. So creating a compile all task would not be practical, as it would take a really long time.
Update 2
I'm hoping there is something like this:
lazy val scala_xml = "org.scala-lang.modules" %% "scala-xml" % "1.0.1"
lazy val scala_parser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1"
...
lazy val common =
coreProject("common")
.settings(description := "Common Libraties and Utilities",
libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12),
libraryDependencies <++= scalaVersion {
case "2.11.0" => Seq(scala_xml, scala_parser)
case _ => Seq()
}
)
but for the projects list
Note how depending on the scala version, I add the scala_xml and scala_parser_combinator libraries
You can see the complete build file here
Cross building a project
Simply speaking, I'd like to exclude json_scalaz for Scala 2.11.0
The built-in support in sbt for this is called cross building, which is described in Cross-Building a Project. Here's from the section with a bit of correction:
Define the versions of Scala to build against in the crossScalaVersions setting. For example, in a .sbt build definition:
crossScalaVersions := Seq("2.10.4", "2.11.0")
To build against all versions listed crossScalaVersions, prefix the action to run with +. For example:
> +compile
Multiple-project builds
sbt also has built-in support to aggregate tasks across multiple projects, which is described Aggregation. If what you need eventually is normal built-in tasks like compile and test, you could set up a dummy aggregate without json_scalaz.
lazy val withoutJsonScalaz = (project in file("without-json-scalaz")).
.aggregate(liftProjects filterNot {_ == json_scalaz}: _*)
From the shell, you should be able to use this as:
> ++2.11.0
> project withoutJsonScalaz
> test
Getting values from multiple scopes
Another feature you might be interested in is ScopeFilter. This has the ability to traverse multiple projects beyond usual aggregation and cross building. You would need to create a setting whose type is ScopeFilter and set it based on scalaBinaryVersion.value. With scope filters, you can do:
val coreProjects = settingKey[ScopeFilter]("my core projects")
val compileAll = taskKey[Seq[sbt.inc.Analysis]]("compile all")
coreProjects := {
(scalaBinaryVersion.value) match {
case "2.10" => ScopeFilter(inProjects(common, json_scalaz7, json_scalaz))
}
}
compileAll := compileAllTask.value
lazy val compileAllTask = Def.taskDyn {
val f = coreProjects.value
(compile in Compile) all f
}
In this case compileAll would have the same effect as +compile, but you could aggregate the result and do something interesting like sbt-unidoc.
I would like to configure sbt-assembly to skip a specific test class.
Is there any way to do this? If it helps, I tagged the test using ScalaTest #Network tag.
See Additional test configurations with shared sources. This allows you to come up with alternative "test" task in FunTest configuration while reusing your test source.
After you have fun:test working with whatever filter you define using testOptions in FunTest := Seq(Tests.Filter(itFilter)), you can then rewire
test in assembly := test in FunTest
Eugene is right (obviously) but that wasn't quite enough information for me to get it to work - I have a build.scala file. I am defining baseSettings like this:
val baseSettings = Defaults.defaultSettings ++
buildSettings ++
Seq(sbt.Keys.test in assembly := {})
You can tag your tests with ignore, then sbt/ScalaTest won't run them. See ScalaTest docs on Tagging tests.
Just for completeness, if you want to skip all tests in assembly task or run only particular ones you can customize it with test in assembly := { ... }
Based on #eugene-yokata reply, I found how to use the flag from ScalaTest:
lazy val UnitTest = config("unit") extend (Test)
lazy val companyApp = (project in file("applications/"))
.assembly("com.company.app", "app.jar")
.configs(UnitTest)
.settings(
inConfig(UnitTest)(Defaults.testTasks),
UnitTest / testOptions ++= Seq(
Tests.Argument(
TestFrameworks.ScalaTest,
"-l",
"com.company.tag.HttpIntegrationTest"
),
Tests.Argument(
TestFrameworks.ScalaTest,
"-l",
"com.company.tag.EsIntegrationTest"
)
),
test in assembly := (UnitTest / test).value
)