cross-configurations

This commit is contained in:
Mark Harrah 2010-08-27 19:17:03 -04:00
parent 4bb7c44730
commit 62691e6681
7 changed files with 247 additions and 51 deletions

View File

@ -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

View File

@ -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")
}

View File

@ -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) )
}

View File

@ -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"))
}
}

View File

@ -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
{

View File

@ -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
}

View File

@ -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() }