From 22a71f24325bca8b7ca78df47623ca403788e99e Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 12 Dec 2010 21:33:32 -0500 Subject: [PATCH] Settings --- main/LogManager.scala | 4 +- main/MultiProject.scala | 3 +- util/collection/Attributes.scala | 4 + util/collection/Dag.scala | 14 ++- util/collection/Settings.scala | 147 ++++++++++++++++++++++++++----- 5 files changed, 145 insertions(+), 27 deletions(-) diff --git a/main/LogManager.scala b/main/LogManager.scala index 97dd3e105..35bccc561 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -9,12 +9,12 @@ package sbt object LogManager { - def construct(context: Transform.Context[Project], settings: Settings) = (task: Task[_], to: PrintWriter) => + def construct(context: Transform.Context[Project]) = (task: Task[_], to: PrintWriter) => { val owner = context owner task val ownerName = owner flatMap ( context ownerName _ ) getOrElse "" val taskPath = (context staticName task).toList ::: ownerName :: Nil - def level(key: AttributeKey[Level.Value], default: Level.Value): Level.Value = settings.get(key, taskPath) getOrElse default + def level(key: AttributeKey[Level.Value], default: Level.Value): Level.Value = default//settings.get(key, taskPath) getOrElse default val screenLevel = level(ScreenLogLevel, Level.Info) val backingLevel = level(PersistLogLevel, Level.Debug) diff --git a/main/MultiProject.scala b/main/MultiProject.scala index a1347cabb..fd68a9b97 100644 --- a/main/MultiProject.scala +++ b/main/MultiProject.scala @@ -165,7 +165,6 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named { val info: ProjectInfo - def settings: Settings = Settings.empty def name: String = info.name getOrElse error("'name' not overridden") def normalizedName: String = StringUtilities.normalize(name) @@ -191,7 +190,7 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named val context = MultiProject.makeContext(this) val dummies = new Transform.Dummies(In, State, Streams, Context) def name(t: Task[_]): String = context.staticName(t.original) getOrElse std.Streams.name(t) - val mklog = LogManager.construct(context, settings) + val mklog = LogManager.construct(context) def getOwner(t: Task[_]) = context.owner(t.original).getOrElse(error("No owner for " + name(t.original) + "\n\t" + t.original)) val actualStreams = std.Streams(t => getOwner(t).streamBase / name(t), mklog ) val injected = new Transform.Injected( input, state, actualStreams ) diff --git a/util/collection/Attributes.scala b/util/collection/Attributes.scala index 4037884dd..0d9e9ade1 100644 --- a/util/collection/Attributes.scala +++ b/util/collection/Attributes.scala @@ -10,6 +10,7 @@ import Types._ // a single AttributeKey instance cannot conform to AttributeKey[T] for different Ts sealed trait AttributeKey[T] { def label: String + override final def toString = label } object AttributeKey { @@ -30,6 +31,9 @@ trait AttributeMap object AttributeMap { val empty: AttributeMap = new BasicAttributeMap(Map.empty) + implicit def toNatTrans(map: AttributeMap): AttributeKey ~> Id = new (AttributeKey ~> Id) { + def apply[T](key: AttributeKey[T]): T = map(key) + } } private class BasicAttributeMap(private val backing: Map[AttributeKey[_], Any]) extends AttributeMap { diff --git a/util/collection/Dag.scala b/util/collection/Dag.scala index 3ecc1f95b..55064eaef 100644 --- a/util/collection/Dag.scala +++ b/util/collection/Dag.scala @@ -14,21 +14,29 @@ object Dag import scala.collection.{mutable, JavaConversions}; import JavaConversions.{asIterable, asSet} - def topologicalSort[T](root: T)(dependencies: T => Iterable[T]) = { + // TODO: replace implementation with call to new version + def topologicalSort[T](root: T)(dependencies: T => Iterable[T]): List[T] = topologicalSort(root :: Nil)(dependencies) + + def topologicalSort[T](nodes: Iterable[T])(dependencies: T => Iterable[T]): List[T] = + { val discovered = new mutable.HashSet[T] val finished = asSet(new java.util.LinkedHashSet[T]) + def visitAll(nodes: Iterable[T]) = nodes foreach visit def visit(dag : T){ if (!discovered(dag)) { discovered(dag) = true; - dependencies(dag).foreach(visit); + visitAll(dependencies(dag)); finished += dag; } + else if(!finished(dag)) + throw new Cyclic(dag) } - visit(root); + visitAll(nodes); finished.toList; } + final class Cyclic(val value: Any) extends Exception("Cyclic reference involving " + value) } diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index ab85fd87d..a059e84da 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -1,29 +1,136 @@ package sbt -sealed trait Settings + import annotation.tailrec + import Settings._ + +sealed trait Settings[Scope] { - def get[T](key: AttributeKey[T], path: List[String]): Option[T] - def set[T](key: AttributeKey[T], path: List[String], value: T): Settings + def data: Scope => AttributeMap + def definitions: Scope => Definitions + def linear: Scope => Seq[Scope] + def get[T](scope: Scope, key: AttributeKey[T]): Option[T] + def set[T](scope: Scope, key: AttributeKey[T], value: T): Settings[Scope] +} +private final class Settings0[Scope](val data: Map[Scope, AttributeMap], val definitions: Map[Scope, Definitions], val linear: Scope => Seq[Scope]) extends Settings[Scope] +{ + def get[T](scope: Scope, key: AttributeKey[T]): Option[T] = + linear(scope).toStream.flatMap(sc => scopeLocal(sc, key) ).headOption + + private def scopeLocal[T](scope: Scope, key: AttributeKey[T]): Option[T] = + (data get scope).flatMap(_ get key) + + def set[T](scope: Scope, key: AttributeKey[T], value: T): Settings[Scope] = + { + val map = (data get scope) getOrElse AttributeMap.empty + val newData = data.updated(scope, map.put(key, value)) + new Settings0(newData, definitions, linear) + } } object Settings { - def empty: Settings = new Basic(Map.empty) - def x = 3 + type Data[Scope] = Map[Scope, AttributeMap] + type Init = Seq[Setting[_]] + type Keys = Set[AttributeKey[_]] - private[this] class Basic(val roots: Map[ List[String], AttributeMap ]) extends Settings + def make[Scope](inits: Iterable[(Scope,Init)], lzA: Scope => Seq[Scope]): Settings[Scope] = { - def get[T](key: AttributeKey[T], path: List[String]): Option[T] = - { - def notFound = path match { - case Nil => None - case x :: xs => get(key, xs) - } - (roots get path) flatMap ( _ get key ) orElse notFound - } - def set[T](key: AttributeKey[T], path: List[String], value: T): Settings = - { - val amap = (roots get path) getOrElse AttributeMap.empty - new Basic( roots updated(path, amap put(key, value)) ) - } + val definitions = inits map { case (scope, init) => (scope, compile(init)) } toMap; + val resolved = for( (scope, definition) <- definitions) yield (scope, resolveScopes(definition, lzA(scope), definitions) ) + val scopeDeps = resolved map { case (scope, requiredMap) => (scope, requiredMap.values) } toMap; + val ordered = Dag.topologicalSort(scopeDeps.keys)(scopeDeps) + val data = (Map.empty[Scope, AttributeMap] /: ordered) { (mp, scope) => add(mp, scope, definitions, resolved) } + new Settings0(data, definitions, lzA) } -} \ No newline at end of file + + private[this] def add[Scope](data: Data[Scope], scope: Scope, definitions: Map[Scope, Definitions], resolved: Map[Scope, Map[AttributeKey[_], Scope]]): Map[Scope, AttributeMap] = + data.updated(scope, mkScopeMap(data, definitions(scope), resolved(scope)) ) + + private[this] def mkScopeMap[Scope](data: Data[Scope], definitions: Definitions, definedIn: Map[AttributeKey[_], Scope]): AttributeMap = + { + val start = (AttributeMap.empty /: definitions.requires) ( (mp, key) => prepop(data, definedIn, mp, key)) + definitions eval start + } + + private[this] def prepop[T, Scope](data: Data[Scope], definedIn: Map[AttributeKey[_], Scope], mp: AttributeMap, key: AttributeKey[T]): AttributeMap = + mp.put(key, data(definedIn(key))(key)) + + private[this] def resolveScopes[Scope](definition: Definitions, search: Seq[Scope], definitions: Map[Scope, Definitions]): Map[AttributeKey[_], Scope] = + definition.requires.view.map(req => (req, resolveScope(req, search, definitions )) ).toMap + + private[this] def resolveScope[Scope](key: AttributeKey[_], search: Seq[Scope], definitions: Map[Scope, Definitions]): Scope = + search find defines(key, definitions) getOrElse { throw new Uninitialized(key) } + + private[this] def defines[Scope](key: AttributeKey[_], definitions: Map[Scope, Definitions])(scope: Scope): Boolean = + (definitions get scope).filter(_.provides contains key).isDefined + + final class Definitions(val provides: Keys, val requires: Keys, val eval: AttributeMap => AttributeMap) + + def value[T](key: AttributeKey[T])(value: => T): Setting[T] = new Value(key, value _) + def update[T](key: AttributeKey[T])(f: T => T): Setting[T] = new Update(key, f) + def app[HL <: HList, T](key: AttributeKey[T], inputs: KList[AttributeKey, HL])(f: HL => T): Setting[T] = new Apply(key, f, inputs) + + def compile(settings: Seq[Setting[_]]): Definitions = + { + val grpd = grouped(settings) + val sorted = sort(grpd) + val eval = (map: AttributeMap) => (map /: sorted)( (m, c) => c eval m ) + val provided = grpd.keySet.toSet + val requires = sorted.flatMap(_.dependencies).toSet -- provided ++ sorted.collect { case c if !c.selfContained => c.key } + new Definitions(provided, requires, eval) + } + private[this] def grouped(settings: Seq[Setting[_]]): Map[AttributeKey[_], Compiled] = + settings.groupBy(_.key) map { case (key: AttributeKey[t], actions) => + (key: AttributeKey[_], compileSetting(key, actions.asInstanceOf[Seq[Setting[t]]]) ) + } toMap; + + private[this] def compileSetting[T](key: AttributeKey[T], actions: Seq[Setting[T]]): Compiled = + { + val (alive, selfContained) = live(key, actions) + val f = (map: AttributeMap) => (map /: alive)(eval) + new Compiled(key, f, dependencies(actions), selfContained) + } + private[this] final class Compiled(val key: AttributeKey[_], val eval: AttributeMap => AttributeMap, val dependencies: Iterable[AttributeKey[_]], val selfContained: Boolean) { + override def toString = key.label + } + + private[this] def sort(actionMap: Map[AttributeKey[_], Compiled]): Seq[Compiled] = + Dag.topologicalSort(actionMap.values)( _.dependencies.flatMap(actionMap.get) ) + + private[this] def live[T](key: AttributeKey[T], actions: Seq[Setting[T]]): (Seq[Setting[T]], Boolean) = + { + val lastOverwrite = actions.lastIndexWhere(_ overwrite key) + val selfContained = lastOverwrite >= 0 + val alive = if(selfContained) actions.drop(lastOverwrite) else actions + (alive, selfContained) + } + private[this] def dependencies(actions: Seq[Setting[_]]): Seq[AttributeKey[_]] = actions.flatMap(_.dependsOn) + private[this] def eval[T](map: AttributeMap, a: Setting[T]): AttributeMap = + a match + { + case s: Value[T] => map.put(s.key, s.value()) + case u: Update[T] => map.put(u.key, u.f(map(u.key))) + case a: Apply[hl, T] => map.put(a.key, a.f(a.inputs.down(map))) + } + + sealed trait Setting[T] + { + def key: AttributeKey[T] + def overwrite(key: AttributeKey[T]): Boolean + def dependsOn: Seq[AttributeKey[_]] = Nil + } + private[this] final class Value[T](val key: AttributeKey[T], val value: () => T) extends Setting[T] + { + def overwrite(key: AttributeKey[T]) = true + } + private[this] final class Update[T](val key: AttributeKey[T], val f: T => T) extends Setting[T] + { + def overwrite(key: AttributeKey[T]) = false + } + private[this] final class Apply[HL <: HList, T](val key: AttributeKey[T], val f: HL => T, val inputs: KList[AttributeKey, HL]) extends Setting[T] + { + def overwrite(key: AttributeKey[T]) = inputs.toList.forall(_ ne key) + override def dependsOn = inputs.toList - key + } + + final class Uninitialized(key: AttributeKey[_]) extends Exception("Update on uninitialized setting " + key.label) +}