diff --git a/main/SingleProject.scala b/main/SingleProject.scala index bf74f2304..2d545ba52 100644 --- a/main/SingleProject.scala +++ b/main/SingleProject.scala @@ -27,7 +27,7 @@ trait SingleProject extends Tasked val dummies = new Transform.Dummies(In, State, Streams) def name(t: Task[_]): String = context.staticName(t) getOrElse std.Streams.name(t) val injected = new Transform.Injected( input, state, std.Streams(t => streamBase / name(t)) ) - context.forName(input.name) map { t => (t map(_ => state), Transform(dummies, injected, context) ) } + context.forName(input.name) map { t => (t.merge.map(_ => state), Transform(dummies, injected, context) ) } } def help: Seq[Help] = Nil diff --git a/tasks/standard/Action.scala b/tasks/standard/Action.scala index 51e2c3395..e7c327b58 100644 --- a/tasks/standard/Action.scala +++ b/tasks/standard/Action.scala @@ -3,10 +3,10 @@ */ package sbt -import Types._ -import Task._ + import Types._ + import Task._ -// Action, Task, and Info are intentionally invariant in its type parameter. +// Action, Task, and Info are intentionally invariant in their type parameter. // Various natural transformations used, such as PMap, require invariant type constructors for correctness sealed trait Action[T] @@ -15,9 +15,12 @@ final case class Mapped[T, In <: HList](in: Tasks[In], f: Results[In] => T) exte 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[Result[U]] => Either[Task[T], T]) extends Action[T] +final case class CrossAction[T](subs: Cross[Task[T]] ) extends Action[T] + object Task { + type Cross[T] = Seq[(AttributeMap, T)] type Tasks[HL <: HList] = KList[Task, HL] type Results[HL <: HList] = KList[Result, HL] } @@ -41,8 +44,8 @@ final case class Info[T](attributes: AttributeMap = AttributeMap.empty, original } object Info { - val Name = AttributeKey.make[String] - val Description = AttributeKey.make[String] - val Implied = AttributeKey.make[Boolean] - val Cross = AttributeKey.make[AttributeMap] + val Name = AttributeKey[String]("name") + val Description = AttributeKey[String]("description") + val Implied = AttributeKey[Boolean]("implied") + val Cross = AttributeKey[AttributeMap]("cross-configuration") } \ No newline at end of file diff --git a/tasks/standard/Cross.scala b/tasks/standard/Cross.scala new file mode 100644 index 000000000..eea17a49f --- /dev/null +++ b/tasks/standard/Cross.scala @@ -0,0 +1,98 @@ +// TODO: join, reduce +package sbt +package std + + import Types._ + import Task._ + +object Cross +{ + type AttributeSet = Set[AttributeKey[_]] + + def hasCross(s: Seq[Task[_]]): Boolean = s.exists { _.work.isInstanceOf[CrossAction[_]] } + + def extract[T](in: AttributeMap = AttributeMap.empty): Task[T] => Task[T] = (result: Task[T]) => + result.work match + { + case CrossAction(subs) => + subs.filter { x => compatible(in)(x._1) } match { + case Seq( (_,matchingTask) ) => matchingTask + case Seq() => if(subs.isEmpty) error("Cannot introduce cross configurations with flatMap") else error("No compatible cross configurations returned by " + result) + case _ => error("Multiple compatible cross configurations returned by " + result) + } + case _ => result + } + + def compatible(base: AttributeMap)(nested: AttributeMap): Boolean = + nested.entries forall { case AttributeEntry(key, v) => base get(key) filter (_ == v) isDefined } + + def expandUniform[S](uniform: Seq[Task[S]]): Cross[ Seq[Task[S]] ] = + // the cast is necessary to avoid duplicating the contents of expandExist + // and expandCrossed (expandExist cannot be defined in terms of expandUniform + // because there is no valid instatiation of S) + expandExist(uniform).asInstanceOf[ Cross[ Seq[Task[S]] ] ] + + def expandExist(dependsOn: Seq[Task[_]]): Cross[ Seq[Task[_]] ] = + { + val klist = KList.fromList(dependsOn) + map( expandCrossed( klist ) ) { _.toList } + } + + def keys(c: Cross[_]): AttributeSet = + (Set.empty[AttributeKey[_]] /: c ){ _ ++ _._1.keys } + + def uniform[S,T](in: Seq[Task[S]])(f: (AttributeMap, Seq[Task[S]]) => Task[T]): Task[T] = + crossTask( expandUniform(in), f) + + def exist[T](in: Seq[Task[_]])(f: (AttributeMap, Seq[Task[_]]) => Task[T]): Task[T] = + crossTask( expandExist(in), f) + + def apply[T, HL <: HList](in: Tasks[HL])(f: (AttributeMap, Tasks[HL]) => Task[T]): Task[T] = + crossTask( expandCrossed(in), f) + + def crossTask[S,T](subs: Cross[S], f: (AttributeMap, S) => Task[T]): Task[T] = + crossTask( subs map { case (m, t) => (m, f(m, t)) } ) + + def crossTask[T](subs: Cross[Task[T]]): Task[T] = + TaskExtra.actionToTask( CrossAction(subs) ) + + def expandCrossed[HL <: HList](in: Tasks[HL]): Cross[Tasks[HL]] = + in match { + case KCons(head, tail) => + val crossTail = expandCrossed(tail) + head.work match { + case CrossAction(subs) => combine(subs, crossTail)(KCons.apply) + case _ => crossTail map { case (m, k) => (m, KCons(head, k) ) } + } + case x => (AttributeMap.empty, x) :: Nil + } + + + def combine[A,B,C](a: Cross[A], b: Cross[B])(f: (A,B) => C): Cross[C] = + { + val keysA = keys(a) + val keysB = keys(b) + val common = keysA & keysB + if( keysA.size > keysB.size ) + merge(b,a, common)( (x,y) => f(y,x) ) + else + merge(a,b, common)(f) + } + private[this] def merge[A,B,C](a: Cross[A], b: Cross[B], common: AttributeSet)(f: (A,B) => C): Seq[(AttributeMap, C)] = + { + def related(mapA: AttributeMap): Cross[B] = + b filter { case (mapB, _) => + common forall ( c => mapA(c) == mapB(c) ) + } + def check(aRb: Cross[B]) = if(aRb.isEmpty) error("Cross mismatch") else aRb + + for { + (mapA, taskA) <- a + (mapB, taskB) <- check(related(mapA)) + } yield + ( mapA ++ mapB, f(taskA, taskB) ) + } + + def map[A,B](c: Cross[A])(f: A => B): Cross[B] = + for( (m, a) <- c) yield (m, f(a) ) +} \ No newline at end of file diff --git a/tasks/standard/System.scala b/tasks/standard/System.scala index e5b763bb5..c1ba592e7 100644 --- a/tasks/standard/System.scala +++ b/tasks/standard/System.scala @@ -140,6 +140,7 @@ object Convert case FlatMapped(in, f) => toNode(in)( left ∙ f ) case DependsOn(in, deps) => toNode(KList.fromList(deps))( _ => Left(in) ) case Join(in, f) => uniform(in)(f) + case CrossAction(subs) => error("Cannot run cross task: " + subs.mkString("\n\t","\n\t","\n")) } } diff --git a/tasks/standard/TaskExtra.scala b/tasks/standard/TaskExtra.scala index 02e9752b2..62016edb6 100644 --- a/tasks/standard/TaskExtra.scala +++ b/tasks/standard/TaskExtra.scala @@ -4,10 +4,10 @@ package sbt package std -import Types._ -import Task._ -import java.io.{BufferedInputStream, BufferedReader, File, InputStream} - + import Types._ + import Task._ + import java.io.{BufferedInputStream, BufferedReader, File, InputStream} + import Cross.{combine, crossTask, exist, expandExist, extract, hasCross, uniform} sealed trait MultiInTask[In <: HList] { @@ -32,6 +32,10 @@ sealed trait SingleInTask[S] def || [T >: S](alt: Task[T]): Task[T] def && [T](alt: Task[T]): Task[T] } +sealed trait CrossMerge[T] +{ + def merge: Task[Cross[T]] +} sealed trait TaskInfo[S] { def named(s: String): Task[S] @@ -41,13 +45,14 @@ sealed trait TaskInfo[S] sealed trait ForkTask[S, CC[_]] { def fork[T](f: S => T): CC[Task[T]] + def tasks: Seq[Task[S]] } sealed trait JoinTask[S, CC[_]] { def join: Task[CC[S]] def reduce(f: (S,S) => S): Task[S] } -import java.io._ + sealed trait BinaryPipe { def binary[T](f: BufferedInputStream => T): Task[T] @@ -65,62 +70,97 @@ sealed trait TaskLines def lines: Task[List[String]] def lines(sid: String): Task[List[String]] } -sealed trait ProcessPipe { +sealed trait ProcessPipe +{ def #| (p: ProcessBuilder): Task[Int] def pipe(sid: String)(p: ProcessBuilder): Task[Int] } trait TaskExtra { - final implicit def actionToTask[A <% Action[T], T](a: A): Task[T] = Task(Info(), a) + final def cross[T](key: AttributeKey[T])(values: T*): Task[T] = + CrossAction( for(v <- values) yield ( AttributeMap.empty put (key, v), task(v) ) ) + + final implicit def t2ToMulti[A,B](t: (Task[A],Task[B])) = multInputTask(t._1 :^: t._2 :^: KNil) + final implicit def f2ToHfun[A,B,R](f: (A,B) => R): (A :+: B :+: HNil => R) = { case a :+: b :+: HNil => f(a,b) } + + final implicit def t3ToMulti[A,B,C](t: (Task[A],Task[B],Task[C])) = multInputTask(t._1 :^: t._2 :^: t._3 :^: KNil) + final implicit def f3ToHfun[A,B,C,R](f: (A,B,C) => R): (A :+: B :+: C :+: HNil => R) = { case a :+: b :+: c :+: HNil => f(a,b,c) } + + final implicit def actionToTask[T](a: Action[T]): Task[T] = Task(Info(), a) final def task[T](f: => T): Task[T] = toTask(f _) final implicit def toTask[T](f: () => T): Task[T] = new Pure(f) - final implicit def pureTasks[S](in: Seq[S]): Seq[Task[S]] = in.map(s => task(s)) + final implicit def upcastTask[A >: B, B](t: Task[B]): Task[A] = t map { x => x : B } final implicit def toTasks[S](in: Seq[() => S]): Seq[Task[S]] = in.map(toTask) final implicit def iterableTask[S](in: Seq[S]): ForkTask[S, Seq] = new ForkTask[S, Seq] { - def fork[T](f: S => T): Seq[Task[T]] = in.map(x => task(x) map f) + def fork[T](f: S => T): Seq[Task[T]] = in.map(x => task(f(x))) + def tasks: Seq[Task[S]] = fork(identity) } - 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] { + + final implicit def joinAnyTasks(in: Seq[Task[_]]): JoinTask[Any, Seq] = joinTasks[Any](in map (x => x: Task[Any])) + final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = + if(hasCross(in)) multJoin(in) else basicJoin(in) + + final def multJoin[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { + def join: Task[Seq[S]] = uniform(in)( (_, s) => basicJoin(s).join ) + def reduce(f: (S,S) => S): Task[S] = basicJoin(in) reduce f + } + final def basicJoin[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { 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] { - import TaskExtra.{allM, anyFailM} - - def flatMapR[T](f: Results[In] => Task[T]): Task[T] = new FlatMapped(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 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 crossMerge[T](in: Task[T]): CrossMerge[T] = new CrossMerge[T] { + def merge: Task[Cross[T]] = in.work match { + case CrossAction(subs) => + val (maps, tasks) = subs.unzip + tasks.join.map { maps zip _ } + case _ => in map( x => (AttributeMap.empty, x) :: Nil) + } } - final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleInTask[S] { - import TaskExtra.{successM, failM} + final implicit def multInputTask[In <: HList](tasks: Tasks[In]): MultiInTask[In] = + if(hasCross(tasks.toList)) multCross(tasks) else multBasic(tasks) + final def multCross[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiBase[In] { + def flatMapR[T](f: Results[In] => Task[T]): Task[T] = Cross(tasks)( (m, ts) => new FlatMapped[T,In](ts, extract[T](m) compose f) ) + def mapR[T](f: Results[In] => T): Task[T] = Cross(tasks)( (_, ts) => multBasic(ts) mapR f) + } + + final def multBasic[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiBase[In] { + def flatMapR[T](f: Results[In] => Task[T]): Task[T] = new FlatMapped(tasks, extract() ∙ f) + def mapR[T](f: Results[In] => T): Task[T] = new Mapped(tasks, f) + } + + final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = + in.work match { + case CrossAction(subs) => singleCross(in, subs) + case x => singleBasic(in) + } + + final def singleCross[S](in: Task[S], subs: Cross[Task[S]]): SingleInTask[S] = + new SingleBase[S] { + def impl[T](f: (AttributeMap, Task[S]) => Task[T]): Task[T] = CrossAction( subs map { case (m, t) => (m, f(m, t)) } ) + def flatMapR[T](f: Result[S] => Task[T]): Task[T] = impl( (m, t) => singleBasic(t) flatMapR( Cross.extract(m) ∙ f) ) + def mapR[T](f: Result[S] => T): Task[T] = impl( (m,t) => t mapR f) + def dependsOn(tasks: Task[_]*): Task[S] = crossTask( combine( subs, expandExist(tasks) ){ (t,deps) => new DependsOn(t, deps) } ) + } + + final def singleBasic[S](in: Task[S]): SingleInTask[S] = new SingleBase[S] { type HL = S :+: HNil private val ml = in :^: KNil private def headM = (_: Results[HL]).combine.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] = flatMapR(f compose successM) - def flatFailure[T](f: Incomplete => Task[T]): Task[T] = flatMapR(f compose failM) - + def flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, HL](ml, Cross.extract() ∙ f ∙ headM) def mapR[T](f: Result[S] => T): Task[T] = new Mapped[T, HL](ml, f ∙ headM) - 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 })) - - def || [T >: S](alt: Task[T]): Task[T] = flatMapR { case Value(v) => task(v); case Inc(i) => alt } - def && [T](alt: Task[T]): Task[T] = flatMap( _ => alt ) + def dependsOn(tasks: Task[_]*): Task[S] = + if(hasCross(tasks)) + singleCross(in, (AttributeMap.empty, in) :: Nil).dependsOn(tasks :_*) + else + new DependsOn(in, tasks) } + final implicit def toTaskInfo[S](in: Task[S]): TaskInfo[S] = new TaskInfo[S] { 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)) @@ -167,6 +207,31 @@ trait TaskExtra val pio = TaskExtra.processIO(s) (p run pio).exitValue } + + private[this] abstract class SingleBase[S] extends SingleInTask[S] + { + import TaskExtra.{successM, failM} + + 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 map[T](f: S => T): Task[T] = mapR(f compose successM) + def mapFailure[T](f: Incomplete => T): Task[T] = mapR(f compose failM) + + def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]( { fin; x })) + def || [T >: S](alt: Task[T]): Task[T] = flatMapR { case Value(v) => task(v); case Inc(i) => alt } + def && [T](alt: Task[T]): Task[T] = flatMap( _ => alt ) + } + private[this] abstract class MultiBase[In <: HList] extends MultiInTask[In] + { + import TaskExtra.{allM, anyFailM} + + 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 map[T](f: In => T): Task[T] = mapR(f compose allM) + def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = mapR(f compose anyFailM) + } } object TaskExtra extends TaskExtra { diff --git a/util/collection/Attributes.scala b/util/collection/Attributes.scala index 231cda300..488142ad7 100644 --- a/util/collection/Attributes.scala +++ b/util/collection/Attributes.scala @@ -8,10 +8,12 @@ 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] +sealed trait AttributeKey[T] { + def label: String +} object AttributeKey { - def make[T]: AttributeKey[T] = new AttributeKey[T] {} + def apply[T](name: String): AttributeKey[T] = new AttributeKey[T] { def label = name } } trait AttributeMap @@ -20,15 +22,35 @@ trait AttributeMap def get[T](k: AttributeKey[T]): Option[T] def contains[T](k: AttributeKey[T]): Boolean def put[T](k: AttributeKey[T], value: T): AttributeMap + def keys: Iterable[AttributeKey[_]] + def ++(o: AttributeMap): AttributeMap + def entries: Iterable[AttributeEntry[_]] + def isEmpty: Boolean } object AttributeMap { - def empty: AttributeMap = new BasicAttributeMap(Map.empty) + val empty: AttributeMap = new BasicAttributeMap(Map.empty) } -private class BasicAttributeMap(backing: Map[AttributeKey[_], Any]) extends AttributeMap +private class BasicAttributeMap(private val backing: Map[AttributeKey[_], Any]) extends AttributeMap { + def isEmpty: Boolean = backing.isEmpty 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) ) + def keys: Iterable[AttributeKey[_]] = backing.keys + def ++(o: AttributeMap): AttributeMap = + o match { + case bam: BasicAttributeMap => new BasicAttributeMap(backing ++ bam.backing) + case _ => o ++ this + } + def entries: Iterable[AttributeEntry[_]] = + for( (k: AttributeKey[kt], v) <- backing) yield AttributeEntry(k, v.asInstanceOf[kt]) + override def toString = entries.mkString("(", ", ", ")") +} + +// type inference required less generality +final case class AttributeEntry[T](a: AttributeKey[T], b: T) +{ + override def toString = a.label + ": " + b } \ No newline at end of file diff --git a/util/io/IO.scala b/util/io/IO.scala index 1cc3a3520..c16455d58 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -454,14 +454,21 @@ object IO // Not optimized for large files def readLines(in: BufferedReader): List[String] = + foldLines[List[String]](in, Nil)( (accum, line) => line :: accum ) + + def foreachLine(in: BufferedReader)(f: String => Unit): Unit = + foldLines(in, ())( (_, line) => f(line) ) + + def foldLines[T](in: BufferedReader, init: T)(f: (T, String) => T): T = { - def readLine(accum: List[String]): List[String] = + def readLine(accum: T): T = { val line = in.readLine() - if(line eq null) accum.reverse else readLine(line :: accum) + if(line eq null) accum else readLine(f(accum, line)) } - readLine(Nil) + readLine(init) } + def writeLines(file: File, lines: Seq[String], charset: Charset = defaultCharset, append: Boolean = false): Unit = writer(file, lines.headOption.getOrElse(""), charset, append) { w => lines.foreach { line => w.write(line); w.newLine() }