From f24af2a05b4fb191e97ec07958a6724eb77156f7 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 23 Apr 2011 11:49:58 -0400 Subject: [PATCH] support for task hooks: triggeredBy and runBefore --- main/Aggregation.scala | 2 +- main/Build.scala | 16 ++++++++++++++++ main/EvaluteTask.scala | 6 +++--- main/Keys.scala | 3 +++ main/Load.scala | 4 ++-- main/Structure.scala | 16 ++++++++++++++++ tasks/Execute.scala | 14 ++++++++++---- 7 files changed, 51 insertions(+), 10 deletions(-) diff --git a/main/Aggregation.scala b/main/Aggregation.scala index 1f1268159..89a560fe7 100644 --- a/main/Aggregation.scala +++ b/main/Aggregation.scala @@ -80,7 +80,7 @@ final object Aggregation import std.TaskExtra._ val toRun = ts map { case KeyValue(k,t) => t.map(v => KeyValue(k,v)) } join; val start = System.currentTimeMillis - val result = withStreams(structure){ str => runTask(toRun, str)(nodeView(s, str, extra.tasks, extra.values)) } + val result = withStreams(structure){ str => runTask(toRun, str, structure.index.triggers)(nodeView(s, str, extra.tasks, extra.values)) } val stop = System.currentTimeMillis val log = logger(s) lazy val extracted = Project.extract(s) diff --git a/main/Build.scala b/main/Build.scala index 498bc9be6..93c25f3a8 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -123,6 +123,22 @@ object Index else error(duplicates.mkString("AttributeKey ID collisions detected for '", "', '", "'")) } + private[this] type TriggerMap = collection.mutable.HashMap[Task[_], Seq[Task[_]]] + def triggers(ss: Settings[Scope]): Triggers[Task] = + { + val runBefore = new TriggerMap + val triggeredBy = new TriggerMap + for( (_, amap) <- ss.data; AttributeEntry(_, value: Task[_]) <- amap.entries) + { + val as = value.info.attributes + update(runBefore, value, as get Keys.runBefore) + update(triggeredBy, value, as get Keys.triggeredBy) + } + new Triggers[Task](runBefore, triggeredBy) + } + private[this] def update(map: TriggerMap, base: Task[_], tasksOpt: Option[Seq[Task[_]]]): Unit = + for( tasks <- tasksOpt; task <- tasks ) + map(task) = base +: map.getOrElse(task, Nil) } object BuildStreams { diff --git a/main/EvaluteTask.scala b/main/EvaluteTask.scala index 6cb07b36c..b42ed0047 100644 --- a/main/EvaluteTask.scala +++ b/main/EvaluteTask.scala @@ -35,7 +35,7 @@ object EvaluateTask def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] = withStreams(structure) { str => for( (task, toNode) <- getTask(structure, taskKey, state, str, ref) ) yield - runTask(task, str, checkCycles, maxWorkers)(toNode) + runTask(task, str, structure.index.triggers, checkCycles, maxWorkers)(toNode) } def logIncResult(result: Result[_], streams: Streams) = result match { case Inc(i) => logIncomplete(i, streams); case _ => () } def logIncomplete(result: Incomplete, streams: Streams) @@ -71,11 +71,11 @@ object EvaluateTask def nodeView[HL <: HList](state: State, streams: Streams, extraDummies: KList[Task, HL] = KNil, extraValues: HL = HNil): Execute.NodeView[Task] = Transform(dummyStreamsManager :^: KCons(dummyState, extraDummies), streams :+: HCons(state, extraValues)) - def runTask[Task[_] <: AnyRef, T](root: Task[T], streams: Streams, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): Result[T] = + def runTask[Task[_] <: AnyRef, T](root: Task[T], streams: Streams, triggers: Triggers[Task], checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): Result[T] = { val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers) - val x = new Execute[Task](checkCycles)(taskToNode) + val x = new Execute[Task](checkCycles, triggers)(taskToNode) val result = try { x.run(root)(service) } finally { shutdown() } val replaced = transformInc(result) logIncResult(replaced, streams) diff --git a/main/Keys.scala b/main/Keys.scala index 6128d2cff..697f3e60e 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -234,6 +234,9 @@ object Keys val resolvedScoped = SettingKey[ScopedKey[_]]("resolved-scoped", "The ScopedKey for the referencing setting or task.") private[sbt] val parseResult: TaskKey[_] = TaskKey("$parse-result", "Internal: used to implement input tasks.") + val triggeredBy = AttributeKey[Seq[Task[_]]]("triggered-by") + val runBefore = AttributeKey[Seq[Task[_]]]("run-before") + type Streams = std.Streams[ScopedKey[_]] type TaskStreams = std.TaskStreams[ScopedKey[_]] diff --git a/main/Load.scala b/main/Load.scala index bc7d8e5a8..51d9a0b3e 100644 --- a/main/Load.scala +++ b/main/Load.scala @@ -120,7 +120,7 @@ object Load if(isDummy(tk)) tk else Task(tk.info.set(Keys.taskDefinitionKey, key), tk.work) def structureIndex(settings: Settings[Scope]): StructureIndex = - new StructureIndex(Index.stringToKeyMap(settings), Index.taskToKeyMap(settings), KeyIndex(settings.allKeys( (s,k) => ScopedKey(s,k)))) + new StructureIndex(Index.stringToKeyMap(settings), Index.taskToKeyMap(settings), Index.triggers(settings), KeyIndex(settings.allKeys( (s,k) => ScopedKey(s,k)))) // Reevaluates settings after modifying them. Does not recompile or reload any build components. def reapply(newSettings: Seq[Setting[_]], structure: BuildStructure): BuildStructure = @@ -444,7 +444,7 @@ object Load } final case class LoadBuildConfiguration(stagingDirectory: File, commonPluginClasspath: Seq[Attributed[File]], classpath: Seq[File], loader: ClassLoader, compilers: Compilers, evalPluginDef: (BuildStructure, State) => Seq[Attributed[File]], delegates: LoadedBuild => Scope => Seq[Scope], scopeLocal: ScopeLocal, injectSettings: Seq[Setting[_]], log: Logger) // information that is not original, but can be reconstructed from the rest of BuildStructure - final class StructureIndex(val keyMap: Map[String, AttributeKey[_]], val taskToKey: Map[Task[_], ScopedKey[Task[_]]], val keyIndex: KeyIndex) + final class StructureIndex(val keyMap: Map[String, AttributeKey[_]], val taskToKey: Map[Task[_], ScopedKey[Task[_]]], val triggers: Triggers[Task], val keyIndex: KeyIndex) private[this] def memo[A,B](f: A => B): A => B = { diff --git a/main/Structure.scala b/main/Structure.scala index fa60acc57..a4268cfa0 100644 --- a/main/Structure.scala +++ b/main/Structure.scala @@ -213,6 +213,22 @@ object Scoped def dependOn: Initialize[Task[Unit]] = Apply.tasks(KList.fromList(keys)) { kl => nop.dependsOn(kl.toList :_*) } } + + implicit def richInitializeTask[T](init: Initialize[Task[T]]): RichInitializeTask[T] = new RichInitializeTask(init) + final class RichInitializeTask[T](init: Initialize[Task[T]]) + { + def triggeredBy(tasks: ScopedTask[_]*): Initialize[Task[T]] = nonLocal(tasks, Keys.triggeredBy) + def runBefore(tasks: ScopedTask[_]*): Initialize[Task[T]] = nonLocal(tasks, Keys.runBefore) + private[this] def nonLocal(tasks: Seq[ScopedTask[_]], key: AttributeKey[Seq[Task[_]]]): Initialize[Task[T]] = + { + val getTasks = Apply.tasks(KList.fromList(tasks))(idFun) + (getTasks zipWith init) { (tasks, i) => + i.copy(info = i.info.set(key, tasks.toList)) + } + } + } + + implicit def richFileSetting(s: ScopedSetting[File]): RichFileSetting = new RichFileSetting(s) implicit def richFilesSetting(s: ScopedSetting[Seq[File]]): RichFilesSetting = new RichFilesSetting(s) diff --git a/tasks/Execute.scala b/tasks/Execute.scala index 695930a6b..d43662ce1 100644 --- a/tasks/Execute.scala +++ b/tasks/Execute.scala @@ -26,9 +26,9 @@ object Execute sealed trait Completed { def process(): Unit } +final class Triggers[A[_]](val runBefore: collection.Map[A[_], Seq[A[_]]], val injectFor: collection.Map[A[_], Seq[A[_]]]) - -final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeView[A] ) +final class Execute[A[_] <: AnyRef](checkCycles: Boolean, triggers: Triggers[A])(implicit view: NodeView[A]) { type Strategy = CompletionService[A[_], Completed] @@ -119,6 +119,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie state(node) = Done remove( reverse, node ) foreach { dep => notifyDone(node, dep) } callers.remove( node ).flatten.foreach { c => retire(c, callerResult(c, result)) } + triggeredBy( node ) foreach { t => addChecked(t) } post { assert( done(node) ) @@ -126,6 +127,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie readyInv( node ) assert( ! (reverse contains node) ) assert( ! (callers contains node) ) + assert( triggeredBy(node) forall added ) } } def callerResult[T](node: A[T], result: Result[T]): Result[T] = @@ -161,7 +163,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie pre { newPre(node) } val v = register( node ) - val deps = dependencies(v) + val deps = dependencies(v) ++ runBefore(node) val active = IDSet[A[_]](deps filter notDone ) if( active.isEmpty) @@ -241,6 +243,10 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie def dependencies(node: A[_]): Iterable[A[_]] = dependencies(viewCache(node)) def dependencies(v: Node[A, _]): Iterable[A[_]] = v.uniformIn ++ v.mixedIn.toList + def runBefore(node: A[_]): Seq[A[_]] = getSeq(triggers.runBefore, node) + def triggeredBy(node: A[_]): Seq[A[_]] = getSeq(triggers.injectFor, node) + def getSeq(map: collection.Map[A[_], Seq[A[_]]], node: A[_]): Seq[A[_]] = map.getOrElse(node, Nil) + // Contracts def addedInv(node: A[_]): Unit = topologicalSort(node) foreach addedCheck @@ -262,7 +268,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie def pendingInv(node: A[_]) { assert( atState(node, Pending) ) - assert( dependencies( node ) exists notDone ) + assert( (dependencies( node ) ++ runBefore(node) ) exists notDone ) } def runningInv( node: A[_] ) {