From 699840be3fec5fad486c19325945a63cbbfaa85d Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 14 Jan 2014 16:22:42 -0500 Subject: [PATCH] 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. --- main/settings/src/main/scala/sbt/Def.scala | 3 +- .../src/main/scala/sbt/std/SettingMacro.scala | 6 +- main/src/main/scala/sbt/Defaults.scala | 10 --- main/src/main/scala/sbt/ScopeFilter.scala | 4 +- .../src/main/scala/sbt/Settings.scala | 82 +++++++++++++++---- .../src/test/scala/SettingsTest.scala | 50 +---------- 6 files changed, 71 insertions(+), 84 deletions(-) diff --git a/main/settings/src/main/scala/sbt/Def.scala b/main/settings/src/main/scala/sbt/Def.scala index e8735abc7..1ebc8e53d 100644 --- a/main/settings/src/main/scala/sbt/Def.scala +++ b/main/settings/src/main/scala/sbt/Def.scala @@ -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] diff --git a/main/settings/src/main/scala/sbt/std/SettingMacro.scala b/main/settings/src/main/scala/sbt/std/SettingMacro.scala index e87cd8b65..8ed5586ca 100644 --- a/main/settings/src/main/scala/sbt/std/SettingMacro.scala +++ b/main/settings/src/main/scala/sbt/std/SettingMacro.scala @@ -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]) } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index f42374f1d..3c9a5b1cd 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -814,16 +814,6 @@ object Defaults extends BuildCommon recurse ?? Nil } - @deprecated("Use the new .all() 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 .all() 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] = diff --git a/main/src/main/scala/sbt/ScopeFilter.scala b/main/src/main/scala/sbt/ScopeFilter.scala index c2f2a65e2..a91d41539 100644 --- a/main/src/main/scala/sbt/ScopeFilter.scala +++ b/main/src/main/scala/sbt/ScopeFilter.scala @@ -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) } diff --git a/util/collection/src/main/scala/sbt/Settings.scala b/util/collection/src/main/scala/sbt/Settings.scala index 7ce4bfc13..4e0e0b673 100644 --- a/util/collection/src/main/scala/sbt/Settings.scala +++ b/util/collection/src/main/scala/sbt/Settings.scala @@ -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) diff --git a/util/collection/src/test/scala/SettingsTest.scala b/util/collection/src/test/scala/SettingsTest.scala index 9ff703526..b9d908fd7 100644 --- a/util/collection/src/test/scala/SettingsTest.scala +++ b/util/collection/src/test/scala/SettingsTest.scala @@ -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) - } -} \ No newline at end of file