I'm trying to use sbt as a general task runner (similar to rake/npm). I can get it to parse input the way I want through an inputTask, but I'm absolutely stumped how to use this to invoke a runTask/fullRunTask
val partners: List[Parser[String]] = List("foo", "bar")
val partnerParser = partners.reduce(_ | _)
val acceptArgs = (' ' ~> partnerParser ~ (' ' ~> StringBasic))
lazy val importDump = inputKey[Unit]("Import static data dump")
lazy val importDumpTask = importDump := {
val (arg1, arg2) = acceptArgs.parsed
// how can I make this call?
// ... runTask(Compile, "foo.bar.baz.DoIt.dispatch", arg1, arg2).value
}
I understand that you can't directly call tasks from other tasks, only "depend" on them so the above code won't work.
I know I can do something like
mainClass := Some("foo.bar.baz.DoIt.dispatch")
(runMain in Compile).toTask(s" foo.bar.baz.DoIt.dispatch $arg1 $arg2").value
But that means I can't use any of the parsing/autocomplete functionality.
So my question is:
How can I parse input with an inputTask, then call a main method in my code with the resulting arguments?
This is extremely painful to do in sbt. I would recommend writing a shell script (or using sbt's built-in Process support).
That said, it's possible to do this by writing a new Command that mutates the State object provided, adding the tasks you want to run as items in the remainingCommands field.
Related
I'm creating a val into my build.sbt, made of a random string, to be used in the Setup and Cleanup methods for scalatest, like this:
val foo = Random.alphanumeric.take(3).mkString
...
Test / testOptions += Tests.Setup(() => {
// do stuff with it
})
...
Test / testOptions += Tests.Cleanup(() => {
// do stuff with the same string
}
but it seems that the two functions are actually re-evaluating the val, resulting in two different strings. It seems that the forking of the JVM (fork := true) does not play a role into it, so I'm kinda out of ideas. Is that intended and/or is there a way to fix it/finding another approach to the problem (native to Scala/sbt)?
Apparently the solution was easier than thought:
lazy val foo = SettingKey[String]("foo", "Random string")
foo := Random.alphanumeric.take(3).mkString
and then call foo.value in the sbt code after
By trial and probably error, as a plugin author I've fallen into using the following style, which seems to work:
object AmazingPlugin extends AutoPlugin {
object autoImport {
val amaze = TaskKey[Amazement]("Do something totally effing amazing")
}
import autoImport._
lazy val ethDefaults : Seq[sbt.Def.Setting[_]] = Seq(
amaze in Compile := { amazeTask( Compile ).value },
amaze in Test := { amazeTask( Test ).value }
)
def amazeTask( config : Configuration ) : Initialize[Task[Amazement]] = Def.task {
???
}
}
Unfortunately, I do not actually understand how all of these constructs work, why it is an Initialize[Task[T]] I generate rather than a Task[T], for example. I assume that this idiom "does the right thing", which I take to mean that the amazeTask function generates some wrapper or seed or generator of an immutable Task for each Configuration and binds it just once to the appropriate task key. But it is entirely opaque to me how this might work. For example, when I look up Initialize, I see a value method that requires an argument, which I do not supply in the idiom above. I assume in designing the SBT configuration DSL tricks with implicits and/or macros were used, and shrug it off.
However, recently I've wanted to factor out some logic from my tasks into what are logically private functions, but which also require access to the value of tasks and settings. If just used private functions, gathering all the arguments would become repetitive boilerplate of the form
val setting1 = (setting1 in config).value
val setting2 = (setting2 in config).value
...
val settingN = (settingN in config).value
val derivedValue = somePrivateFunction( setting1, setting2 ... settingN )
everywhere I need to use get the value derived from settings. So it's better to factor all this into a derivedValue task, and I can replace all of the above with
derivedValue.value
Kewl.
But I do want derivedValue to be private, so I don't bind it to a task key. I just do this:
private def findDerivedValueTask( config : Configuration ) : Initialize[Task[DerivedValue]] = Def.task {
val setting1 = (setting1 in config).value
val setting2 = (setting2 in config).value
...
val settingN = (settingN in config).value
somePrivateFunction( setting1, setting2 ... settingN )
}
Now it works fine to implement my real, public task as...
def amazeTask( config : Configuration ) : Initialize[Task[Amazement]] = Def.task {
val derivedValue = findDerivedValueTask( config ).value
// do stuff with derivedValue that computes and Amazement object
???
}
Great! It really does work just fine.
But I have a hard time presuming that there is some magic by which this idiom does the right thing, that is generate an immutable Task object just once per config and reuse it. So, I think to myself, I should memoize the findDerivedValueTask function, so that after it generates a task, the task gets stored in a Map, whose keys are Configurations but whose values are logically Tasks.
But now my nonunderstanding of what happens behind the scenes bites. Should I store Initialize[Task[DerivedValue]] or just Task[DerivedValue], or what? Do I have to bother, does sbt have some clever magic that is already taking care of this for me? I really just don't know.
If you have read this far, I am very grateful. If you can clear this up, or point me to documentation that explains how this stuff works, I'll be even more grateful. Thank you!
Let's say I have a helper method defModules that accepts some String prefix and returns a tuple/collection of modules to define, like api/impl/etc:
def defModules(prefix: String): (Project, Project, ...) = ???
or
def defModules(prefix: String): Seq[Project] = ???
It is possible to define multiple projects on single line in build.sbt by invoking defModules helper?
I tried something like
val (fooApi, fooImpl) = defModules("foo")
but got an error saying it's impossible.
Back then in sbt 0.13.13 or earlier, that was possible and I personally used it a lot. It's no longer supported.
See https://github.com/sbt/sbt/issues/2290 and the code responsible for the error.
Your best bet is to do the following:
val modules = defModules("foo")
val fooApi = modules._1
val fooImpl = modules._2
Can someone help me create a SBT task that can support property-like arguments from command line?
lazy val myTask = inputKey[Unit]("my task")
myTask := {
if (directoryOpt.isEmpty) // directoryOpt comes from an optional command line argument: directory="~/downloads"
fullRunInputTask(inputKey, Compile, "example.MyTaskClass")
else
fullRunInputTask(inputKey, Compile, "example.MyTaskClass", directoryOpt.get)
}
Where the task can be run from command line like:
sbt myTask directory="~/downloads"
I did read the sbt doc at http://www.scala-sbt.org/0.13/docs/Input-Tasks.html. But it only explains how to create a task parser like sbt myTask option1 option2 which does not quite meet my need.
UPDATE:
I used jazmit's solution since that was an easy change. It works well! I will also try Mariusz's solution and update here.
You can use project/Build.scala along your build.sbt with your inputs. You can also use Commands instead of Tasks. Below, an example:
import sbt._
import Keys._
object CustomBuild extends Build {
def myTask = Command.args("myTask", "<name>"){ (state, args) =>
val argMap = args.map { s =>
s.split("=").toList match {
case n :: v :: Nil => n -> v
}
}.toMap
//println(argMap) //to see all argument pairs
//react on name in params list
println("Hi "+ argMap.getOrElse("name", "Unknown"))
state //Command can modify state, so you must to return it.
}
}
Now You have to add this command to you project, in build.sbt add
commands += myTask
Now you can use it:
> sbt "myTask name=Mario"
> Hi Mario
> sbt myTask
> sbt Hi Unknown
Hope, it'll help You!
more about commands:
you can find here
You can use environmental properties to achieve what you want quickly.
From the command line, set a property as follows:
sbt myTask -Ddirectory="~/downloads"
From the task, you can retrieve the value as follows:
val directory = System.getProperty("directory");
If you want to do something more solid with syntax checking, tab completion, etc, you can define an input task as detailed here. If you need property=value syntax, you can define this using the parser combinator library, eg:
import sbt.complete.DefaultParsers._
val myArgs: Parser[String] = "directory=" ~> StringEscapable
I want to create a new custom InputTask (testOnlyCustom)
that calls testOnly with the same arguments as given to testOnlyCustom and
that maybe, based on a SBT setting (condition), calls another task (let's call it pre) before calling testOnly. Here, I have to force "sequential" execution.
Thus:
If condition is true
testOnlyCustom com.dummy.TestSuite calls
pre and then
testOnly com.dummy.TestSuite
If condition is false
testOnlyCustom com.dummy.TestSuite calls
testOnly com.dummy.TestSuite
While I was able to achieve a solution with testCustom referring to pre and test (and thus having no arguments), I'm not able to solve the problem for testOnlyCustom, as InputTask used
Here is my code:
import sbt._
import sbt.Keys._
import sbt.Def._
import sbtsequential.Plugin._
object Simple extends sbt.Plugin {
import SimpleKeys._
object SimpleKeys {
lazy val condition = SettingKey[Boolean]("mode", "The mode.")
lazy val pre = TaskKey[Unit]("test-with-pre", "Do some pre step.")
lazy val testWithPre = TaskKey[Unit]("test-with-pre", "Run pre task beforehand")
lazy val testCustom = TaskKey[Unit]("test-custom", "Run pre (depending on condition) and then test.")
lazy val testOnlyWithPre = InputKey[Unit]("test-only-with-pre", "Run selected tests (like test-only in SBT) with pre executed before.")
lazy val testOnlyCustom = InputKey[Unit]("test-only-configured", "Run pre (depending on condition) and then call test-only.")
}
lazy val baseSettings: Seq[sbt.Def.Setting[_]] = Seq(
// this is working
testWithPre := test.value,
testWithPre <<= testWithPre.dependsOn( pre ),
testCustom := Def.taskDyn {
val c = condition.value
if (c) {
testWithPre
} else {
test
}
}.value,
//
// this is the part, where my question focuses on
//
testOnlyWithPre := testOnly.evaluated,
testOnlyWithPre <<= testOnlyWithPre.dependsOn( pre ),
// is this the correct approach?
testOnlyCustom := Def.inputTaskDyn {
// ???????????????????????????????
Def.task()
}.evaluated
)
lazy val testSimpleSettings: Seq[sbt.Def.Setting[_]] = baseSettings
}
Is inputTaskDyn the way to go? What exactly does it? I have just chosen it, because it seems to be dynamic version for InputTasks. Unfortunately, documentation is very rare on inputTaskDyn.
Is it okay to force "sequential" execution via dependsOn, like I did? I already have seen tha SBT 0.13.8 contains Def.sequantial. But this does not seem to be applicable to InputTasks?
How to convert an InputTask into a Task (to be used with taskDyn / inputTaskDyn) but still sticking to evaluated instesd of using an explicit parser? Or is there a way to reuse the testOnly parser?
Could someone illustrate a little more on .evaluated and .parsed of InputTask. What exactly does InputTask.parse do under the hood?
It would be great if someone could provide a working solution!
Many thanks in advance
Martin
Just for the record, the SBT 1 equivalent is
testOnlyWithPre := test.dependsOn(pre).value
and
testOnlyWithPre := testOnly.dependsOn(pre).evaluated
the best solution I could come up with is
testOnlyCustom := Def.inputTaskDyn {
val args: Seq[String] = spaceDelimited("").parsed
val c = condition.value
if (c) {
testOnlyWithPre.toTask(" " + args.head)
} else {
testOnly.toTask(" " + args.head)
}
}.evaluated
But still, this forces me to use a new parser (spaceDelimited) and I am not able to (re-)use the testOnly parser.
Any ideas how to reuse the parser?
Additional comments
First, OlegYch_ indicated on Freenode # sbt that coming up with SBT 0.13.9 the execution of Inputtasks will be possible via
def runInputTask[T](key: InputKey[T], input: String, state: State): (State, T)
in sbt.Extracted.scala.
Second, the testOnly parsers can be reused via sbt.Defaults#inputTests.