From 9dcb8727d819fb8992db0eff65995b18ac7e71d5 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 18 Oct 2013 16:49:34 -0400 Subject: [PATCH] New method `toTask` on `Initialize[InputTask[T]]` to apply the full input and get a plain task out. --- main/settings/src/main/scala/sbt/Def.scala | 12 ++++ .../src/main/scala/sbt/InputTask.scala | 14 ++++ main/src/main/scala/sbt/EvaluateTask.scala | 4 +- main/src/main/scala/sbt/Keys.scala | 27 +++++--- main/src/main/scala/sbt/Load.scala | 4 +- src/sphinx/Extending/Input-Tasks.rst | 68 +++++++++++++++++-- 6 files changed, 109 insertions(+), 20 deletions(-) diff --git a/main/settings/src/main/scala/sbt/Def.scala b/main/settings/src/main/scala/sbt/Def.scala index 1a5bda0c5..f3c7f1d22 100644 --- a/main/settings/src/main/scala/sbt/Def.scala +++ b/main/settings/src/main/scala/sbt/Def.scala @@ -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 diff --git a/main/settings/src/main/scala/sbt/InputTask.scala b/main/settings/src/main/scala/sbt/InputTask.scala index 65fe7fb73..b802eaf24 100644 --- a/main/settings/src/main/scala/sbt/InputTask.scala +++ b/main/settings/src/main/scala/sbt/InputTask.scala @@ -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] = ??? diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 06724d993..af4872dd1 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -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 diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index bdefa1e85..283e8e251 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -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) } diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index df1e9defb..c9f8fed20 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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 diff --git a/src/sphinx/Extending/Input-Tasks.rst b/src/sphinx/Extending/Input-Tasks.rst index a3bd2ac03..2e61a7b1e 100644 --- a/src/sphinx/Extending/Input-Tasks.rst +++ b/src/sphinx/Extending/Input-Tasks.rst @@ -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 ' ' - 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!