mirror of https://github.com/sbt/sbt.git
fully-scoped Settings
This commit is contained in:
parent
6960d24158
commit
7ced3c0406
|
|
@ -3,7 +3,8 @@
|
||||||
*/
|
*/
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
import Types._
|
import Types._
|
||||||
|
import collection.mutable
|
||||||
|
|
||||||
trait RMap[K[_], V[_]]
|
trait RMap[K[_], V[_]]
|
||||||
{
|
{
|
||||||
|
|
@ -11,39 +12,79 @@ trait RMap[K[_], V[_]]
|
||||||
def get[T](k: K[T]): Option[V[T]]
|
def get[T](k: K[T]): Option[V[T]]
|
||||||
def contains[T](k: K[T]): Boolean
|
def contains[T](k: K[T]): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait IMap[K[_], V[_]] extends (K ~> V) with RMap[K,V]
|
||||||
|
{
|
||||||
|
def put[T](k: K[T], v: V[T]): IMap[K,V]
|
||||||
|
def remove[T](k: K[T]): IMap[K,V]
|
||||||
|
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): IMap[K,V]
|
||||||
|
def mapValues[V2[_]](f: V ~> V2): IMap[K,V2]
|
||||||
|
def toSeq: Seq[(K[_], V[_])]
|
||||||
|
}
|
||||||
trait PMap[K[_], V[_]] extends (K ~> V) with RMap[K,V]
|
trait PMap[K[_], V[_]] extends (K ~> V) with RMap[K,V]
|
||||||
{
|
{
|
||||||
def update[T](k: K[T], v: V[T]): Unit
|
def update[T](k: K[T], v: V[T]): Unit
|
||||||
def remove[T](k: K[T]): Option[V[T]]
|
def remove[T](k: K[T]): Option[V[T]]
|
||||||
def getOrUpdate[T](k: K[T], make: => V[T]): V[T]
|
def getOrUpdate[T](k: K[T], make: => V[T]): V[T]
|
||||||
|
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): V[T]
|
||||||
}
|
}
|
||||||
object PMap
|
object PMap
|
||||||
{
|
{
|
||||||
implicit def toFunction[K[_], V[_]](map: PMap[K,V]): K[_] => V[_] = k => map(k)
|
implicit def toFunction[K[_], V[_]](map: PMap[K,V]): K[_] => V[_] = k => map(k)
|
||||||
|
def empty[K[_], V[_]]: PMap[K,V] = new DelegatingPMap[K,V](new mutable.HashMap)
|
||||||
|
}
|
||||||
|
object IMap
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Only suitable for K that is invariant in its type parameter.
|
||||||
|
* Option and List keys are not suitable, for example,
|
||||||
|
* because None <:< Option[String] and None <: Option[Int].
|
||||||
|
*/
|
||||||
|
def empty[K[_], V[_]]: IMap[K,V] = new IMap0[K,V](Map.empty)
|
||||||
|
|
||||||
|
private[this] class IMap0[K[_], V[_]](backing: Map[K[_], V[_]]) extends AbstractRMap[K,V] with IMap[K,V]
|
||||||
|
{
|
||||||
|
def get[T](k: K[T]): Option[V[T]] = ( backing get k ).asInstanceOf[Option[V[T]]]
|
||||||
|
def put[T](k: K[T], v: V[T]) = new IMap0[K,V]( backing.updated(k, v) )
|
||||||
|
def remove[T](k: K[T]) = new IMap0[K,V]( backing - k )
|
||||||
|
|
||||||
|
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]) =
|
||||||
|
put(k, f(this get k getOrElse init))
|
||||||
|
|
||||||
|
def mapValues[V2[_]](f: V ~> V2) =
|
||||||
|
new IMap0[K,V2](backing.mapValues(x => f(x)))
|
||||||
|
def toSeq = backing.toSeq
|
||||||
|
|
||||||
|
override def toString = backing.toString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractPMap[K[_], V[_]] extends PMap[K,V]
|
abstract class AbstractRMap[K[_], V[_]] extends RMap[K,V]
|
||||||
{
|
{
|
||||||
def apply[T](k: K[T]): V[T] = get(k).get
|
def apply[T](k: K[T]): V[T] = get(k).get
|
||||||
def contains[T](k: K[T]): Boolean = get(k).isDefined
|
def contains[T](k: K[T]): Boolean = get(k).isDefined
|
||||||
}
|
}
|
||||||
|
|
||||||
import collection.mutable.Map
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only suitable for K that is invariant in its type parameter.
|
* Only suitable for K that is invariant in its type parameter.
|
||||||
* Option and List keys are not suitable, for example,
|
* Option and List keys are not suitable, for example,
|
||||||
* because None <:< Option[String] and None <: Option[Int].
|
* because None <:< Option[String] and None <: Option[Int].
|
||||||
*/
|
*/
|
||||||
class DelegatingPMap[K[_], V[_]](backing: Map[K[_], V[_]]) extends AbstractPMap[K,V]
|
class DelegatingPMap[K[_], V[_]](backing: mutable.Map[K[_], V[_]]) extends AbstractRMap[K,V] with PMap[K,V]
|
||||||
{
|
{
|
||||||
def get[T](k: K[T]): Option[V[T]] = cast[T]( backing.get(k) )
|
def get[T](k: K[T]): Option[V[T]] = cast[T]( backing.get(k) )
|
||||||
def update[T](k: K[T], v: V[T]) { backing(k) = v }
|
def update[T](k: K[T], v: V[T]) { backing(k) = v }
|
||||||
def remove[T](k: K[T]) = cast( backing.remove(k) )
|
def remove[T](k: K[T]) = cast( backing.remove(k) )
|
||||||
def getOrUpdate[T](k: K[T], make: => V[T]) = cast[T]( backing.getOrElseUpdate(k, make) )
|
def getOrUpdate[T](k: K[T], make: => V[T]) = cast[T]( backing.getOrElseUpdate(k, make) )
|
||||||
|
def mapValue[T](k: K[T], init: V[T], f: V[T] => V[T]): V[T] =
|
||||||
|
{
|
||||||
|
val v = f(this get k getOrElse init)
|
||||||
|
update(k, v)
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
private[this] def cast[T](v: V[_]): V[T] = v.asInstanceOf[V[T]]
|
private[this] def cast[T](v: V[_]): V[T] = v.asInstanceOf[V[T]]
|
||||||
private[this] def cast[T](o: Option[V[_]]): Option[V[T]] = o map cast[T]
|
private[this] def cast[T](o: Option[V[_]]): Option[V[T]] = o map cast[T]
|
||||||
|
|
||||||
override def toString = backing.toString
|
override def toString = backing.toString
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
|
import Types._
|
||||||
import annotation.tailrec
|
import annotation.tailrec
|
||||||
import Settings._
|
import collection.mutable
|
||||||
|
|
||||||
sealed trait Settings[Scope]
|
sealed trait Settings[Scope]
|
||||||
{
|
{
|
||||||
def data: Scope => AttributeMap
|
def data: Scope => AttributeMap
|
||||||
def definitions: Scope => Definitions
|
def scopes: Seq[Scope]
|
||||||
def linear: Scope => Seq[Scope]
|
|
||||||
def get[T](scope: Scope, key: AttributeKey[T]): Option[T]
|
def get[T](scope: Scope, key: AttributeKey[T]): Option[T]
|
||||||
def set[T](scope: Scope, key: AttributeKey[T], value: T): Settings[Scope]
|
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]
|
|
||||||
|
private final class Settings0[Scope](val data: Map[Scope, AttributeMap], val delegates: Scope => Seq[Scope]) extends Settings[Scope]
|
||||||
{
|
{
|
||||||
|
def scopes: Seq[Scope] = data.keys.toSeq
|
||||||
|
|
||||||
def get[T](scope: Scope, key: AttributeKey[T]): Option[T] =
|
def get[T](scope: Scope, key: AttributeKey[T]): Option[T] =
|
||||||
linear(scope).toStream.flatMap(sc => scopeLocal(sc, key) ).headOption
|
delegates(scope).toStream.flatMap(sc => scopeLocal(sc, key) ).headOption
|
||||||
|
|
||||||
private def scopeLocal[T](scope: Scope, key: AttributeKey[T]): Option[T] =
|
private def scopeLocal[T](scope: Scope, key: AttributeKey[T]): Option[T] =
|
||||||
(data get scope).flatMap(_ get key)
|
(data get scope).flatMap(_ get key)
|
||||||
|
|
@ -23,114 +26,118 @@ private final class Settings0[Scope](val data: Map[Scope, AttributeMap], val def
|
||||||
{
|
{
|
||||||
val map = (data get scope) getOrElse AttributeMap.empty
|
val map = (data get scope) getOrElse AttributeMap.empty
|
||||||
val newData = data.updated(scope, map.put(key, value))
|
val newData = data.updated(scope, map.put(key, value))
|
||||||
new Settings0(newData, definitions, linear)
|
new Settings0(newData, delegates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object Settings
|
// delegates should contain the input Scope as the first entry
|
||||||
|
final class Init[Scope](val delegates: Scope => Seq[Scope])
|
||||||
{
|
{
|
||||||
type Data[Scope] = Map[Scope, AttributeMap]
|
final case class ScopedKey[T](scope: Scope, key: AttributeKey[T])
|
||||||
type Init = Seq[Setting[_]]
|
|
||||||
type Keys = Set[AttributeKey[_]]
|
|
||||||
|
|
||||||
def make[Scope](inits: Iterable[(Scope,Init)], lzA: Scope => Seq[Scope]): Settings[Scope] =
|
type SettingSeq[T] = Seq[Setting[T]]
|
||||||
{
|
type ScopedMap = IMap[ScopedKey, SettingSeq]
|
||||||
val definitions = inits map { case (scope, init) => (scope, compile(init)) } toMap;
|
type CompiledMap = Map[ScopedKey[_], Compiled]
|
||||||
val resolved = for( (scope, definition) <- definitions) yield (scope, resolveScopes(definition, lzA(scope), definitions) )
|
type MapScoped = ScopedKey ~> ScopedKey
|
||||||
val scopeDeps = resolved map { case (scope, requiredMap) => (scope, requiredMap.values) } toMap;
|
|
||||||
val ordered = Dag.topologicalSort(scopeDeps.keys)(scopeDeps)
|
def value[T](key: ScopedKey[T])(value: => T): Setting[T] = new Value(key, value _)
|
||||||
val data = (Map.empty[Scope, AttributeMap] /: ordered) { (mp, scope) => add(mp, scope, definitions, resolved) }
|
def update[T](key: ScopedKey[T])(f: T => T): Setting[T] = app(key, key :^: KNil)(h => f(h.head))
|
||||||
new Settings0(data, definitions, lzA)
|
def app[HL <: HList, T](key: ScopedKey[T], inputs: KList[ScopedKey, HL])(f: HL => T): Setting[T] = new Apply(key, f, inputs)
|
||||||
|
|
||||||
|
def empty: Settings[Scope] = new Settings0(Map.empty, delegates)
|
||||||
|
def asTransform(s: Settings[Scope]): ScopedKey ~> Id = new (ScopedKey ~> Id) {
|
||||||
|
def apply[T](k: ScopedKey[T]): T = s.get(k.scope, k.key).get
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def add[Scope](data: Data[Scope], scope: Scope, definitions: Map[Scope, Definitions], resolved: Map[Scope, Map[AttributeKey[_], Scope]]): Map[Scope, AttributeMap] =
|
def make(init: Seq[Setting[_]]): Settings[Scope] =
|
||||||
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))
|
// group by Scope/Key, dropping dead initializations
|
||||||
definitions eval start
|
val sMap: ScopedMap = grouped(init)
|
||||||
|
// delegate references to undefined values according to 'delegates'
|
||||||
|
val dMap: ScopedMap = delegate(sMap)
|
||||||
|
// merge Seq[Setting[_]] into Compiled
|
||||||
|
val cMap: CompiledMap = compile(dMap)
|
||||||
|
// order the initializations. cyclic references are detected here.
|
||||||
|
val ordered: Seq[Compiled] = sort(cMap)
|
||||||
|
// evaluation: apply the initializations.
|
||||||
|
applyInits(ordered)
|
||||||
}
|
}
|
||||||
|
def sort(cMap: CompiledMap): Seq[Compiled] =
|
||||||
|
Dag.topologicalSort(cMap.values)(_.dependencies.map(cMap))
|
||||||
|
|
||||||
private[this] def prepop[T, Scope](data: Data[Scope], definedIn: Map[AttributeKey[_], Scope], mp: AttributeMap, key: AttributeKey[T]): AttributeMap =
|
def compile(sMap: ScopedMap): CompiledMap =
|
||||||
mp.put(key, data(definedIn(key))(key))
|
sMap.toSeq.map { case (k, ss) =>
|
||||||
|
val deps = ss flatMap { _.dependsOn }
|
||||||
private[this] def resolveScopes[Scope](definition: Definitions, search: Seq[Scope], definitions: Map[Scope, Definitions]): Map[AttributeKey[_], Scope] =
|
val eval = (settings: Settings[Scope]) => (settings /: ss)(applySetting)
|
||||||
definition.requires.view.map(req => (req, resolveScope(req, search, definitions )) ).toMap
|
(k, new Compiled(deps, eval))
|
||||||
|
|
||||||
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;
|
} toMap;
|
||||||
|
|
||||||
private[this] def compileSetting[T](key: AttributeKey[T], actions: Seq[Setting[T]]): Compiled =
|
def grouped(init: Seq[Setting[_]]): ScopedMap =
|
||||||
|
((IMap.empty : ScopedMap) /: init) ( (m,s) => add(m,s) )
|
||||||
|
|
||||||
|
def add[T](m: ScopedMap, s: Setting[T]): ScopedMap =
|
||||||
|
m.mapValue[T]( s.key, Nil, ss => append(ss, s))
|
||||||
|
|
||||||
|
def append[T](ss: Seq[Setting[T]], s: Setting[T]): Seq[Setting[T]] =
|
||||||
|
if(s.definitive) s :: Nil else ss :+ s
|
||||||
|
|
||||||
|
def delegate(sMap: ScopedMap): ScopedMap =
|
||||||
{
|
{
|
||||||
val (alive, selfContained) = live(key, actions)
|
val md = memoDelegates
|
||||||
val f = (map: AttributeMap) => (map /: alive)(eval)
|
def refMap(refKey: ScopedKey[_]) = new (ScopedKey ~> ScopedKey) { def apply[T](k: ScopedKey[T]) = mapReferenced(sMap, k, md(k.scope), refKey) }
|
||||||
new Compiled(key, f, dependencies(actions), selfContained)
|
val f = new (SettingSeq ~> SettingSeq) { def apply[T](ks: Seq[Setting[T]]) = ks.map{ s => s mapReferenced refMap(s.key) } }
|
||||||
|
sMap mapValues f
|
||||||
}
|
}
|
||||||
private[this] final class Compiled(val key: AttributeKey[_], val eval: AttributeMap => AttributeMap, val dependencies: Iterable[AttributeKey[_]], val selfContained: Boolean) {
|
private[this] def mapReferenced[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], refKey: ScopedKey[_]): ScopedKey[T] =
|
||||||
override def toString = key.label
|
{
|
||||||
|
val scache = PMap.empty[ScopedKey, ScopedKey]
|
||||||
|
def resolve(search: Seq[Scope]): ScopedKey[T] =
|
||||||
|
search match {
|
||||||
|
case Seq() => throw new Uninitialized(k)
|
||||||
|
case Seq(x, xs @ _*) =>
|
||||||
|
val sk = ScopedKey(x, k.key)
|
||||||
|
scache.getOrUpdate(sk, if(defines(sMap, sk, refKey)) sk else resolve(xs))
|
||||||
|
}
|
||||||
|
resolve(scopes)
|
||||||
|
}
|
||||||
|
private[this] def defines(map: ScopedMap, key: ScopedKey[_], refKey: ScopedKey[_]): Boolean =
|
||||||
|
(map get key) match { case Some(Seq(x, _*)) => (refKey != key) || x.definitive; case _ => false }
|
||||||
|
|
||||||
|
private[this] def applyInits(ordered: Seq[Compiled]): Settings[Scope] =
|
||||||
|
(empty /: ordered){ (m, comp) => comp.eval(m) }
|
||||||
|
|
||||||
|
private[this] def memoDelegates: Scope => Seq[Scope] =
|
||||||
|
{
|
||||||
|
val dcache = new mutable.HashMap[Scope, Seq[Scope]]
|
||||||
|
(scope: Scope) => dcache.getOrElseUpdate(scope, delegates(scope))
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def sort(actionMap: Map[AttributeKey[_], Compiled]): Seq[Compiled] =
|
private[this] def applySetting[T](map: Settings[Scope], a: Setting[T]): Settings[Scope] =
|
||||||
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
|
a match
|
||||||
{
|
{
|
||||||
case s: Value[T] => map.put(s.key, s.value())
|
case s: Value[T] => map.set(s.key.scope, s.key.key, s.value())
|
||||||
case u: Update[T] => map.put(u.key, u.f(map(u.key)))
|
case a: Apply[hl, T] => map.set(a.key.scope, a.key.key, a.f(a.inputs.down(asTransform(map))))
|
||||||
case a: Apply[hl, T] => map.put(a.key, a.f(a.inputs.down(map)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Uninitialized(key: ScopedKey[_]) extends Exception("Update on uninitialized setting " + key.key.label + " (in " + key.scope + ")")
|
||||||
|
final class Compiled(val dependencies: Iterable[ScopedKey[_]], val eval: Settings[Scope] => Settings[Scope])
|
||||||
|
|
||||||
sealed trait Setting[T]
|
sealed trait Setting[T]
|
||||||
{
|
{
|
||||||
def key: AttributeKey[T]
|
def key: ScopedKey[T]
|
||||||
def overwrite(key: AttributeKey[T]): Boolean
|
def definitive: Boolean
|
||||||
def dependsOn: Seq[AttributeKey[_]] = Nil
|
def dependsOn: Seq[ScopedKey[_]]
|
||||||
|
def mapReferenced(f: MapScoped): Setting[T]
|
||||||
}
|
}
|
||||||
private[this] final class Value[T](val key: AttributeKey[T], val value: () => T) extends Setting[T]
|
private[this] final class Value[T](val key: ScopedKey[T], val value: () => T) extends Setting[T]
|
||||||
{
|
{
|
||||||
def overwrite(key: AttributeKey[T]) = true
|
def definitive = true
|
||||||
|
def dependsOn = Nil
|
||||||
|
def mapReferenced(f: MapScoped) = this
|
||||||
}
|
}
|
||||||
private[this] final class Update[T](val key: AttributeKey[T], val f: T => T) extends Setting[T]
|
private[this] final class Apply[HL <: HList, T](val key: ScopedKey[T], val f: HL => T, val inputs: KList[ScopedKey, HL]) extends Setting[T]
|
||||||
{
|
{
|
||||||
def overwrite(key: AttributeKey[T]) = false
|
def definitive = !inputs.toList.contains(key)
|
||||||
|
def dependsOn = inputs.toList - key
|
||||||
|
def mapReferenced(g: MapScoped) = new Apply(key, f, inputs transform g)
|
||||||
}
|
}
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue