From fcb35f3b8ffe7f460314a003868d51a49197d114 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 24 Nov 2013 18:24:15 -0500 Subject: [PATCH] Add Initialize[Task[T]].taskValue: Task[T] to allow selecting the Task for use by *Generators. Fixes #866. --- main/settings/src/main/scala/sbt/Def.scala | 11 ++++---- .../src/main/scala/sbt/Structure.scala | 11 ++++---- .../src/main/scala/sbt/std/InputWrapper.scala | 15 ++++++++-- src/sphinx/Howto/generatefiles.rst | 28 +++++++++---------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/main/settings/src/main/scala/sbt/Def.scala b/main/settings/src/main/scala/sbt/Def.scala index f3c7f1d22..e8735abc7 100644 --- a/main/settings/src/main/scala/sbt/Def.scala +++ b/main/settings/src/main/scala/sbt/Def.scala @@ -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)}") @@ -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 = ""): Parser[Seq[String]] = complete.Parsers.spaceDelimited(argLabel) + def spaceDelimited(argLabel: String = ""): 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) @@ -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] @@ -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 @@ -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] = ??? diff --git a/main/settings/src/main/scala/sbt/Structure.scala b/main/settings/src/main/scala/sbt/Structure.scala index bdc422b49..8c023c876 100644 --- a/main/settings/src/main/scala/sbt/Structure.scala +++ b/main/settings/src/main/scala/sbt/Structure.scala @@ -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 ) @@ -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 } @@ -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) @@ -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") @@ -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) @@ -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]] diff --git a/main/settings/src/main/scala/sbt/std/InputWrapper.scala b/main/settings/src/main/scala/sbt/std/InputWrapper.scala index b5ada74ce..7a2a121ff 100644 --- a/main/settings/src/main/scala/sbt/std/InputWrapper.scala +++ b/main/settings/src/main/scala/sbt/std/InputWrapper.scala @@ -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] diff --git a/src/sphinx/Howto/generatefiles.rst b/src/sphinx/Howto/generatefiles.rst index 8b5d1efbf..a8a1c3ec3 100644 --- a/src/sphinx/Howto/generatefiles.rst +++ b/src/sphinx/Howto/generatefiles.rst @@ -9,31 +9,31 @@ sbt provides standard hooks for adding source or resource generation tasks. :title: Generate sources :type: setting - sourceGenerators in Compile <+= + sourceGenerators in Compile += .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 <+= + sourceGenerators in Compile += .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. @@ -44,32 +44,32 @@ By default, generated sources are not included in the packaged source artifact. :title: Generate resources :type: setting - resourceGenerators in Compile <+= + resourceGenerators in Compile += -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 <+= + resourceGenerators in Compile += .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 <+= { + 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.