From dfe418b3c3cd64cca3ecfe0df558239dda984943 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Wed, 8 May 2013 12:56:58 -0400 Subject: [PATCH] Derived settings, which allows injecting settings wherever their dependencies are defined. This is an advanced feature initially intended for internal sbt use. --- main/settings/src/main/scala/sbt/Def.scala | 12 ++++ main/src/main/scala/sbt/Project.scala | 6 +- .../src/main/scala/sbt/Settings.scala | 71 ++++++++++++++++--- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/main/settings/src/main/scala/sbt/Def.scala b/main/settings/src/main/scala/sbt/Def.scala index 31c3eac88..77c5ed11e 100644 --- a/main/settings/src/main/scala/sbt/Def.scala +++ b/main/settings/src/main/scala/sbt/Def.scala @@ -3,6 +3,7 @@ package sbt import Types.const import complete.Parser import java.io.File + import Scope.ThisScope /** A concrete settings system that uses `sbt.Scope` for the scope type. */ object Def extends Init[Scope] with TaskMacroExtra @@ -35,6 +36,17 @@ object Def extends Init[Scope] with TaskMacroExtra case Some(c) => c + s + scala.Console.RESET case None => s } + + override def deriveAllowed[T](s: Setting[T]): Option[String] = + super.deriveAllowed(s) orElse + (if(s.key.scope != ThisScope) Some(s"Scope cannot be defined for ${definedSettingString(s)}") else None ) orElse + s.dependencies.find(k => k.scope != ThisScope).map(k => s"Scope cannot be defined for dependency ${k.key.label} of ${definedSettingString(s)}") + + private[this] def definedSettingString(s: Setting[_]): String = + s"derived setting ${s.key.key.label}${positionString(s)}" + private[this] def positionString(s: Setting[_]): String = + s.positionString match { case None => ""; case Some(pos) => s" defined at $pos" } + /** A default Parser for splitting input into space-separated arguments. * `argLabel` is an optional, fixed label shown for an argument during tab completion.*/ diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index b89709256..f687e9552 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -270,11 +270,7 @@ object Project extends ProjectExtra val definingScoped = definingScope match { case Some(sc) => ScopedKey(sc, key); case None => scoped } val comp = Def.compiled(structure.settings, actual)(structure.delegates, structure.scopeLocal, display) val definedAt = comp get definingScoped map { c => - def fmt(s: Def.Setting[_]) = s.pos match { - case pos: FilePosition => (pos.path + ":" + pos.startLine) :: Nil - case NoPosition => Nil - } - val posDefined = c.settings.flatMap(fmt) + val posDefined = c.settings.flatMap(_.positionString.toList) if (posDefined.size > 0) { val header = if (posDefined.size == c.settings.size) "Defined at:" else "Some of the defining occurrences:" diff --git a/util/collection/src/main/scala/sbt/Settings.scala b/util/collection/src/main/scala/sbt/Settings.scala index cf12a58fc..4f9bf5206 100644 --- a/util/collection/src/main/scala/sbt/Settings.scala +++ b/util/collection/src/main/scala/sbt/Settings.scala @@ -66,13 +66,22 @@ trait Init[Scope] def value[T](value: => T): Initialize[T] = pure(value _) def pure[T](value: () => T): Initialize[T] = new Value(value) def optional[T,U](i: Initialize[T])(f: Option[T] => U): Initialize[U] = new Optional(Some(i), f) - def update[T](key: ScopedKey[T])(f: T => T): Setting[T] = new Setting[T](key, map(key)(f), NoPosition) + def update[T](key: ScopedKey[T])(f: T => T): Setting[T] = setting[T](key, map(key)(f), NoPosition) def bind[S,T](in: Initialize[S])(f: S => Initialize[T]): Initialize[T] = new Bind(f, in) def map[S,T](in: Initialize[S])(f: S => T): Initialize[T] = new Apply[ ({ type l[L[x]] = L[S] })#l, T](f, in, AList.single[S]) def app[K[L[x]], T](inputs: K[Initialize])(f: K[Id] => T)(implicit alist: AList[K]): Initialize[T] = new Apply[K, T](f, inputs, alist) def uniform[S,T](inputs: Seq[Initialize[S]])(f: Seq[S] => T): Initialize[T] = new Apply[({ type l[L[x]] = List[L[S]] })#l, T](f, inputs.toList, AList.seq[S]) + def derive[T](s: Setting[T]): Setting[T] = { + deriveAllowed(s) foreach error + new DerivedSetting[T](s.key, s.init, s.pos) + } + def deriveAllowed[T](s: Setting[T]): Option[String] = s.init match { + case _: Bind[_,_] => Some("Cannot derive from dynamic dependencies.") + case _ => None + } + def empty(implicit delegates: Scope => Seq[Scope]): Settings[Scope] = new Settings0(Map.empty, delegates) def asTransform(s: Settings[Scope]): ScopedKey ~> Id = new (ScopedKey ~> Id) { def apply[T](k: ScopedKey[T]): T = getValue(s, k) @@ -83,10 +92,37 @@ trait Init[Scope] def apply[T](k: ScopedKey[T]): ScopedKey[T] = k.copy(scope = f(k.scope)) } + private[this] def plain[T](s: Setting[T]): Setting[T] = if(s.isDerived) new Setting(s.key, s.init, s.pos) else s + private[this] def derive(init: Seq[Setting[_]]): Seq[Setting[_]] = + { + import collection.mutable + val (derived, defs) = init.partition(_.isDerived) + val derivs = new mutable.HashMap[AttributeKey[_], mutable.ListBuffer[Setting[_]]] + for(s <- derived; d <- s.dependencies) + derivs.getOrElseUpdate(d.key, new mutable.ListBuffer) += plain(s) + + val deriveFor = (sk: ScopedKey[_]) => + derivs.get(sk.key).toList.flatMap(_.toList).map(_.setScope(sk.scope)) + + val processed = new mutable.HashSet[ScopedKey[_]] + val out = new mutable.ListBuffer[Setting[_]] + def process(rem: List[Setting[_]]): Unit = rem match { + case s :: ss => + val sk = s.key + val ds = if(processed.add(sk)) deriveFor(sk) else Nil + out ++= ds + process(ds ::: ss) + case Nil => + } + process(defs.toList) + out.toList ++ defs + } + def compiled(init: Seq[Setting[_]], actual: Boolean = true)(implicit delegates: Scope => Seq[Scope], scopeLocal: ScopeLocal, display: Show[ScopedKey[_]]): CompiledMap = { - // prepend per-scope settings - val withLocal = addLocal(init)(scopeLocal) + val derived = derive(init) + // prepend per-scope settings + val withLocal = addLocal(derived)(scopeLocal) // group by Scope/Key, dropping dead initializations val sMap: ScopedMap = grouped(withLocal) // delegate references to undefined values according to 'delegates' @@ -256,20 +292,33 @@ trait Init[Scope] def settings: Seq[Setting[_]] } final class SettingList(val settings: Seq[Setting[_]]) extends SettingsDefinition - final class Setting[T](val key: ScopedKey[T], val init: Initialize[T], val pos: SourcePosition) extends SettingsDefinition + sealed class Setting[T] private[Init](val key: ScopedKey[T], val init: Initialize[T], val pos: SourcePosition) extends SettingsDefinition { def settings = this :: Nil def definitive: Boolean = !init.dependencies.contains(key) def dependencies: Seq[ScopedKey[_]] = remove(init.dependencies, key) - def mapReferenced(g: MapScoped): Setting[T] = new Setting(key, init mapReferenced g, pos) - def validateReferenced(g: ValidateRef): Either[Seq[Undefined], Setting[T]] = (init validateReferenced g).right.map(newI => new Setting(key, newI, pos)) - def mapKey(g: MapScoped): Setting[T] = new Setting(g(key), init, pos) - def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = new Setting(key, init(t => f(key,t)), pos) - def mapConstant(g: MapConstant): Setting[T] = new Setting(key, init mapConstant g, pos) - def withPos(pos: SourcePosition) = new Setting(key, init, pos) - private[sbt] def mapInitialize(f: Initialize[T] => Initialize[T]): Setting[T] = new Setting(key, f(init), pos) + def mapReferenced(g: MapScoped): Setting[T] = make(key, init mapReferenced g, pos) + def validateReferenced(g: ValidateRef): Either[Seq[Undefined], Setting[T]] = (init validateReferenced g).right.map(newI => make(key, newI, pos)) + def mapKey(g: MapScoped): Setting[T] = make(g(key), init, pos) + def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = make(key, init(t => f(key,t)), pos) + def mapConstant(g: MapConstant): Setting[T] = make(key, init mapConstant g, pos) + def withPos(pos: SourcePosition) = make(key, init, pos) + def positionString: Option[String] = pos match { + case pos: FilePosition => Some(pos.path + ":" + pos.startLine) + case NoPosition => None + } + private[sbt] def mapInitialize(f: Initialize[T] => Initialize[T]): Setting[T] = make(key, f(init), pos) override def toString = "setting(" + key + ") at " + pos + + protected[this] def make[T](key: ScopedKey[T], init: Initialize[T], pos: SourcePosition): Setting[T] = new Setting[T](key, init, pos) + protected[sbt] def isDerived: Boolean = false + private[sbt] def setScope(s: Scope): Setting[T] = make(key.copy(scope = s), init.mapReferenced(mapScope(const(s))), pos) } + private[Init] final class DerivedSetting[T](sk: ScopedKey[T], i: Initialize[T], p: SourcePosition) extends Setting[T](sk, i, p) { + override def make[T](key: ScopedKey[T], init: Initialize[T], pos: SourcePosition): Setting[T] = new DerivedSetting[T](key, init, pos) + protected[sbt] override def isDerived: Boolean = true + } + private[this] def handleUndefined[T](vr: ValidatedInit[T]): Initialize[T] = vr match { case Left(undefs) => throw new RuntimeUndefined(undefs)