Skip to content

Commit

Permalink
Add Initialize[Task[T]].taskValue: Task[T] to allow selecting the Tas…
Browse files Browse the repository at this point in the history
…k for use by *Generators. Fixes #866.

Conflicts:
	src/sphinx/Howto/generatefiles.rst
  • Loading branch information
harrah authored and eed3si9n committed Mar 21, 2014
1 parent 40990e0 commit 592c4f7
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 26 deletions.
11 changes: 6 additions & 5 deletions main/settings/src/main/scala/sbt/Def.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ object Def extends Init[Scope] with TaskMacroExtra
case Some(c) => c + s + scala.Console.RESET
case None => s
}
override def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] =

override def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] =
super.deriveAllowed(s, allowDynamic) orElse
(if(s.key.scope != ThisScope) Some(s"Scope cannot be defined for ${definedSettingString(s)}") else None ) orElse
s.dependencies.find(k => k.scope != ThisScope).map(k => s"Scope cannot be defined for dependency ${k.key.label} of ${definedSettingString(s)}")
Expand All @@ -51,7 +51,7 @@ object Def extends Init[Scope] with TaskMacroExtra

/** A default Parser for splitting input into space-separated arguments.
* `argLabel` is an optional, fixed label shown for an argument during tab completion.*/
def spaceDelimited(argLabel: String = "<arg>"): Parser[Seq[String]] = complete.Parsers.spaceDelimited(argLabel)
def spaceDelimited(argLabel: String = "<arg>"): Parser[Seq[String]] = complete.Parsers.spaceDelimited(argLabel)

/** Lifts the result of a setting initialization into a Task. */
def toITask[T](i: Initialize[T]): Initialize[Task[T]] = map(i)(std.TaskExtra.inlineTask)
Expand All @@ -63,7 +63,7 @@ object Def extends Init[Scope] with TaskMacroExtra
import language.experimental.macros
import std.TaskMacro.{inputTaskMacroImpl, inputTaskDynMacroImpl, taskDynMacroImpl, taskMacroImpl}
import std.SettingMacro.{settingDynMacroImpl,settingMacroImpl}
import std.{InputEvaluated, MacroValue, ParserInput}
import std.{InputEvaluated, MacroValue, MacroTaskValue, ParserInput}

def task[T](t: T): Def.Initialize[Task[T]] = macro taskMacroImpl[T]
def taskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[Task[T]] = macro taskDynMacroImpl[T]
Expand All @@ -78,6 +78,7 @@ object Def extends Init[Scope] with TaskMacroExtra
implicit def macroValueI[T](in: Initialize[T]): MacroValue[T] = ???
implicit def macroValueIT[T](in: Initialize[Task[T]]): MacroValue[T] = ???
implicit def macroValueIInT[T](in: Initialize[InputTask[T]]): InputEvaluated[T] = ???
implicit def taskMacroValueIT[T](in: Initialize[Task[T]]): MacroTaskValue[T] = ???

// The following conversions enable the types Parser[T], Initialize[Parser[T]], and Initialize[State => Parser[T]] to
// be used in the inputTask macro as an input with an ultimate result of type T
Expand All @@ -101,7 +102,7 @@ object Def extends Init[Scope] with TaskMacroExtra
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
trait TaskMacroExtra
{
implicit def macroValueT[T](in: Task[T]): std.MacroValue[T] = ???
implicit def macroValueIn[T](in: InputTask[T]): std.InputEvaluated[T] = ???
Expand Down
11 changes: 6 additions & 5 deletions main/settings/src/main/scala/sbt/Structure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ sealed abstract class SettingKey[T] extends ScopedTaskable[T] with KeyedInitiali
final def := (v: T): Setting[T] = macro std.TaskMacro.settingAssignMacroImpl[T]
final def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[T] = macro std.TaskMacro.settingAppend1Impl[T,U]
final def ++=[U](vs: U)(implicit a: Append.Values[T, U]): Setting[T] = macro std.TaskMacro.settingAppendNImpl[T,U]
final def <+= [V](v: Initialize[V])(implicit a: Append.Value[T, V]): Setting[T] = macro std.TaskMacro.settingAppend1Position[T,V]
final def <+= [V](v: Initialize[V])(implicit a: Append.Value[T, V]): Setting[T] = macro std.TaskMacro.settingAppend1Position[T,V]
final def <++= [V](vs: Initialize[V])(implicit a: Append.Values[T, V]): Setting[T] = macro std.TaskMacro.settingAppendNPosition[T,V]
final def ~= (f: T => T): Setting[T] = macro std.TaskMacro.settingTransformPosition[T]
final def transform(f: T => T, source: SourcePosition): Setting[T] = set( scopedKey(f), source )
Expand Down Expand Up @@ -108,7 +108,7 @@ object Scoped
def in(p: Reference, c: ConfigKey, t: Scoped): Result = in(Select(p), Select(c), Select(t.key))
def in(p: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]]): Result = in( Scope(p, c, t, This) )
}

def scopedSetting[T](s: Scope, k: AttributeKey[T]): SettingKey[T] = new SettingKey[T] { val scope = s; val key = k}
def scopedInput[T](s: Scope, k: AttributeKey[InputTask[T]]): InputKey[T] = new InputKey[T] { val scope = s; val key = k }
def scopedTask[T](s: Scope, k: AttributeKey[Task[T]]): TaskKey[T] = new TaskKey[T] { val scope = s; val key = k }
Expand Down Expand Up @@ -142,6 +142,7 @@ object Scoped
def set(app: Initialize[Task[S]], source: SourcePosition): Setting[Task[S]] = Def.setting(scopedKey, app, source)
def transform(f: S => S, source: SourcePosition): Setting[Task[S]] = set( scopedKey(_ map f), source)

@deprecated("No longer needed with new task syntax and SettingKey inheriting from Initialize.", "0.13.2")
def task: SettingKey[Task[S]] = scopedSetting(scope, key)
def get(settings: Settings[Scope]): Option[Task[S]] = settings.get(scope, key)

Expand Down Expand Up @@ -214,7 +215,7 @@ object Scoped

implicit def richFileSetting(s: SettingKey[File]): RichFileSetting = new RichFileSetting(s)
implicit def richFilesSetting(s: SettingKey[Seq[File]]): RichFilesSetting = new RichFilesSetting(s)

final class RichFileSetting(s: SettingKey[File]) extends RichFileBase
{
@deprecated("Use a standard setting definition.", "0.13.0")
Expand All @@ -237,7 +238,7 @@ object Scoped
protected[this] def finder(f: PathFinder => PathFinder): Seq[File] => Seq[File] =
in => f(in).get
}

// this is the least painful arrangement I came up with
implicit def t2ToTable2[A,B](t2: (ScopedTaskable[A], ScopedTaskable[B]) ): RichTaskable2[A,B] = new RichTaskable2(t2)
implicit def t3ToTable3[A,B,C](t3: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C]) ): RichTaskable3[A,B,C] = new RichTaskable3(t3)
Expand All @@ -253,7 +254,7 @@ object Scoped
implicit def t13ToTable13[A,B,C,D,E,F,G,H,I,J,K,L,N](t13: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C], ScopedTaskable[D], ScopedTaskable[E], ScopedTaskable[F], ScopedTaskable[G], ScopedTaskable[H], ScopedTaskable[I], ScopedTaskable[J], ScopedTaskable[K], ScopedTaskable[L], ScopedTaskable[N]) ): RichTaskable13[A,B,C,D,E,F,G,H,I,J,K,L,N] = new RichTaskable13(t13)
implicit def t14ToTable14[A,B,C,D,E,F,G,H,I,J,K,L,N,O](t14: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C], ScopedTaskable[D], ScopedTaskable[E], ScopedTaskable[F], ScopedTaskable[G], ScopedTaskable[H], ScopedTaskable[I], ScopedTaskable[J], ScopedTaskable[K], ScopedTaskable[L], ScopedTaskable[N], ScopedTaskable[O]) ): RichTaskable14[A,B,C,D,E,F,G,H,I,J,K,L,N,O] = new RichTaskable14(t14)
implicit def t15ToTable15[A,B,C,D,E,F,G,H,I,J,K,L,N,O,P](t15: (ScopedTaskable[A], ScopedTaskable[B], ScopedTaskable[C], ScopedTaskable[D], ScopedTaskable[E], ScopedTaskable[F], ScopedTaskable[G], ScopedTaskable[H], ScopedTaskable[I], ScopedTaskable[J], ScopedTaskable[K], ScopedTaskable[L], ScopedTaskable[N], ScopedTaskable[O], ScopedTaskable[P]) ): RichTaskable15[A,B,C,D,E,F,G,H,I,J,K,L,N,O,P] = new RichTaskable15(t15)*/

sealed abstract class RichTaskables[K[L[x]]](final val keys: K[ScopedTaskable])(implicit a: AList[K])
{
type App[T] = Initialize[Task[T]]
Expand Down
15 changes: 13 additions & 2 deletions main/settings/src/main/scala/sbt/std/InputWrapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,23 @@ object InputWrapper
InputWrapper.wrapInputTask[T](c)(ts,pos)
else if(tpe <:< c.weakTypeOf[Initialize[InputTask[T]]])
InputWrapper.wrapInitInputTask[T](c)(ts,pos)

else
c.abort(pos, s"Internal sbt error. Unexpected type $tpe")
c.abort(pos, s"Internal sbt error. Unexpected type ${tpe.widen}")
}
def taskValueMacroImpl[T: c.WeakTypeTag](c: Context): c.Expr[Task[T]] =
ContextUtil.selectMacroImpl[Task[T]](c) { (ts, pos) =>
val tpe = ts.tree.tpe
if(tpe <:< c.weakTypeOf[Initialize[Task[T]]])
InputWrapper.wrapInit[Task[T]](c)(ts,pos)
else
c.abort(pos, s"Internal sbt error. Unexpected type ${tpe.widen}")
}
}

sealed abstract class MacroTaskValue[T] {
@compileTimeOnly("`taskValue` can only be used within a setting macro, such as :=, +=, ++=, or Def.setting.")
def taskValue: Task[T] = macro InputWrapper.taskValueMacroImpl[T]
}
sealed abstract class MacroValue[T] {
@compileTimeOnly("`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.")
def value: T = macro InputWrapper.valueMacroImpl[T]
Expand Down
28 changes: 14 additions & 14 deletions src/sphinx/Howto/generatefiles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,31 @@ sbt provides standard hooks for adding source or resource generation tasks.
:title: Generate sources
:type: setting

sourceGenerators in Compile <+= <your Task[Seq[File]] here>
sourceGenerators in Compile += <task of type Seq[File]>.taskValue

A source generation task should generate sources in a subdirectory of :key:`sourceManaged` and return a sequence of files generated. The key to add the task to is called :key:`sourceGenerators`. It should be scoped according to whether the generated files are main (`Compile`) or test (`Test`) sources. This basic structure looks like:
A source generation task should generate sources in a subdirectory of :key:`sourceManaged` and return a sequence of files generated. The key to add the task to is called :key:`sourceGenerators`. Because we want to add the unexecuted task, we use `taskValue` instead of the usual `value`. :key:`sourceGenerators` should be scoped according to whether the generated files are main (`Compile`) or test (`Test`) sources. This basic structure looks like:

::

sourceGenerators in Compile <+= <your Task[Seq[File]] here>
sourceGenerators in Compile += <task of type Seq[File]>.taskValue

For example, assuming a method `def makeSomeSources(base: File): Seq[File]`,

::

sourceGenerators in Compile <+=
Def.task { makeSomeSources( (sourceManaged in Compile).value / "demo" ) }
sourceGenerators in Compile +=
Def.task( makeSomeSources( (sourceManaged in Compile).value / "demo" ) ).taskValue


As a specific example, the following generates a hello world source file:

::

sourceGenerators in Compile <+= Def.task {
sourceGenerators in Compile += Def.task {
val file = (sourceManaged in Compile).value / "demo" / "Test.scala"
IO.write(file, """object Test extends App { println("Hi") }""")
Seq(file)
}
}.taskValue

Executing 'run' will print "Hi". Change `Compile` to `Test` to make it a test source. For efficiency, you would only want to generate sources when necessary and not every run.

Expand All @@ -44,32 +44,32 @@ By default, generated sources are not included in the packaged source artifact.
:title: Generate resources
:type: setting

resourceGenerators in Compile <+= <your Task[Seq[File]] here>
resourceGenerators in Compile += <task of type Seq[File]>

A resource generation task should generate resources in a subdirectory of :key:`resourceManaged` and return a sequence of files generated. The key to add the task to is called :key:`resourceGenerators`. It should be scoped according to whether the generated files are main (`Compile`) or test (`Test`) resources. This basic structure looks like:
A resource generation task should generate resources in a subdirectory of :key:`resourceManaged` and return a sequence of files generated. The key to add the task to is called :key:`resourceGenerators`. Because we want to add the unexecuted task, we use `taskValue` instead of the usual `value`. It should be scoped according to whether the generated files are main (`Compile`) or test (`Test`) resources. This basic structure looks like:

::

resourceGenerators in Compile <+= <your Task[Seq[File]] here>
resourceGenerators in Compile += <task of type Seq[File]>.taskValue

For example, assuming a method `def makeSomeResources(base: File): Seq[File]`,

::

resourceGenerators in Compile <+= Def.task {
resourceGenerators in Compile += Def.task {
makeSomeResources( (resourceManaged in Compile).value / "demo")
}
}.taskValue

As a specific example, the following generates a properties file containing the application name and version:

::

resourceGenerators in Compile <+= Def.task {
resourceGenerators in Compile += Def.task {
val file = (resourceManaged in Compile).value / "demo" / "myapp.properties"
val contents = "name=%s\nversion=%s".format(name.value,version.value)
IO.write(file, contents)
Seq(file)
}
}.taskValue

Change `Compile` to `Test` to make it a test resource. Normally, you would only want to generate resources when necessary and not every run.

Expand Down

0 comments on commit 592c4f7

Please sign in to comment.