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