Provide Def.early instead of Def.bind.

This makes setting evaluation effectively static.
A small exception is made for the computation structure to depend on "constant" inputs.
This allows for ScopeFilter and all to work, but is less general than full dynamic support.

This is more efficient to evaluate, covers known use cases, and will allow future use cases
like moving cross-building within the task system.
This commit is contained in:
Mark Harrah
2014-01-14 16:22:42 -05:00
parent 4b73a62653
commit 699840be3f
6 changed files with 71 additions and 84 deletions
+1 -2
View File
@@ -62,13 +62,12 @@ object Def extends Init[Scope] with TaskMacroExtra
import language.experimental.macros
import std.TaskMacro.{inputTaskMacroImpl, inputTaskDynMacroImpl, taskDynMacroImpl, taskMacroImpl}
import std.SettingMacro.{settingDynMacroImpl,settingMacroImpl}
import std.SettingMacro.settingMacroImpl
import std.{InputEvaluated, MacroValue, MacroTaskValue, ParserInput}
def task[T](t: T): Def.Initialize[Task[T]] = macro taskMacroImpl[T]
def taskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[Task[T]] = macro taskDynMacroImpl[T]
def setting[T](t: T): Def.Initialize[T] = macro settingMacroImpl[T]
def settingDyn[T](t: Def.Initialize[T]): Def.Initialize[T] = macro settingDynMacroImpl[T]
def inputTask[T](t: T): Def.Initialize[InputTask[T]] = macro inputTaskMacroImpl[T]
def inputTaskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[InputTask[T]] = macro inputTaskDynMacroImpl[T]
@@ -5,12 +5,11 @@ package std
import Types.{idFun,Id}
import appmacro.{Convert, Converted, Instance, MixedBuilder, MonadInstance}
object InitializeInstance extends MonadInstance
object InitializeInstance extends Instance
{
type M[x] = Initialize[x]
def app[K[L[x]], Z](in: K[Initialize], f: K[Id] => Z)(implicit a: AList[K]): Initialize[Z] = Def.app[K,Z](in)(f)(a)
def map[S,T](in: Initialize[S], f: S => T): Initialize[T] = Def.map(in)(f)
def flatten[T](in: Initialize[Initialize[T]]): Initialize[T] = Def.bind(in)(idFun[Initialize[T]])
def pure[T](t: () => T): Initialize[T] = Def.pure(t)
}
@@ -37,7 +36,4 @@ object SettingMacro
{
def settingMacroImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]): c.Expr[Initialize[T]] =
Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder)(Left(t), Instance.idTransform[c.type])
def settingDynMacroImpl[T: c.WeakTypeTag](c: Context)(t: c.Expr[Initialize[T]]): c.Expr[Initialize[T]] =
Instance.contImpl[T, Id](c, InitializeInstance, InitializeConvert, MixedBuilder)(Right(t), Instance.idTransform[c.type])
}
-10
View File
@@ -814,16 +814,6 @@ object Defaults extends BuildCommon
recurse ?? Nil
}
@deprecated("Use the new <key>.all(<ScopeFilter>) API", "0.13.0")
def inDependencies[T](key: SettingKey[T], default: ProjectRef => T, includeRoot: Boolean = true, classpath: Boolean = true, aggregate: Boolean = false): Initialize[Seq[T]] =
forDependencies[T,T](ref => (key in ref) ?? default(ref), includeRoot, classpath, aggregate)
@deprecated("Use the new <key>.all(<ScopeFilter>) API", "0.13.0")
def forDependencies[T,V](init: ProjectRef => Initialize[V], includeRoot: Boolean = true, classpath: Boolean = true, aggregate: Boolean = false): Initialize[Seq[V]] =
Def.bind( (loadedBuild, thisProjectRef).identity ) { case (lb, base) =>
transitiveDependencies(base, lb, includeRoot, classpath, aggregate) map init join ;
}
def transitiveDependencies(base: ProjectRef, structure: LoadedBuild, includeRoot: Boolean, classpath: Boolean = true, aggregate: Boolean = false): Seq[ProjectRef] =
{
def tdeps(enabled: Boolean, f: ProjectRef => Seq[ProjectRef]): Seq[ProjectRef] =
+2 -2
View File
@@ -45,14 +45,14 @@ object ScopeFilter
final class SettingKeyAll[T] private[sbt](i: Initialize[T]) {
/** Evaluates the initialization in all scopes selected by the filter. These are dynamic dependencies, so
* static inspections will not show them. */
def all(sfilter: => ScopeFilter): Initialize[Seq[T]] = Def.bind(getData) { data =>
def all(sfilter: => ScopeFilter): Initialize[Seq[T]] = Def.early(getData) { data =>
data.allScopes.toSeq.filter(sfilter(data)).map(s => Project.inScope(s, i)).join
}
}
final class TaskKeyAll[T] private[sbt](i: Initialize[Task[T]]) {
/** Evaluates the task in all scopes selected by the filter. These are dynamic dependencies, so
* static inspections will not show them. */
def all(sfilter: => ScopeFilter): Initialize[Task[Seq[T]]] = Def.bind(getData) { data =>
def all(sfilter: => ScopeFilter): Initialize[Task[Seq[T]]] = Def.early(getData) { data =>
import std.TaskExtra._
data.allScopes.toSeq.filter(sfilter(data)).map(s => Project.inScope(s, i)).join(_.join)
}
@@ -63,17 +63,19 @@ trait Init[Scope]
* This can be useful when dealing with dynamic Initialize values. */
lazy val capturedTransformations: Initialize[Initialize ~> Initialize] = new TransformCapture(idK[Initialize])
def setting[T](key: ScopedKey[T], init: Initialize[T], pos: SourcePosition = NoPosition): Setting[T] = new Setting[T](key, init, pos)
def valueStrict[T](value: T): Initialize[T] = pure(() => value)
def valueStrict[T](value: T): Initialize[T] = new Constant(value)
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] = 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 early[T, S](i: Initialize[T])(f: T => Initialize[S]): Initialize[S] =
new Early(i, new TransformCapture(idK[Initialize]), f)
/** Constructs a derived setting that will be automatically defined in every scope where one of its dependencies
* is explicitly defined and the where the scope matches `filter`.
* A setting initialized with dynamic dependencies is only allowed if `allowDynamic` is true.
@@ -82,10 +84,8 @@ trait Init[Scope]
deriveAllowed(s, allowDynamic) foreach error
new DerivedSetting[T](s.key, s.init, s.pos, filter, trigger, nextDefaultID())
}
def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] = s.init match {
case _: Bind[_,_] if !allowDynamic => Some("Cannot derive from dynamic dependencies.")
case _ => None
}
def deriveAllowed[T](s: Setting[T], allowDynamic: Boolean): Option[String] = None
// id is used for equality
private[sbt] final def defaultSetting[T](s: Setting[T]): Setting[T] = s match {
case _: DefaultSetting[_] | _: DerivedSetting[_] => s
@@ -117,11 +117,37 @@ trait Init[Scope]
val (defaults, others) = Util.separate[Setting[_], DefaultSetting[_], Setting[_]](ss) { case u: DefaultSetting[_] => Left(u); case s => Right(s) }
defaults.distinct ++ others
}
private[this] def expandEarly(settings: Seq[Setting[_]])(implicit delegates: Scope => Seq[Scope]): Seq[Setting[_]] =
{
def const[T](ss: Settings[Scope], s: Setting[T]): Settings[Scope] =
s.init.constantValue match {
case Some(v) => ss.set(s.key.scope, s.key.key, v)
case None => ss
}
val predef = (empty /: settings)( (ss, s) => const(ss, s))
val static = settings.map(_.key.scope).toSet
lazy val subStatic: Initialize ~> Initialize = new (Initialize ~> Initialize) {
def apply[T](i: Initialize[T]): Initialize[T] = i match {
case StaticScopes => new Constant(static)
case _ => i.mapInputs(subStatic)
}
}
lazy val me: Initialize ~> Initialize = new (Initialize ~> Initialize) {
def apply[T](i: Initialize[T]): Initialize[T] = i match {
case e: Early[s, T] => e.mapInputs(subStatic).getInit(predef)
case _ => i.mapInputs(me)
}
}
settings.map(_.mapInitialize(me.fn))
}
def compiled(init: Seq[Setting[_]], actual: Boolean = true)(implicit delegates: Scope => Seq[Scope], scopeLocal: ScopeLocal, display: Show[ScopedKey[_]]): CompiledMap =
{
Time.block("compiled")
val initDefaults = applyDefaults(init)
val earlyExpanded = expandEarly(init)
Time("expandedEarly")
val initDefaults = applyDefaults(earlyExpanded)
Time("appliedDefaults")
// inject derived settings into scopes where their dependencies are directly defined
// and prepend per-scope settings
@@ -394,9 +420,12 @@ trait Init[Scope]
def mapReferenced(g: MapScoped): Initialize[T]
def validateReferenced(g: ValidateRef): ValidatedInit[T]
def mapConstant(g: MapConstant): Initialize[T]
// applies `g` to immediate dependencies only
def mapInputs(g: Initialize ~> Initialize): Initialize[T]
def evaluate(map: Settings[Scope]): T
def zip[S](o: Initialize[S]): Initialize[(T,S)] = zipTupled(o)(idFun)
def zipWith[S,U](o: Initialize[S])(f: (T,S) => U): Initialize[U] = zipTupled(o)(f.tupled)
private[sbt] def constantValue: Option[T] = None
private[this] def zipTupled[S,U](o: Initialize[S])(f: ((T,S)) => U): Initialize[U] =
new Apply[({ type l[L[x]] = (L[T], L[S]) })#l, U](f, (this, o), AList.tuple2[T,S])
}
@@ -485,6 +514,7 @@ trait Init[Scope]
final def apply[Z](g: T => Z): Initialize[Z] = new GetValue(scopedKey, g compose transform)
final def evaluate(ss: Settings[Scope]): T = transform(getValue(ss, scopedKey))
final def mapReferenced(g: MapScoped): Initialize[T] = new GetValue( g(scopedKey), transform)
final def mapInputs(g: Initialize ~> Initialize): Initialize[T] = this
final def validateReferenced(g: ValidateRef): ValidatedInit[T] = g(scopedKey) match {
case Left(un) => Left(un :: Nil)
case Right(nk) => Right(new GetValue(nk, transform))
@@ -503,26 +533,38 @@ trait Init[Scope]
def dependencies = Nil
def apply[Z](g2: (Initialize ~> Initialize) => Z): Initialize[Z] = map(this)(g2)
def evaluate(ss: Settings[Scope]): Initialize ~> Initialize = f
def mapInputs(g: Initialize ~> Initialize) = this
def mapReferenced(g: MapScoped) = new TransformCapture(mapReferencedT(g) ∙ f)
def mapConstant(g: MapConstant) = new TransformCapture(mapConstantT(g) ∙ f)
def validateReferenced(g: ValidateRef) = Right(new TransformCapture(getValidated ∙ validateReferencedT(g) ∙ f))
}
private[sbt] final class Bind[S,T](val f: S => Initialize[T], val in: Initialize[S]) extends Initialize[T]
/** Computes an Initialize from constants. This is a restricted form of flatMap.
* The only key references allowed in `captured` are to keys initialized by a constant.
* This allows the nested Initialize to be computed during setting processing. This computed
* Initialize will then be available to static analysis, and sbt's inspect command, and static transformations.
*/
private[sbt] final class Early[T, S](wrapped: Initialize[T], captured: TransformCapture, f: T => Initialize[S]) extends Initialize[S]
{
def dependencies = in.dependencies
def apply[Z](g: T => Z): Initialize[Z] = new Bind[S,Z](s => f(s)(g), in)
def evaluate(ss: Settings[Scope]): T = f(in evaluate ss) evaluate ss
def mapReferenced(g: MapScoped) = new Bind[S,T](s => f(s) mapReferenced g, in mapReferenced g)
def validateReferenced(g: ValidateRef) = (in validateReferenced g).right.map { validIn =>
new Bind[S,T](s => handleUndefined( f(s) validateReferenced g), validIn)
}
def mapConstant(g: MapConstant) = new Bind[S,T](s => f(s) mapConstant g, in mapConstant g)
def dependencies = wrapped.dependencies
def mapReferenced(g: MapScoped) = new Early(wrapped mapReferenced g, captured mapReferenced g, f)
def apply[U](g: S => U) = map(this)(g)
def mapConstant(g: MapConstant) = new Early(wrapped mapConstant g, captured mapConstant g, f)
def mapInputs(g: Initialize ~> Initialize) = new Early(g(wrapped), captured, f)
def evaluate(ss: Settings[Scope]) = getInit(ss).evaluate(ss)
def getInit(predef: Settings[Scope]): Initialize[S] = captured.evaluate(predef)( f(wrapped.evaluate(predef)) )
def validateReferenced(g: ValidateRef) =
wrapped.validateReferenced(g).right.flatMap { init =>
captured.validateReferenced(g).right.map { cap =>
new Early(init, cap, f)
}
}
}
private[sbt] final class Optional[S,T](val a: Option[Initialize[S]], val f: Option[S] => T) extends Initialize[T]
{
def dependencies = deps(a.toList)
def apply[Z](g: T => Z): Initialize[Z] = new Optional[S,Z](a, g compose f)
def mapReferenced(g: MapScoped) = new Optional(a map mapReferencedT(g).fn, f)
def mapInputs(g: Initialize ~> Initialize) = new Optional(a map g.fn, f)
def validateReferenced(g: ValidateRef) = a match {
case None => Right(this)
case Some(i) => Right( new Optional(i.validateReferenced(g).right.toOption, f) )
@@ -532,18 +574,24 @@ trait Init[Scope]
// proper solution is for evaluate to be deprecated or for external use only and a new internal method returning Either be used
private[this] def trapBadRef[A](run: => A): Option[A] = try Some(run) catch { case e: InvalidReference => None }
}
private[sbt] final class Value[T](val value: () => T) extends Initialize[T]
private[sbt] sealed class Value[T](val value: () => T) extends Initialize[T]
{
def dependencies = Nil
def mapReferenced(g: MapScoped) = this
def mapInputs(g: Initialize ~> Initialize) = this
def validateReferenced(g: ValidateRef) = Right(this)
def apply[S](g: T => S) = new Value[S](() => g(value()))
def mapConstant(g: MapConstant) = this
def evaluate(map: Settings[Scope]): T = value()
}
private[sbt] final class Constant[T](const: T) extends Value(() => const) {
override def apply[S](g: T => S) = new Constant(g(const))
override def constantValue = Some(const)
}
private[sbt] final object StaticScopes extends Initialize[Set[Scope]]
{
def dependencies = Nil
def mapInputs(g: Initialize ~> Initialize) = this
def mapReferenced(g: MapScoped) = this
def validateReferenced(g: ValidateRef) = Right(this)
def apply[S](g: Set[Scope] => S) = map(this)(g)
@@ -12,43 +12,13 @@ object SettingsTest extends Properties("settings")
property("Basic settings test") = secure( all( tests: _*) )
property("Basic chain") = forAll(chainLengthGen) { (i: Int) =>
property("Basic chain") = forAll(chainLengthGen) { (i: Int) =>
val abs = math.abs(i)
singleIntTest( chain( abs, value(0)), abs )
}
property("Basic bind chain") = forAll(chainLengthGen) { (i: Int) =>
val abs = math.abs(i)
singleIntTest( chainBind(value(abs)), 0 )
}
property("Allows references to completed settings") = forAllNoShrink(30) { allowedReference _ }
final def allowedReference(intermediate: Int): Prop =
{
val top = value(intermediate)
def iterate(init: Initialize[Int]): Initialize[Int] =
bind(init) { t =>
if(t <= 0)
top
else
iterate(value(t-1) )
}
try { evaluate( setting(chk, iterate(top)) :: Nil); true }
catch { case e: java.lang.Exception => ("Unexpected exception: " + e) |: false }
}
// Circular (dynamic) references currently loop infinitely.
// This is the expected behavior (detecting dynamic cycles is expensive),
// but it may be necessary to provide an option to detect them (with a performance hit)
// This would test that cycle detection.
// property("Catches circular references") = forAll(chainLengthGen) { checkCircularReferences _ }
final def checkCircularReferences(intermediate: Int): Prop =
{
val ccr = new CCR(intermediate)
try { evaluate( setting(chk, ccr.top) :: Nil); false }
catch { case e: java.lang.Exception => true }
}
def tests =
def tests =
for(i <- 0 to 5; k <- Seq(a, b)) yield {
val expected = expectedValues(2*i + (if(k == a) 0 else 1))
checkKey[Int]( ScopedKey( Scope(i), k ), expected, applied)
@@ -61,10 +31,6 @@ object SettingsTest extends Properties("settings")
def chain(i: Int, prev: Initialize[Int]): Initialize[Int] =
if(i <= 0) prev else chain(i - 1, prev(_ + 1))
def chainBind(prev: Initialize[Int]): Initialize[Int] =
bind(prev) { v =>
if(v <= 0) prev else chainBind(value(v - 1) )
}
def singleIntTest(i: Initialize[Int], expected: Int) =
{
val eval = evaluate( setting( chk, i ) :: Nil )
@@ -84,15 +50,3 @@ object SettingsTest extends Properties("settings")
try { make(settings)(delegates, scopeLocal, showFullKey) }
catch { case e: Throwable => e.printStackTrace; throw e }
}
// This setup is a workaround for module synchronization issues
final class CCR(intermediate: Int)
{
lazy val top = iterate(value(intermediate), intermediate)
def iterate(init: Initialize[Int], i: Int): Initialize[Int] =
bind(init) { t =>
if(t <= 0)
top
else
iterate(value(t - 1), t-1)
}
}