New method `toTask` on `Initialize[InputTask[T]]` to apply the full input and get a plain task out.

This commit is contained in:
Mark Harrah 2013-10-18 16:49:34 -04:00
parent e3e95f902d
commit 9dcb8727d8
6 changed files with 109 additions and 20 deletions

View File

@ -4,6 +4,7 @@ package sbt
import complete.Parser
import java.io.File
import Scope.ThisScope
import KeyRanks.{DTask, Invisible}
/** A concrete settings system that uses `sbt.Scope` for the scope type. */
object Def extends Init[Scope] with TaskMacroExtra
@ -87,6 +88,17 @@ object Def extends Init[Scope] with TaskMacroExtra
def settingKey[T](description: String): SettingKey[T] = macro std.KeyMacro.settingKeyImpl[T]
def taskKey[T](description: String): TaskKey[T] = macro std.KeyMacro.taskKeyImpl[T]
def inputKey[T](description: String): InputKey[T] = macro std.KeyMacro.inputKeyImpl[T]
private[sbt] def dummy[T: Manifest](name: String, description: String): (TaskKey[T], Task[T]) = (TaskKey[T](name, description, DTask), dummyTask(name))
private[sbt] def dummyTask[T](name: String): Task[T] =
{
import std.TaskExtra.{task => newTask, _}
val base: Task[T] = newTask( sys.error("Dummy task '" + name + "' did not get converted to a full task.") ) named name
base.copy(info = base.info.set(isDummyTask, true))
}
private[sbt] def isDummy(t: Task[_]): Boolean = t.info.attributes.get(isDummyTask) getOrElse false
private[sbt] val isDummyTask = AttributeKey[Boolean]("is-dummy-task", "Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.", Invisible)
private[sbt] val (stateKey, dummyState) = dummy[State]("state", "Current build state.")
}
// these need to be mixed into the sbt package object because the target doesn't involve Initialize or anything in Def
trait TaskMacroExtra

View File

@ -30,6 +30,20 @@ object InputTask
implicit class InitializeInput[T](i: Initialize[InputTask[T]]) {
def partialInput(in: String): Initialize[InputTask[T]] = i(_ partialInput in)
def fullInput(in: String): Initialize[InputTask[T]] = i(_ fullInput in)
import std.FullInstance._
def toTask(in: String): Initialize[Task[T]] = flatten(
(Def.stateKey zipWith i) ( (sTask, it) =>
sTask map ( s =>
Parser.parse(in, it.parser(s)) match {
case Right(t) => Def.value(t)
case Left(msg) =>
val indented = msg.lines.map(" " + _).mkString("\n")
sys.error(s"Invalid programmatic input:\n$indented")
}
)
)
)
}
implicit def inputTaskParsed[T](in: InputTask[T]): std.ParserInputTask[T] = ???

View File

@ -4,9 +4,9 @@
package sbt
import java.io.File
import Def.{displayFull, ScopedKey, Setting}
import Def.{displayFull, dummyState, ScopedKey, Setting}
import Keys.{streams, Streams, TaskStreams}
import Keys.{dummyRoots, dummyState, dummyStreamsManager, executionRoots, pluginData, streamsManager, taskDefinitionKey, transformState}
import Keys.{dummyRoots, dummyStreamsManager, executionRoots, pluginData, streamsManager, taskDefinitionKey, transformState}
import Project.richInitializeTask
import Scope.{GlobalScope, ThisScope}
import Types.const

View File

@ -325,11 +325,17 @@ object Keys
val cancelable = SettingKey[Boolean]("cancelable", "Enables (true) or disables (false) the ability to interrupt task execution with CTRL+C.", BMinusSetting)
val settingsData = std.FullInstance.settingsData
val streams = TaskKey[TaskStreams]("streams", "Provides streams for logging and persisting data.", DTask)
val isDummyTask = AttributeKey[Boolean]("is-dummy-task", "Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.", Invisible)
val taskDefinitionKey = AttributeKey[ScopedKey[_]]("task-definition-key", "Internal: used to map a task back to its ScopedKey.", Invisible)
val (executionRoots, dummyRoots)= dummy[Seq[ScopedKey[_]]]("execution-roots", "The list of root tasks for this task execution. Roots are the top-level tasks that were directly requested to be run.")
val (state, dummyState) = dummy[State]("state", "Current build state.")
val (streamsManager, dummyStreamsManager) = dummy[Streams]("streams-manager", "Streams manager, which provides streams for different contexts.")
val (executionRoots, dummyRoots)= Def.dummy[Seq[ScopedKey[_]]]("execution-roots", "The list of root tasks for this task execution. Roots are the top-level tasks that were directly requested to be run.")
val state = Def.stateKey
@deprecated("Implementation detail.", "0.13.1")
val isDummyTask = Def.isDummyTask
@deprecated("Implementation detail.", "0.13.1")
val dummyState = Def.dummyState
val (streamsManager, dummyStreamsManager) = Def.dummy[Streams]("streams-manager", "Streams manager, which provides streams for different contexts.")
val stateStreams = AttributeKey[Streams]("streams-manager", "Streams manager, which provides streams for different contexts. Setting this on State will override the default Streams implementation.")
val resolvedScoped = Def.resolvedScoped
val pluginData = TaskKey[PluginData]("plugin-data", "Information from the plugin build needed in the main build definition.", DTask)
@ -344,11 +350,10 @@ object Keys
type Streams = std.Streams[ScopedKey[_]]
type TaskStreams = std.TaskStreams[ScopedKey[_]]
def dummy[T: Manifest](name: String, description: String): (TaskKey[T], Task[T]) = (TaskKey[T](name, description, DTask), dummyTask(name))
def dummyTask[T](name: String): Task[T] =
{
val base: Task[T] = task( sys.error("Dummy task '" + name + "' did not get converted to a full task.") ) named name
base.copy(info = base.info.set(isDummyTask, true))
}
def isDummy(t: Task[_]): Boolean = t.info.attributes.get(isDummyTask) getOrElse false
@deprecated("Implementation detail.", "0.13.1")
def dummy[T: Manifest](name: String, description: String): (TaskKey[T], Task[T]) = Def.dummy(name, description)
@deprecated("Implementation detail.", "0.13.1")
def dummyTask[T](name: String): Task[T] = Def.dummyTask(name)
@deprecated("Implementation detail.", "0.13.1")
def isDummy(t: Task[_]): Boolean = Def.isDummy(t)
}

View File

@ -14,9 +14,9 @@ package sbt
import Compiler.{Compilers,Inputs}
import inc.{FileValueCache, Locate}
import Project.{inScope,makeSettings}
import Def.{ScopedKey, ScopeLocal, Setting}
import Def.{isDummy, ScopedKey, ScopeLocal, Setting}
import Keys.{appConfiguration, baseDirectory, configuration, fullResolvers, fullClasspath, pluginData, streams, thisProject, thisProjectRef, update}
import Keys.{exportedProducts, isDummy, loadedBuild, onLoadMessage, resolvedScoped, sbtPlugin, scalacOptions, taskDefinitionKey}
import Keys.{exportedProducts, loadedBuild, onLoadMessage, resolvedScoped, sbtPlugin, scalacOptions, taskDefinitionKey}
import tools.nsc.reporters.ConsoleReporter
import Build.analyzed
import Attributed.data

View File

@ -194,6 +194,7 @@ Preapplying input
=================
Because `InputTasks` are built from `Parsers`, it is possible to generate a new `InputTask` by applying some input programmatically.
(It is also possible to generate a `Task`, which is covered in the next section.)
Two convenience methods are provided on `InputTask[T]` and `Initialize[InputTask[T]]` that accept the String to apply.
* `partialInput` applies the input and allows further input, such as from the command line
@ -212,18 +213,16 @@ NOTE: the current implementation of `:=` doesn't actually support applying input
::
val run2 = inputKey[Unit]("Runs the main class twice: " +
lazy val run2 = inputKey[Unit]("Runs the main class twice: " +
"once with the project name and version as arguments"
"and once with command line arguments preceded by hard coded values.")
// The argument string for the first run task is ' <name> <version>'
val firstInput: Initialize[String] =
lazy val firstInput: Initialize[String] =
Def.setting(s" ${name.value} ${version.value}")
// Make the first arguments to the second run task ' red blue'
val secondInput: String = " red blue"
val separator: Parser[String] = "--"
lazy val secondInput: String = " red blue"
run2 := {
val one = (run in Compile).fullInput(firstInput.value).evaluated
@ -243,3 +242,62 @@ For a main class Demo that echoes its arguments, this looks like:
red
blue
green
Get a Task from an InputTask
============================
The previous section showed how to derive a new `InputTask` by applying input.
In this section, applying input produces a `Task`.
The `toTask` method on `Initialize[InputTask[T]]` accepts the `String` input to apply and produces a task that can be used normally.
For example, the following defines a plain task `runFixed` that can be used by other tasks or run directly without providing any input, ::
lazy val runFixed = taskKey[Unit]("A task that hard codes the values to `run`")
runFixed := {
val _ = (run in Compile).toTask(" blue green").value
println("Done!")
}
For a main class Demo that echoes its arguments, running `runFixed` looks like:
::
$ sbt
> runFixed
[info] Running Demo blue green
blue
green
Done!
Each call to `toTask` generates a new task, but each task is configured the same as the original `InputTask` (in this case, `run`) but with different input applied.
For example, ::
lazy val runFixed2 = taskKey[Unit]("A task that hard codes the values to `run`")
fork in run := true
runFixed2 := {
val x = (run in Compile).toTask(" blue green").value
val y = (run in Compile).toTask(" red orange").value
println("Done!")
}
The different `toTask` calls define different tasks that each run the project's main class in a new jvm.
That is, the `fork` setting configures both, each has the same classpath, and each run the same main class.
However, each task passes different arguments to the main class.
For a main class Demo that echoes its arguments, the output of running `runFixed2` might look like:
::
$ sbt
> runFixed2
[info] Running Demo blue green
[info] Running Demo red orange
blue
green
red
orange
Done!