diff --git a/tasks/Execute.scala b/tasks/Execute.scala index e3400f1d0..b80eb48c2 100644 --- a/tasks/Execute.scala +++ b/tasks/Execute.scala @@ -207,7 +207,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie def submit[T]( node: A[T] )(implicit strategy: Strategy) { val v = viewCache(node) - val rs = v.mixedIn.map(results) + val rs = v.mixedIn transform results val ud = v.uniformIn.map(results.apply[v.Uniform]) strategy.submit( node, () => work(node, v.work(rs, ud)) ) } diff --git a/tasks/standard/Action.scala b/tasks/standard/Action.scala index a1d76c68e..51e2c3395 100644 --- a/tasks/standard/Action.scala +++ b/tasks/standard/Action.scala @@ -10,19 +10,11 @@ import Task._ // Various natural transformations used, such as PMap, require invariant type constructors for correctness sealed trait Action[T] -sealed case class Pure[T](f: () => T) extends Action[T] - +final case class Pure[T](f: () => T) extends Action[T] final case class Mapped[T, In <: HList](in: Tasks[In], f: Results[In] => T) extends Action[T] -final case class MapAll[T, In <: HList](in: Tasks[In], f: In => T) extends Action[T] -final case class MapFailure[T, In <: HList](in: Tasks[In], f: Seq[Incomplete] => T) extends Action[T] - -final case class FlatMapAll[T, In <: HList](in: Tasks[In], f: In => Task[T]) extends Action[T] -final case class FlatMapFailure[T, In <: HList](in: Tasks[In], f: Seq[Incomplete] => Task[T]) extends Action[T] final case class FlatMapped[T, In <: HList](in: Tasks[In], f: Results[In] => Task[T]) extends Action[T] - final case class DependsOn[T](in: Task[T], deps: Seq[Task[_]]) extends Action[T] - -final case class Join[T, U](in: Seq[Task[U]], f: Seq[U] => Either[Task[T], T]) extends Action[T] +final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T], T]) extends Action[T] object Task { @@ -35,7 +27,22 @@ final case class Task[T](info: Info[T], work: Action[T]) def original = info.original getOrElse this } /** `original` is used during transformation only.*/ -final case class Info[T](name: Option[String] = None, description: Option[String] = None, implied: Boolean = false, original: Option[Task[T]] = None) +final case class Info[T](attributes: AttributeMap = AttributeMap.empty, original: Option[Task[T]] = None) { - assert(name forall (_ != null)) + import Info._ + def name = attributes.get(Name) + def description = attributes.get(Description) + def implied = attributes.get(Implied).getOrElse(false) + def setName(n: String) = set(Name, n) + def setDescription(d: String) = set(Description, d) + def setImplied(i: Boolean) = set(Implied, i) + def set[T](key: AttributeKey[T], value: T) = copy(attributes = this.attributes.put(key, value)) + } +object Info +{ + val Name = AttributeKey.make[String] + val Description = AttributeKey.make[String] + val Implied = AttributeKey.make[Boolean] + val Cross = AttributeKey.make[AttributeMap] +} \ No newline at end of file diff --git a/tasks/standard/Streams.scala b/tasks/standard/Streams.scala index c70d4e973..bb02aaf17 100644 --- a/tasks/standard/Streams.scala +++ b/tasks/standard/Streams.scala @@ -111,5 +111,5 @@ object Streams } def name(a: Task[_]): String = a.info.name getOrElse anonName(a) - def anonName(a: Task[_]) = "anon" + java.lang.Integer.toString(java.lang.System.identityHashCode(a), 36) + def anonName(a: Task[_]) = "anon-" + java.lang.Integer.toString(java.lang.System.identityHashCode(a), 36) } \ No newline at end of file diff --git a/tasks/standard/System.scala b/tasks/standard/System.scala index 4f56052fb..e5b763bb5 100644 --- a/tasks/standard/System.scala +++ b/tasks/standard/System.scala @@ -46,7 +46,10 @@ object System new (Task ~> Task) { def apply[T](in: Task[T]): Task[T] = { val finalName = in.info.name orElse staticName(in.original) - in.copy(info = in.info.copy(name = finalName) ) + finalName match { + case None => in + case Some(finalName) => in.copy(info = in.info.setName(finalName) ) + } } } @@ -61,13 +64,12 @@ object System * For example, this does not include the inputs of MapFailure or the dependencies of DependsOn. */ def usedInputs(t: Action[_]): Seq[Task[_]] = t match { case m: Mapped[_,_] => m.in.toList - case m: MapAll[_,_] => m.in.toList case m: FlatMapped[_,_] => m.in.toList - case m: FlatMapAll[_,_] => m.in.toList case j: Join[_,_] => j.in case _ => Nil } + def streamed(streams: Streams, dummy: Task[TaskStreams]): Task ~> Task = new (Task ~> Task) { def apply[T](t: Task[T]): Task[T] = if(usedInputs(t.work) contains dummy) substitute(t) else t @@ -86,10 +88,8 @@ object System def newWork(a: Action[T]): Task[T] = t.copy(work = a) t.work match { - case m: Mapped[_,_] => newWork( m.copy(in = m.in map depMap, f = wrap0(m.f) ) ) // the Streams instance is valid only within the mapping function - case m: MapAll[_,_] => newWork( m.copy(in = m.in map depMap, f = wrap0(m.f) ) ) - case fm: FlatMapped[_,_] => fin(newWork( fm.copy(in = fm.in map depMap) )) // the Streams instance is valid until a result is produced for the task - case fm: FlatMapAll[_,_] => fin(newWork( fm.copy(in = fm.in map depMap) )) + case m: Mapped[_,_] => newWork( m.copy(in = m.in transform depMap, f = wrap0(m.f) ) ) // the Streams instance is valid only within the mapping function + case fm: FlatMapped[_,_] => fin(newWork( fm.copy(in = fm.in transform depMap) )) // the Streams instance is valid until a result is produced for the task case j: Join[_,u] => newWork( j.copy(j.in map depMap.fn[u], f = wrap0(j.f)) ) case _ => t // can't get a TaskStreams value from the other types, so no need to transform here (shouldn't get here anyway because of usedInputs check) } @@ -108,6 +108,17 @@ object Transform def subs: Owner => Iterable[Owner] def static: (Owner, String) => Option[Task[_]] } + def setOriginal(delegate: Task ~> Task): Task ~> Task = + new (Task ~> Task) { + def apply[T](in: Task[T]): Task[T] = + { + val transformed = delegate(in) + if( (transformed eq in) || transformed.info.original.isDefined) + transformed + else + transformed.copy(info = transformed.info.copy(original = in.info.original orElse Some(in))) + } + } def apply[Input, State, Owner](dummies: Dummies[Input, State], injected: Injected[Input, State], context: Context[Owner]) = { @@ -117,7 +128,7 @@ object Transform import System._ import Convert._ val inputs = dummyMap(dummyIn, dummyState)(in, state) - Convert.taskToNode ∙ streamed(streams, dummyStreams) ∙ implied(owner, subs, static) ∙ name(staticName) ∙ getOrId(inputs) + Convert.taskToNode ∙ setOriginal(streamed(streams, dummyStreams)) ∙ implied(owner, subs, static) ∙ setOriginal(name(staticName)) ∙ getOrId(inputs) } } object Convert @@ -126,13 +137,9 @@ object Convert def apply[T](t: Task[T]): Node[Task, T] = t.work match { case Pure(eval) => toNode(KNil)( _ => Right(eval()) ) case Mapped(in, f) => toNode(in)( right ∙ f ) - case MapAll(in, f) => toNode(in)( right ∙ (f compose allM) ) - case MapFailure(in, f) => toNode(in)( right ∙ (f compose anyFailM)) case FlatMapped(in, f) => toNode(in)( left ∙ f ) - case FlatMapAll(in, f) => toNode(in)( left ∙ (f compose allM) ) - case FlatMapFailure(in, f) => toNode(in)( left ∙ (f compose anyFailM) ) case DependsOn(in, deps) => toNode(KList.fromList(deps))( _ => Left(in) ) - case Join(in, f) => uniform(in)(f compose all) + case Join(in, f) => uniform(in)(f) } } @@ -150,24 +157,4 @@ object Convert val uniformIn = Nil def work(results: Results[In], units: Seq[Result[Uniform]]) = f(results) } - - def anyFailM[In <: HList]: Results[In] => Seq[Incomplete] = in => - { - val incs = failuresM(in) - if(incs.isEmpty) throw Incomplete(message = Some("Expected failure")) else incs - } - - def allM[In <: HList]: Results[In] => In = in => - { - val incs = failuresM(in) - if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs) - } - def all[D]: Seq[Result[D]] => Seq[D] = in => - { - val incs = failures(in) - if(incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw Incomplete(causes = incs) - } - def failuresM[In <: HList]: Results[In] => Seq[Incomplete] = x => failures[Any](x.toList) - def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { case Inc(i) => i } - } \ No newline at end of file diff --git a/tasks/standard/TaskExtra.scala b/tasks/standard/TaskExtra.scala index bffff99a3..02e9752b2 100644 --- a/tasks/standard/TaskExtra.scala +++ b/tasks/standard/TaskExtra.scala @@ -13,7 +13,7 @@ sealed trait MultiInTask[In <: HList] { def flatMap[T](f: In => Task[T]): Task[T] def flatMapR[T](f: Results[In] => Task[T]): Task[T] - def mapH[T](f: In => T): Task[T] + def map[T](f: In => T): Task[T] def mapR[T](f: Results[In] => T): Task[T] def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] def mapFailure[T](f: Seq[Incomplete] => T): Task[T] @@ -51,14 +51,14 @@ import java.io._ sealed trait BinaryPipe { def binary[T](f: BufferedInputStream => T): Task[T] - //def binary[T](sid: String)(f: BufferedInputStream => T): Task[T] + def binary[T](sid: String)(f: BufferedInputStream => T): Task[T] def #>(f: File): Task[Unit] - //def #>(sid: String, f: File): Task[Unit] + def #>(sid: String, f: File): Task[Unit] } sealed trait TextPipe { def text[T](f: BufferedReader => T): Task[T] - //def #| [T](sid: String)(f: BufferedReader => T): Task[T] + def text[T](sid: String)(f: BufferedReader => T): Task[T] } sealed trait TaskLines { @@ -67,7 +67,7 @@ sealed trait TaskLines } sealed trait ProcessPipe { def #| (p: ProcessBuilder): Task[Int] - //def #| (sid: String)(p: ProcessBuilder): Task[Int] + def pipe(sid: String)(p: ProcessBuilder): Task[Int] } trait TaskExtra @@ -84,35 +84,36 @@ trait TaskExtra } final implicit def pureJoin[S](in: Seq[S]): JoinTask[S, Seq] = joinTasks(pureTasks(in)) final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { - def join: Task[Seq[S]] = new Join(in, (s: Seq[S]) => Right(s) ) - //def join[T](f: Iterable[S] => T): Task[Iterable[T]] = new MapAll( MList.fromTCList[Task](in), ml => f(ml.toList)) - //def joinR[T](f: Iterable[Result[S]] => T): Task[Iterable[Result[T]]] = new Mapped( MList.fromTCList[Task](in), ml => f(ml.toList)) + def join: Task[Seq[S]] = new Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)) ) def reduce(f: (S,S) => S): Task[S] = TaskExtra.reduce(in.toIndexedSeq, f) } final implicit def multInputTask[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiInTask[In] { - def flatMap[T](f: In => Task[T]): Task[T] = new FlatMapAll(tasks, f) + import TaskExtra.{allM, anyFailM} + def flatMapR[T](f: Results[In] => Task[T]): Task[T] = new FlatMapped(tasks, f) - def mapH[T](f: In => T): Task[T] = new MapAll(tasks, f) + def flatMap[T](f: In => Task[T]): Task[T] = flatMapR(f compose allM) + def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] = flatMapR(f compose anyFailM) + def mapR[T](f: Results[In] => T): Task[T] = new Mapped(tasks, f) - def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] = new FlatMapFailure(tasks, f) - def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = new MapFailure(tasks, f) + def map[T](f: In => T): Task[T] = mapR(f compose allM) + def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = mapR(f compose anyFailM) } final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleInTask[S] { + import TaskExtra.{successM, failM} + type HL = S :+: HNil private val ml = in :^: KNil private def headM = (_: Results[HL]).combine.head - private def headH = (_: HL).head - private def headS = (_: Seq[Incomplete]).head def flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, HL](ml, f ∙ headM) - def flatMap[T](f: S => Task[T]): Task[T] = new FlatMapAll(ml, f ∙ headH) - def flatFailure[T](f: Incomplete => Task[T]): Task[T] = new FlatMapFailure(ml, f ∙ headS) - - def map[T](f: S => T): Task[T] = new MapAll(ml, f ∙ headH) + def flatMap[T](f: S => Task[T]): Task[T] = flatMapR(f compose successM) + def flatFailure[T](f: Incomplete => Task[T]): Task[T] = flatMapR(f compose failM) + def mapR[T](f: Result[S] => T): Task[T] = new Mapped[T, HL](ml, f ∙ headM) - def mapFailure[T](f: Incomplete => T): Task[T] = new MapFailure(ml, f ∙ headS) + def map[T](f: S => T): Task[T] = mapR(f compose successM) + def mapFailure[T](f: Incomplete => T): Task[T] = mapR(f compose failM) def dependsOn(tasks: Task[_]*): Task[S] = new DependsOn(in, tasks) def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]( { fin; x })) @@ -121,14 +122,14 @@ trait TaskExtra def && [T](alt: Task[T]): Task[T] = flatMap( _ => alt ) } final implicit def toTaskInfo[S](in: Task[S]): TaskInfo[S] = new TaskInfo[S] { - def named(s: String): Task[S] = in.copy(info = in.info.copy(name = Some(s))) - def describedAs(s: String): Task[S] = in.copy(info = in.info.copy(description = Some(s))) - def implies: Task[S] = in.copy(info = in.info.copy(implied = true)) + def named(s: String): Task[S] = in.copy(info = in.info.setName(s)) + def describedAs(s: String): Task[S] = in.copy(info = in.info.setDescription(s)) + def implies: Task[S] = in.copy(info = in.info.setImplied(true)) } final implicit def pipeToProcess(t: Task[_])(implicit streams: Task[TaskStreams]): ProcessPipe = new ProcessPipe { def #| (p: ProcessBuilder): Task[Int] = pipe0(None, p) - //def #| (sid: String)(p: ProcessBuilder): Task[Int] = pipe0(Some(sid), p) + def pipe(sid: String)(p: ProcessBuilder): Task[Int] = pipe0(Some(sid), p) private def pipe0(sid: Option[String], p: ProcessBuilder): Task[Int] = for(s <- streams; in <- s.readBinary(t, sid, true)) yield { val pio = TaskExtra.processIO(s).withInput( out => { BasicIO.transferFully(in, out); out.close() } ) @@ -138,10 +139,10 @@ trait TaskExtra final implicit def binaryPipeTask(in: Task[_])(implicit streams: Task[TaskStreams]): BinaryPipe = new BinaryPipe { def binary[T](f: BufferedInputStream => T): Task[T] = pipe0(None, f) - //def #| [T](sid: String)(f: BufferedInputStream => T): Task[T] = pipe0(Some(sid), f) + def binary[T](sid: String)(f: BufferedInputStream => T): Task[T] = pipe0(Some(sid), f) def #>(f: File): Task[Unit] = pipe0(None, toFile(f)) - //def #>(sid: String, f: File): Task[Unit] = pipe0(Some(sid), toFile(f)) + def #>(sid: String, f: File): Task[Unit] = pipe0(Some(sid), toFile(f)) private def pipe0 [T](sid: Option[String], f: BufferedInputStream => T): Task[T] = streams flatMap { s => s.readBinary(in, sid, true) map f } @@ -150,7 +151,7 @@ trait TaskExtra } final implicit def textPipeTask(in: Task[_])(implicit streams: Task[TaskStreams]): TextPipe = new TextPipe { def text[T](f: BufferedReader => T): Task[T] = pipe0(None, f) - //def #| [T](sid: String)(f: BufferedReader => T): Task[T] = pipe0(Some(sid), f) + def text [T](sid: String)(f: BufferedReader => T): Task[T] = pipe0(Some(sid), f) private def pipe0 [T](sid: Option[String], f: BufferedReader => T): Task[T] = streams flatMap { s => s.readText(in, sid, true) map f } @@ -185,5 +186,29 @@ object TaskExtra extends TaskExtra reducePair( reduce(a, f), reduce(b, f), f ) } def reducePair[S](a: Task[S], b: Task[S], f: (S, S) => S): Task[S] = - (a :^: b :^: KNil) mapH { case x :+: y :+: HNil => f(x,y) } + (a :^: b :^: KNil) map { case x :+: y :+: HNil => f(x,y) } + + def anyFailM[In <: HList]: Results[In] => Seq[Incomplete] = in => + { + val incs = failuresM(in) + if(incs.isEmpty) expectedFailure else incs + } + def failM[T]: Result[T] => Incomplete = { case Inc(i) => i; case x => expectedFailure } + + def expectedFailure = throw Incomplete(message = Some("Expected failure")) + + def successM[T]: Result[T] => T = { case Inc(i) => throw i; case Value(t) => t } + def allM[In <: HList]: Results[In] => In = in => + { + val incs = failuresM(in) + if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs) + } + def failuresM[In <: HList]: Results[In] => Seq[Incomplete] = x => failures[Any](x.toList) + + def all[D](in: Seq[Result[D]]) = + { + val incs = failures(in) + if(incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw Incomplete(causes = incs) + } + def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { case Inc(i) => i } } \ No newline at end of file diff --git a/util/collection/Attributes.scala b/util/collection/Attributes.scala new file mode 100644 index 000000000..231cda300 --- /dev/null +++ b/util/collection/Attributes.scala @@ -0,0 +1,34 @@ +/* sbt -- Simple Build Tool + * Copyright 2010 Mark Harrah + */ +package sbt + +import Types._ + +// T must be invariant to work properly. +// Because it is sealed and the only instances go through make, +// a single AttributeKey instance cannot conform to AttributeKey[T] for different Ts +sealed trait AttributeKey[T] +object AttributeKey +{ + def make[T]: AttributeKey[T] = new AttributeKey[T] {} +} + +trait AttributeMap +{ + def apply[T](k: AttributeKey[T]): T + def get[T](k: AttributeKey[T]): Option[T] + def contains[T](k: AttributeKey[T]): Boolean + def put[T](k: AttributeKey[T], value: T): AttributeMap +} +object AttributeMap +{ + def empty: AttributeMap = new BasicAttributeMap(Map.empty) +} +private class BasicAttributeMap(backing: Map[AttributeKey[_], Any]) extends AttributeMap +{ + def apply[T](k: AttributeKey[T]) = backing(k).asInstanceOf[T] + def get[T](k: AttributeKey[T]) = backing.get(k).asInstanceOf[Option[T]] + def contains[T](k: AttributeKey[T]) = backing.contains(k) + def put[T](k: AttributeKey[T], value: T): AttributeMap = new BasicAttributeMap( backing.updated(k, value) ) +} \ No newline at end of file diff --git a/util/collection/KList.scala b/util/collection/KList.scala index b2ad40859..81ef6afcd 100644 --- a/util/collection/KList.scala +++ b/util/collection/KList.scala @@ -9,21 +9,23 @@ import Types._ * type parameters HL. The underlying data is M applied to each type parameter. * Explicitly tracking M[_] allows performing natural transformations or ensuring * all data conforms to some common type. */ -sealed trait KList[+M[_], +HL <: HList] { +sealed trait KList[+M[_], +HL <: HList] +{ type Raw = HL /** Transform to the underlying HList type.*/ def down(implicit ev: M ~> Id): HL /** Apply a natural transformation. */ - def map[N[_]](f: M ~> N): KList[N, HL] + def transform[N[_]](f: M ~> N): KList[N, HL] /** Convert to a List. */ def toList: List[M[_]] /** Convert to an HList. */ def combine[N[X] >: M[X]]: HL#Wrap[N] } -final case class KCons[H, T <: HList, +M[_]](head: M[H], tail: KList[M,T]) extends KList[M, H :+: T] { - def down(implicit f: M ~> Id) = HCons(f(head), tail.down(f)) - def map[N[_]](f: M ~> N) = KCons( f(head), tail.map(f) ) +final case class KCons[H, T <: HList, +M[_]](head: M[H], tail: KList[M,T]) extends KList[M, H :+: T] +{ + def down(implicit f: M ~> Id) = HCons(f(head), tail down f) + def transform[N[_]](f: M ~> N) = KCons( f(head), tail transform f ) // prepend def :^: [N[X] >: M[X], G](g: N[G]) = KCons(g, this) def toList = head :: tail.toList @@ -31,9 +33,10 @@ final case class KCons[H, T <: HList, +M[_]](head: M[H], tail: KList[M,T]) exten def combine[N[X] >: M[X]]: (H :+: T)#Wrap[N] = HCons(head, tail.combine) } -sealed class KNil extends KList[Nothing, HNil] { +sealed class KNil extends KList[Nothing, HNil] +{ def down(implicit f: Nothing ~> Id) = HNil - def map[N[_]](f: Nothing ~> N) = KNil + def transform[N[_]](f: Nothing ~> N) = KNil def :^: [M[_], H](h: M[H]) = KCons(h, this) def toList = Nil def combine[N[X]] = HNil diff --git a/util/collection/Types.scala b/util/collection/Types.scala index c5d484c51..abd9ee06b 100644 --- a/util/collection/Types.scala +++ b/util/collection/Types.scala @@ -8,4 +8,8 @@ object Types extends TypeFunctions val :^: = KCons val :+: = HCons type :+:[H, T <: HList] = HCons[H,T] + + implicit def hconsToK[M[_], H, T <: HList](h: M[H] :+: T)(implicit mt: T => KList[M, T]): KList[M, H :+: T] = + KCons[H, T, M](h.head, mt(h.tail) ) + implicit def hnilToK(hnil: HNil): KNil = KNil } diff --git a/util/collection/src/test/scala/KListTest.scala b/util/collection/src/test/scala/KListTest.scala index 210084fb1..2ca25a31a 100644 --- a/util/collection/src/test/scala/KListTest.scala +++ b/util/collection/src/test/scala/KListTest.scala @@ -9,7 +9,7 @@ object KTest { val f = new (Option ~> List) { def apply[T](o: Option[T]): List[T] = o.toList } val x = Some(3) :^: Some("asdf") :^: KNil - val y = x map f + val y = x transform f val m1a = y match { case List(3) :^: List("asdf") :^: KNil => println("true") } val m1b = (List(3) :^: KNil) match { case yy :^: KNil => println("true") }