diff --git a/main/settings/src/main/scala/sbt/Def.scala b/main/settings/src/main/scala/sbt/Def.scala index e8735abc7..1d0a480a7 100644 --- a/main/settings/src/main/scala/sbt/Def.scala +++ b/main/settings/src/main/scala/sbt/Def.scala @@ -14,6 +14,7 @@ object Def extends Init[Scope] with TaskMacroExtra val triggeredBy = AttributeKey[Seq[Task[_]]]("triggered-by") val runBefore = AttributeKey[Seq[Task[_]]]("run-before") val resolvedScoped = SettingKey[ScopedKey[_]]("resolved-scoped", "The ScopedKey for the referencing setting or task.", KeyRanks.DSetting) + private[sbt] val taskDefinitionKey = AttributeKey[ScopedKey[_]]("task-definition-key", "Internal: used to map a task back to its ScopedKey.", Invisible) lazy val showFullKey: Show[ScopedKey[_]] = showFullKey(None) def showFullKey(keyNameColor: Option[String]): Show[ScopedKey[_]] = @@ -63,7 +64,7 @@ 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.{InputEvaluated, MacroValue, MacroTaskValue, ParserInput} + import std.{InputEvaluated, MacroPrevious, 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] @@ -79,6 +80,7 @@ object Def extends Init[Scope] with TaskMacroExtra implicit def macroValueIT[T](in: Initialize[Task[T]]): MacroValue[T] = ??? implicit def macroValueIInT[T](in: Initialize[InputTask[T]]): InputEvaluated[T] = ??? implicit def taskMacroValueIT[T](in: Initialize[Task[T]]): MacroTaskValue[T] = ??? + implicit def macroPrevious[T](in: TaskKey[T]): MacroPrevious[T] = ??? // The following conversions enable the types Parser[T], Initialize[Parser[T]], and Initialize[State => Parser[T]] to // be used in the inputTask macro as an input with an ultimate result of type T @@ -100,6 +102,7 @@ object Def extends Init[Scope] with TaskMacroExtra private[sbt] def isDummy(t: Task[_]): Boolean = t.info.attributes.get(isDummyTask) getOrElse false private[sbt] val isDummyTask = AttributeKey[Boolean]("is-dummy-task", "Internal: used to identify dummy tasks. sbt injects values for these tasks at the start of task execution.", Invisible) private[sbt] val (stateKey, dummyState) = dummy[State]("state", "Current build state.") + private[sbt] val (streamsManagerKey, dummyStreamsManager) = Def.dummy[std.Streams[ScopedKey[_]]]("streams-manager", "Streams manager, which provides streams for different contexts.") } // these need to be mixed into the sbt package object because the target doesn't involve Initialize or anything in Def trait TaskMacroExtra diff --git a/main/settings/src/main/scala/sbt/Previous.scala b/main/settings/src/main/scala/sbt/Previous.scala new file mode 100644 index 000000000..1784464f4 --- /dev/null +++ b/main/settings/src/main/scala/sbt/Previous.scala @@ -0,0 +1,99 @@ +package sbt + + import Def.{Initialize, resolvedScoped, ScopedKey, Setting, streamsManagerKey} + import Previous._ + import Types._ + + import java.io.{InputStream, OutputStream} + import sbinary.{DefaultProtocol,Format} + import DefaultProtocol.{StringFormat, withStamp} + +/** Reads the previous value of tasks on-demand. The read values are cached so that they are only read once per task execution. +* `referenced` provides the `Format` to use for each key. */ +private[sbt] final class Previous(streams: Streams, referenced: IMap[ScopedTaskKey, Referenced]) +{ + private[this] val map = referenced.mapValues(toValue) + private[this] def toValue = new (Referenced ~> ReferencedValue) { def apply[T](x: Referenced[T]) = new ReferencedValue(x) } + + private[this] final class ReferencedValue[T](referenced: Referenced[T]) + { + import referenced.{stamped, task} + lazy val previousValue: Option[T] = { + val in = streams(task).readBinary(task, StreamName) + try read(in, stamped) finally in.close() + } + } + + /** Used by the .previous runtime implemention to get the previous value for task `key`. */ + private def get[T](key: ScopedKey[Task[T]]): Option[T] = + map.get(key).flatMap(_.previousValue) +} +object Previous +{ + private[sbt] type ScopedTaskKey[T] = ScopedKey[Task[T]] + private type Streams = sbt.std.Streams[ScopedKey[_]] + + /** The stream where the task value is persisted. */ + private final val StreamName = "previous" + + /** Represents a reference task.previous*/ + private[sbt] final class Referenced[T](val task: ScopedKey[Task[T]], val format: Format[T]) { + lazy val stamped = withStamp(task.key.manifest.toString)(format) + def setTask(newTask: ScopedKey[Task[T]]) = new Referenced(newTask, format) + } + + private[sbt] val references = SettingKey[References]("previous-references", "Collects all static references to previous values of tasks.", KeyRanks.Invisible) + private[sbt] val cache = TaskKey[Previous]("previous-cache", "Caches previous values of tasks read from disk for the duration of a task execution.", KeyRanks.Invisible) + private[this] val previousReferenced = AttributeKey[Referenced[_]]("previous-referenced") + + /** Records references to previous task value. This should be completely populated after settings finish loading. */ + private[sbt] final class References + { + private[this] var map = IMap.empty[ScopedTaskKey, Referenced] + + // TODO: this arbitrarily chooses a Format. + // The need to choose is a fundamental problem with this approach, but this should at least make a stable choice. + def recordReference[T](key: ScopedKey[Task[T]], format: Format[T]): Unit = synchronized { + map = map.put(key, new Referenced(key, format)) + } + def getReferences: IMap[ScopedTaskKey, Referenced] = synchronized { map } + } + + /** Persists values of tasks t where there is some task referencing it via t.previous. */ + private[sbt] def complete(referenced: References, results: RMap[Task,Result], streams: Streams): Unit = + { + val map = referenced.getReferences + def impl[T](key: ScopedKey[_], result: T): Unit = + for(i <- map.get(key.asInstanceOf[ScopedTaskKey[T]])) { + val out = streams.apply(i.task).binary(StreamName) + try write(out, i.stamped, result ) finally out.close() + } + + for { + results.TPair(Task(info, _), Value(result)) <- results.toTypedSeq + key <- info.attributes get Def.taskDefinitionKey + } + impl(key, result) + } + + private def read[T](stream: InputStream, format: Format[T]): Option[T] = + try Some(format.reads(stream)) + catch { case e: Exception => None } + + private def write[T](stream: OutputStream, format: Format[T], value: T): Unit = + try format.writes(stream, value) + catch { case e: Exception => () } + + /** Public as a macro implementation detail. Do not call directly. */ + def runtime[T](skey: TaskKey[T])(implicit format: Format[T]): Initialize[Task[Option[T]]] = + { + val inputs = (cache in Global) zip Def.validated(skey, selfRefOk=true) zip (references in Global) + inputs { case ( (prevTask, resolved), refs ) => + refs.recordReference(resolved, format) // always evaluated on project load + import std.TaskExtra._ + prevTask.map(_ get resolved) // evaluated if this task is evaluated + } + } + + private[sbt] def cacheSetting = (streamsManagerKey, references) map { (s, refs) => new Previous(s, refs.getReferences) } +} diff --git a/main/settings/src/main/scala/sbt/std/InputConvert.scala b/main/settings/src/main/scala/sbt/std/InputConvert.scala index ee0a7a35b..2f25ee216 100644 --- a/main/settings/src/main/scala/sbt/std/InputConvert.scala +++ b/main/settings/src/main/scala/sbt/std/InputConvert.scala @@ -50,7 +50,7 @@ object FullConvert extends Convert { import InputWrapper._ def apply[T: c.WeakTypeTag](c: Context)(nme: String, in: c.Tree): Converted[c.type] = - if(nme == WrapInitTaskName) + if(nme == WrapInitTaskName || nme == WrapPreviousName) Converted.Success(in) else if(nme == WrapInitName) { @@ -83,4 +83,3 @@ object InitParserConvert extends Convert else Converted.NotApplicable } - diff --git a/main/settings/src/main/scala/sbt/std/InputWrapper.scala b/main/settings/src/main/scala/sbt/std/InputWrapper.scala index 7a2a121ff..400dbc8f8 100644 --- a/main/settings/src/main/scala/sbt/std/InputWrapper.scala +++ b/main/settings/src/main/scala/sbt/std/InputWrapper.scala @@ -6,7 +6,7 @@ package std import reflect.macros._ import reflect.internal.annotations.compileTimeOnly - import Def.Initialize + import Def.{Initialize, ScopedKey} import appmacro.ContextUtil import complete.Parser @@ -22,6 +22,7 @@ object InputWrapper private[std] final val WrapInitTaskName = "wrapInitTask_\u2603\u2603" private[std] final val WrapInitInputName = "wrapInitInputTask_\u2603\u2603" private[std] final val WrapInputName = "wrapInputTask_\u2603\u2603" + private[std] final val WrapPreviousName = "wrapPrevious_\u2603\u2603" @compileTimeOnly("`value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task.") def wrapTask_\u2603\u2603[T](in: Any): T = implDetailError @@ -38,6 +39,9 @@ object InputWrapper @compileTimeOnly("`value` can only be called on an input task within a task definition macro, such as := or Def.inputTask.") def wrapInitInputTask_\u2603\u2603[T](in: Any): T = implDetailError + @compileTimeOnly("`previous` can only be called on a task within a task or input task definition macro, such as :=, +=, ++=, Def.task, or Def.inputTask.") + def wrapPrevious_\u2603\u2603[T](in: Any): T = implDetailError + private[this] def implDetailError = error("This method is an implementation detail and should not be referenced.") private[std] def wrapTask[T: c.WeakTypeTag](c: Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] = @@ -52,6 +56,9 @@ object InputWrapper private[std] def wrapInputTask[T: c.WeakTypeTag](c: Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[T] = wrapImpl[T,InputWrapper.type](c, InputWrapper, WrapInputName)(ts, pos) + private[std] def wrapPrevious[T: c.WeakTypeTag](c: Context)(ts: c.Expr[Any], pos: c.Position): c.Expr[Option[T]] = + wrapImpl[Option[T],InputWrapper.type](c, InputWrapper, WrapPreviousName)(ts, pos) + /** Wraps an arbitrary Tree in a call to the `.` method of this module for later processing by an enclosing macro. * The resulting Tree is the manually constructed version of: * @@ -96,6 +103,22 @@ object InputWrapper else c.abort(pos, s"Internal sbt error. Unexpected type ${tpe.widen}") } + /** Translates .previous(format) to Previous.runtime()(format).value*/ + def previousMacroImpl[T: c.WeakTypeTag](c: Context)(format: c.Expr[sbinary.Format[T]]): c.Expr[Option[T]] = + { + import c.universe._ + c.macroApplication match { + case a @ Apply(Select(Apply(_, t :: Nil), tp), fmt) => + if(t.tpe <:< c.weakTypeOf[TaskKey[T]]) { + val tsTyped = c.Expr[TaskKey[T]](t) + val newTree = c.universe.reify { Previous.runtime[T](tsTyped.splice)(format.splice) } + wrapPrevious[T](c)(newTree, a.pos) + } + else + c.abort(a.pos, s"Internal sbt error. Unexpected type ${t.tpe.widen}") + case x => ContextUtil.unexpectedTree(x) + } + } } sealed abstract class MacroTaskValue[T] { @@ -118,6 +141,10 @@ sealed abstract class ParserInputTask[T] { @compileTimeOnly("`parsed` can only be used within an input task macro, such as := or Def.inputTask.") def parsed: Task[T] = macro ParserInput.parsedInputMacroImpl[T] } +sealed abstract class MacroPrevious[T] { + @compileTimeOnly("`previous` can only be used within a task macro, such as :=, +=, ++=, or Def.task.") + def previous(implicit format: sbinary.Format[T]): Option[T] = macro InputWrapper.previousMacroImpl[T] +} /** Implementation detail. The wrap method temporarily holds the input parser (as a Tree, at compile time) until the input task macro processes it. */ object ParserInput { diff --git a/main/settings/src/main/scala/sbt/std/SettingMacro.scala b/main/settings/src/main/scala/sbt/std/SettingMacro.scala index e87cd8b65..813cea7a7 100644 --- a/main/settings/src/main/scala/sbt/std/SettingMacro.scala +++ b/main/settings/src/main/scala/sbt/std/SettingMacro.scala @@ -29,6 +29,8 @@ object InitializeConvert extends Convert } else if(nme == InputWrapper.WrapTaskName || nme == InputWrapper.WrapInitTaskName) Converted.Failure(in.pos, "A setting cannot depend on a task") + else if(nme == InputWrapper.WrapPreviousName) + Converted.Failure(in.pos, "A setting cannot depend on a task's previous value.") else Converted.NotApplicable } @@ -38,6 +40,6 @@ 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]] = + 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..8283b5b54 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -63,6 +63,8 @@ object Defaults extends BuildCommon buildDependencies <<= Classpaths.constructBuildDependencies, taskTemporaryDirectory := { val dir = IO.createTemporaryDirectory; dir.deleteOnExit(); dir }, onComplete := { val dir = taskTemporaryDirectory.value; () => {IO.delete(dir); IO.createDirectory(dir) }}, + Previous.cache <<= Previous.cacheSetting, + Previous.references :== new Previous.References, concurrentRestrictions <<= defaultRestrictions, parallelExecution :== true, sbtVersion := appConfiguration.value.provider.id.version, diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index af4872dd1..66192feb2 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -34,14 +34,14 @@ object EvaluateTask import std.{TaskExtra,Transform} import TaskExtra._ import Keys.state - - private[sbt] def defaultProgress: ExecuteProgress[Task] = + + private[sbt] def defaultProgress: ExecuteProgress[Task] = if(java.lang.Boolean.getBoolean("sbt.task.timings")) new TaskTimings else ExecuteProgress.empty[Task] val SystemProcessors = Runtime.getRuntime.availableProcessors @deprecated("Use extractedConfig.", "0.13.0") - def defaultConfig(state: State): EvaluateConfig = + def defaultConfig(state: State): EvaluateConfig = { val extracted = Project.extract(state) defaultConfig(extracted, extracted.structure) @@ -155,7 +155,7 @@ object EvaluateTask val str = std.Streams.closeable(structure.streams(state)) try { f(str) } finally { str.close() } } - + def getTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, streams: Streams, ref: ProjectRef): Option[(Task[T], NodeView[Task])] = { val thisScope = Load.projectScope(ref) @@ -169,7 +169,7 @@ object EvaluateTask def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], config: EvaluateConfig)(implicit taskToNode: NodeView[Task]): (State, Result[T]) = { import ConcurrentRestrictions.{completionService, TagMap, Tag, tagged, tagsKey} - + val log = state.log log.debug("Running task... Cancelable: " + config.cancelable + ", check cycles: " + config.checkCycles) val tags = tagged[Task[_]](_.info get tagsKey getOrElse Map.empty, Tags.predicate(config.restrictions)) @@ -183,8 +183,11 @@ object EvaluateTask def run() = { val x = new Execute[Task]( Execute.config(config.checkCycles, overwriteNode), triggers, config.progress)(taskToNode) val (newState, result) = - try applyResults(x.runKeep(root)(service), state, root) - catch { case inc: Incomplete => (state, Inc(inc)) } + try { + val results = x.runKeep(root)(service) + storeValuesForPrevious(results, state, streams) + applyResults(results, state, root) + } catch { case inc: Incomplete => (state, Inc(inc)) } finally shutdown() val replaced = transformInc(result) logIncResult(replaced, state, streams) @@ -201,6 +204,10 @@ object EvaluateTask run() } + private[this] def storeValuesForPrevious(results: RMap[Task, Result], state: State, streams: Streams): Unit = + for(referenced <- Previous.references in Global get Project.structure(state).data) + Previous.complete(referenced, results, streams) + def applyResults[T](results: RMap[Task, Result], state: State, root: Task[T]): (State, Result[T]) = (stateTransform(results)(state), results(root)) def stateTransform(results: RMap[Task, Result]): State => State = @@ -210,7 +217,7 @@ object EvaluateTask case _ => Nil } ) - + def transformInc[T](result: Result[T]): Result[T] = // taskToKey needs to be before liftAnonymous. liftA only lifts non-keyed (anonymous) Incompletes. result.toEither.left.map { i => Incomplete.transformBU(i)(convertCyclicInc andThen taskToKey andThen liftAnonymous ) } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 99902d8fa..c0ffdbd6d 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -55,8 +55,6 @@ object Keys val transformState = AttributeKey[State => State]("transform-state", "State transformation to apply after tasks run.", DSetting) val onComplete = SettingKey[() => Unit]("on-complete", "Hook to run when task evaluation completes. The type of this setting is subject to change, pending the resolution of SI-2915.", DSetting) -// https://issues.scala-lang.org/browse/SI-2915 -// val onComplete = SettingKey[RMap[Task,Result] => RMap[Task,Result]]("on-complete", "Transformation to apply to the final task result map. This may also be used to register hooks to run when task evaluation completes.", DSetting) // Command keys val historyPath = SettingKey(BasicKeys.historyPath) @@ -327,17 +325,19 @@ object Keys val cancelable = SettingKey[Boolean]("cancelable", "Enables (true) or disables (false) the ability to interrupt task execution with CTRL+C.", BMinusSetting) val settingsData = std.FullInstance.settingsData val streams = TaskKey[TaskStreams]("streams", "Provides streams for logging and persisting data.", DTask) - val taskDefinitionKey = AttributeKey[ScopedKey[_]]("task-definition-key", "Internal: used to map a task back to its ScopedKey.", Invisible) + val taskDefinitionKey = Def.taskDefinitionKey val (executionRoots, dummyRoots)= Def.dummy[Seq[ScopedKey[_]]]("execution-roots", "The list of root tasks for this task execution. Roots are the top-level tasks that were directly requested to be run.") val state = Def.stateKey + val streamsManager = Def.streamsManagerKey @deprecated("Implementation detail.", "0.13.1") val isDummyTask = Def.isDummyTask @deprecated("Implementation detail.", "0.13.1") val dummyState = Def.dummyState + @deprecated("Implementation detail.", "0.13.2") + val dummyStreamsManager = Def.dummyStreamsManager - val (streamsManager, dummyStreamsManager) = Def.dummy[Streams]("streams-manager", "Streams manager, which provides streams for different contexts.") val stateStreams = AttributeKey[Streams]("streams-manager", "Streams manager, which provides streams for different contexts. Setting this on State will override the default Streams implementation.") val resolvedScoped = Def.resolvedScoped val pluginData = TaskKey[PluginData]("plugin-data", "Information from the plugin build needed in the main build definition.", DTask) diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index fe584a3bb..8b7f3465a 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -76,7 +76,7 @@ object Load config.copy(injectSettings = config.injectSettings.copy(projectLoaded = compiled)) } def buildGlobalSettings(base: File, files: Seq[File], config: sbt.LoadBuildConfiguration): ClassLoader => Seq[Setting[_]] = - { + { val eval = mkEval(data(config.classpath), base, defaultEvalOptions) val imports = BuildUtil.baseImports ++ BuildUtil.importAllRoot(config.globalPluginNames) loader => EvaluateConfigurations(eval, files, imports)(loader).settings @@ -200,7 +200,7 @@ object Load def buildConfigurations(loaded: sbt.LoadedBuild, rootProject: URI => String, injectSettings: InjectSettings): Seq[Setting[_]] = { ((loadedBuild in GlobalScope :== loaded) +: - transformProjectOnly(loaded.root, rootProject, injectSettings.global)) ++ + transformProjectOnly(loaded.root, rootProject, injectSettings.global)) ++ inScope(GlobalScope)( pluginGlobalSettings(loaded) ) ++ loaded.units.toSeq.flatMap { case (uri, build) => val plugins = build.unit.plugins.plugins @@ -234,7 +234,7 @@ object Load def transformSettings(thisScope: Scope, uri: URI, rootProject: URI => String, settings: Seq[Setting[_]]): Seq[Setting[_]] = Project.transform(Scope.resolveScope(thisScope, uri, rootProject), settings) def projectScope(project: Reference): Scope = Scope(Select(project), Global, Global, Global) - + def lazyEval(unit: sbt.BuildUnit): () => Eval = { lazy val eval = mkEval(unit) @@ -438,7 +438,7 @@ object Load new sbt.BuildUnit(uri, normBase, loadedDefs, plugs) } - private[this] def autoID(localBase: File, context: PluginManagement.Context, existingIDs: Seq[String]): String = + private[this] def autoID(localBase: File, context: PluginManagement.Context, existingIDs: Seq[String]): String = { def normalizeID(f: File) = Project.normalizeProjectID(f.getName) match { case Right(id) => id @@ -453,11 +453,11 @@ object Load if(existingIDs.contains(tryID)) Build.defaultID(localBase) else tryID } - private[this] def autoIDError(base: File, reason: String): String = + private[this] def autoIDError(base: File, reason: String): String = "Could not derive root project ID from directory " + base.getAbsolutePath + ":\n" + reason + "\nRename the directory or explicitly define a root project." - private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] = + private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] = b.projectDefinitions(base).map(resolveBase(base)) private[this] def loadTransitive(newProjects: Seq[Project], buildBase: File, imports: Seq[String], plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile]): Seq[Project] = @@ -483,7 +483,7 @@ object Load else loadTransitive(nextProjects, buildBase, imports, plugins, eval, injectSettings, loadedProjects, memoSettings) } - + private[this] def loadSettings(auto: AddSettings, projectBase: File, buildImports: Seq[String], loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile]): LoadedSbtFile = { lazy val defaultSbtFiles = configurationSources(projectBase) @@ -570,7 +570,7 @@ object Load def pluginDefinitionLoader(config: sbt.LoadBuildConfiguration, dependencyClasspath: Seq[Attributed[File]]): (Seq[Attributed[File]], ClassLoader) = pluginDefinitionLoader(config, dependencyClasspath, Nil) def pluginDefinitionLoader(config: sbt.LoadBuildConfiguration, pluginData: PluginData): (Seq[Attributed[File]], ClassLoader) = - pluginDefinitionLoader(config, pluginData.dependencyClasspath, pluginData.definitionClasspath) + pluginDefinitionLoader(config, pluginData.dependencyClasspath, pluginData.definitionClasspath) def pluginDefinitionLoader(config: sbt.LoadBuildConfiguration, depcp: Seq[Attributed[File]], defcp: Seq[Attributed[File]]): (Seq[Attributed[File]], ClassLoader) = { val definitionClasspath = @@ -670,7 +670,7 @@ object Load def initialSession(structure: sbt.BuildStructure, rootEval: () => Eval): SessionSettings = new SessionSettings(structure.root, projectMap(structure, Map.empty), structure.settings, Map.empty, Nil, rootEval) - + def projectMap(structure: sbt.BuildStructure, current: Map[URI, String]): Map[URI, String] = { val units = structure.units @@ -699,7 +699,7 @@ object Load def getImports(unit: sbt.BuildUnit): Seq[String] = BuildUtil.getImports(unit) def referenced[PR <: ProjectReference](definitions: Seq[ProjectDefinition[PR]]): Seq[PR] = definitions flatMap { _.referenced } - + @deprecated("LoadedBuildUnit is now top-level", "0.13.0") type LoadedBuildUnit = sbt.LoadedBuildUnit @@ -711,7 +711,7 @@ object Load @deprecated("LoadBuildConfiguration is now top-level", "0.13.0") type LoadBuildConfiguration = sbt.LoadBuildConfiguration - @deprecated("LoadBuildConfiguration is now top-level", "0.13.0") + @deprecated("LoadBuildConfiguration is now top-level", "0.13.0") val LoadBuildConfiguration = sbt.LoadBuildConfiguration final class EvaluatedConfigurations(val eval: Eval, val settings: Seq[Setting[_]]) diff --git a/sbt/src/sbt-test/actions/previous/build.sbt b/sbt/src/sbt-test/actions/previous/build.sbt new file mode 100644 index 000000000..ef4b0d353 --- /dev/null +++ b/sbt/src/sbt-test/actions/previous/build.sbt @@ -0,0 +1,32 @@ +import sbinary.DefaultProtocol._ + +lazy val a0 = 1 +lazy val b0 = 1 +lazy val a = taskKey[Int]("a") +lazy val b = taskKey[Int]("b") +lazy val next = taskKey[(Int,Int)]("next") +lazy val checkNext = inputKey[Unit]("check-next") + + +// These test that there is no cycle when referring to previous values (a -> b.previous, b -> a.previous) +// Also, it is ok for b to refer to b.previous: +// normally, b's definition could not refer to plain b.value because it would be undefined + +a := b.previous.getOrElse(a0) + +b := a.previous.getOrElse(a0) + b.previous.getOrElse(b0) + +next := (a.value, b.value) + +def parser = { + import complete.DefaultParsers._ + (Space ~> IntBasic) ~ (Space ~> IntBasic) +} + +checkNext := { + val (expectedA, expectedB) = parser.parsed + val actualA = a.value + val actualB = b.value + assert(actualA == expectedA, s"Expected 'a' to be $expectedA, got $actualA") + assert(actualB == expectedB, s"Expected 'b' to be $expectedB, got $actualB") +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/previous/scopes.sbt b/sbt/src/sbt-test/actions/previous/scopes.sbt new file mode 100644 index 000000000..d0ebe7ecf --- /dev/null +++ b/sbt/src/sbt-test/actions/previous/scopes.sbt @@ -0,0 +1,47 @@ +import sbinary.DefaultProtocol._ + +lazy val x = taskKey[Int]("x") +lazy val y = taskKey[Int]("y") +lazy val checkScopes = inputKey[Unit]("check scopes") + +lazy val subA = project +lazy val subB = project + +x := 3 + +x in Compile in y := 7 + +x in Runtime in y := 13 + +x in subA in Compile := { + val xcy = (x in Compile in y).previous getOrElse 0 // 7 + // verify that This is properly resolved to Global and not the defining key's scope + val xg = x.previous getOrElse 0 // 3 + println(s"xcy=$xcy, xg=$xg") + xcy * xg +} + + +inConfig(Compile)(Seq( + y in subB := { + // verify that the referenced key gets delegated + val xty = (x in Test in y).previous getOrElse 0 // 13 + // verify that inConfig gets applied + val xcy = (x in y).previous getOrElse 0 // 7 + println(s"xcy=$xcy, xty=$xty") + xty * xcy + } +)) + +def parser = { + import complete.DefaultParsers._ + (Space ~> IntBasic) ~ (Space ~> IntBasic) +} + +checkScopes := { + val (expectedX, expectedY) = parser.parsed + val actualX = (x in subA in Compile).value + val actualY = (y in subB in Test).value + assert(actualX == expectedX, s"Expected 'x' to be $expectedX, got $actualX") + assert(actualY == expectedY, s"Expected 'y' to be $expectedY, got $actualY") +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/previous/test b/sbt/src/sbt-test/actions/previous/test new file mode 100644 index 000000000..5975f711e --- /dev/null +++ b/sbt/src/sbt-test/actions/previous/test @@ -0,0 +1,12 @@ +> debug + +> checkNext 1 2 +> checkNext 2 3 +> checkNext 3 5 +> checkNext 5 8 +> clean +> checkNext 1 2 + +> checkScopes 0 0 +> all x compile:y::x runtime:y::x y +> checkScopes 21 91 \ No newline at end of file diff --git a/util/collection/src/main/scala/sbt/INode.scala b/util/collection/src/main/scala/sbt/INode.scala index 4ce5ef8bb..67b1c5b36 100644 --- a/util/collection/src/main/scala/sbt/INode.scala +++ b/util/collection/src/main/scala/sbt/INode.scala @@ -27,9 +27,10 @@ abstract class EvaluateSettings[Scope] case k: Keyed[s, T] => single(getStatic(k.scopedKey), k.transform) case a: Apply[k,T] => new MixedNode[k,T]( a.alist.transform[Initialize, INode](a.inputs, transform), a.f, a.alist) case b: Bind[s,T] => new BindNode[s,T]( transform(b.in), x => transform(b.f(x))) - case init.StaticScopes => constant(() => allScopes.asInstanceOf[T]) // can't convince scalac that StaticScopes => T == Set[Scope] + case init.StaticScopes => strictConstant(allScopes.asInstanceOf[T]) // can't convince scalac that StaticScopes => T == Set[Scope] case v: Value[T] => constant(v.value) - case t: TransformCapture => constant(() => t.f) + case v: ValidationCapture[T] => strictConstant(v.key) + case t: TransformCapture => strictConstant(t.f) case o: Optional[s,T] => o.a match { case None => constant( () => o.f(None) ) case Some(i) => single[s,T](transform(i), x => o.f(Some(x))) @@ -80,7 +81,7 @@ abstract class EvaluateSettings[Scope] private[this] def workComplete(): Unit = if(running.decrementAndGet() == 0) complete.put( None ) - + private[this] sealed abstract class INode[T] { private[this] var state: EvaluationState = New @@ -92,9 +93,9 @@ abstract class EvaluateSettings[Scope] override def toString = getClass.getName + " (state=" + state + ",blockedOn=" + blockedOn + ",calledBy=" + calledBy.size + ",blocking=" + blocking.size + "): " + keyString - private[this] def keyString = + private[this] def keyString = (static.toSeq.flatMap { case (key, value) => if(value eq this) init.showFullKey(key) :: Nil else Nil }).headOption getOrElse "non-static" - + final def get: T = synchronized { assert(value != null, toString + " not evaluated") value @@ -103,7 +104,7 @@ abstract class EvaluateSettings[Scope] val ready = state == Evaluated if(!ready) blocking += from registerIfNew() - ready + ready } final def isDone: Boolean = synchronized { state == Evaluated } final def isNew: Boolean = synchronized { state == New } @@ -119,7 +120,7 @@ abstract class EvaluateSettings[Scope] else state = Blocked } - + final def schedule(): Unit = synchronized { assert(state == New || state == Blocked, "Invalid state for schedule() call: " + toString) state = Ready @@ -158,6 +159,7 @@ abstract class EvaluateSettings[Scope] protected def evaluate0(): Unit } + private[this] def strictConstant[T](v: T): INode[T] = constant(() => v) private[this] def constant[T](f: () => T): INode[T] = new MixedNode[ConstK[Unit]#l, T]((), _ => f(), AList.empty) private[this] def single[S,T](in: INode[S], f: S => T): INode[T] = new MixedNode[ ({ type l[L[x]] = L[S] })#l, T](in, f, AList.single[S]) private[this] final class BindNode[S,T](in: INode[S], f: S => INode[T]) extends INode[T] diff --git a/util/collection/src/main/scala/sbt/Settings.scala b/util/collection/src/main/scala/sbt/Settings.scala index 3780adad2..32d4b9f85 100644 --- a/util/collection/src/main/scala/sbt/Settings.scala +++ b/util/collection/src/main/scala/sbt/Settings.scala @@ -59,9 +59,14 @@ trait Init[Scope] type ScopeLocal = ScopedKey[_] => Seq[Setting[_]] type MapConstant = ScopedKey ~> Option + private[sbt] abstract class ValidateKeyRef { + def apply[T](key: ScopedKey[T], selfRefOk: Boolean): ValidatedRef[T] + } + /** The result of this initialization is the composition of applied transformations. * 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 value[T](value: => T): Initialize[T] = pure(value _) @@ -74,10 +79,15 @@ trait Init[Scope] 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]) + /** The result of this initialization is the validated `key`. + * No dependency is introduced on `key`. If `selfRefOk` is true, validation will not fail if the key is referenced by a definition of `key`. + * That is, key := f(validated(key).value) is allowed only if `selfRefOk == true`. */ + private[sbt] final def validated[T](key: ScopedKey[T], selfRefOk: Boolean): ValidationCapture[T] = new ValidationCapture(key, selfRefOk) + /** 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. - * Only the static dependencies are tracked, however. */ + * Only the static dependencies are tracked, however. Dependencies on previous values do not introduce a derived setting either. */ final def derive[T](s: Setting[T], allowDynamic: Boolean = false, filter: Scope => Boolean = const(true), trigger: AttributeKey[_] => Boolean = const(true)): Setting[T] = { deriveAllowed(s, allowDynamic) foreach error new DerivedSetting[T](s.key, s.init, s.pos, filter, trigger, nextDefaultID()) @@ -158,12 +168,12 @@ trait Init[Scope] def delegate(sMap: ScopedMap)(implicit delegates: Scope => Seq[Scope], display: Show[ScopedKey[_]]): ScopedMap = { - def refMap(ref: Setting[_], isFirst: Boolean) = new ValidateRef { def apply[T](k: ScopedKey[T]) = - delegateForKey(sMap, k, delegates(k.scope), ref, isFirst) + def refMap(ref: Setting[_], isFirst: Boolean) = new ValidateKeyRef { def apply[T](k: ScopedKey[T], selfRefOk: Boolean) = + delegateForKey(sMap, k, delegates(k.scope), ref, selfRefOk || !isFirst) } type ValidatedSettings[T] = Either[Seq[Undefined], SettingSeq[T]] val f = new (SettingSeq ~> ValidatedSettings) { def apply[T](ks: Seq[Setting[T]]) = { - val (undefs, valid) = Util.separate(ks.zipWithIndex){ case (s,i) => s validateReferenced refMap(s, i == 0) } + val (undefs, valid) = Util.separate(ks.zipWithIndex){ case (s,i) => s validateKeyReferenced refMap(s, i == 0) } if(undefs.isEmpty) Right(valid) else Left(undefs.flatten) }} type Undefs[_] = Seq[Undefined] @@ -173,10 +183,10 @@ trait Init[Scope] else throw Uninitialized(sMap.keys.toSeq, delegates, undefineds.values.flatten.toList, false) } - private[this] def delegateForKey[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], ref: Setting[_], isFirst: Boolean): Either[Undefined, ScopedKey[T]] = + private[this] def delegateForKey[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], ref: Setting[_], selfRefOk: Boolean): Either[Undefined, ScopedKey[T]] = { val skeys = scopes.iterator.map(x => ScopedKey(x, k.key)) - val definedAt = skeys.find( sk => (!isFirst || ref.key != sk) && (sMap contains sk)) + val definedAt = skeys.find( sk => (selfRefOk || ref.key != sk) && (sMap contains sk)) definedAt.toRight(Undefined(ref, k)) } @@ -377,14 +387,25 @@ trait Init[Scope] { def dependencies: Seq[ScopedKey[_]] def apply[S](g: T => S): Initialize[S] + + @deprecated("Will be made private.", "0.13.2") def mapReferenced(g: MapScoped): Initialize[T] - def validateReferenced(g: ValidateRef): ValidatedInit[T] + @deprecated("Will be made private.", "0.13.2") def mapConstant(g: MapConstant): Initialize[T] + + @deprecated("Will be made private.", "0.13.2") + def validateReferenced(g: ValidateRef): ValidatedInit[T] = + validateKeyReferenced( new ValidateKeyRef { def apply[T](key: ScopedKey[T], selfRefOk: Boolean) = g(key) }) + + private[sbt] def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[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[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]) + /** A fold on the static attributes of this and nested Initializes. */ + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S } object Initialize { @@ -411,10 +432,17 @@ trait Init[Scope] def settings = this :: Nil def definitive: Boolean = !init.dependencies.contains(key) def dependencies: Seq[ScopedKey[_]] = remove(init.dependencies, key) + @deprecated("Will be made private.", "0.13.2") def mapReferenced(g: MapScoped): Setting[T] = make(key, init mapReferenced g, pos) + @deprecated("Will be made private.", "0.13.2") def validateReferenced(g: ValidateRef): Either[Seq[Undefined], Setting[T]] = (init validateReferenced g).right.map(newI => make(key, newI, pos)) + + private[sbt] def validateKeyReferenced(g: ValidateKeyRef): Either[Seq[Undefined], Setting[T]] = + (init validateKeyReferenced 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) + @deprecated("Will be made private.", "0.13.2") 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 { @@ -450,8 +478,8 @@ trait Init[Scope] new (ValidatedInit ~> Initialize) { def apply[T](v: ValidatedInit[T]) = handleUndefined[T](v) } // mainly for reducing generated class count - private[this] def validateReferencedT(g: ValidateRef) = - new (Initialize ~> ValidatedInit) { def apply[T](i: Initialize[T]) = i validateReferenced g } + private[this] def validateKeyReferencedT(g: ValidateKeyRef) = + new (Initialize ~> ValidatedInit) { def apply[T](i: Initialize[T]) = i validateKeyReferenced g } private[this] def mapReferencedT(g: MapScoped) = new (Initialize ~> Initialize) { def apply[T](i: Initialize[T]) = i mapReferenced g } @@ -472,7 +500,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 validateReferenced(g: ValidateRef): ValidatedInit[T] = g(scopedKey) match { + private[sbt] final def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[T] = g(scopedKey, false) match { case Left(un) => Left(un :: Nil) case Right(nk) => Right(new GetValue(nk, transform)) } @@ -480,11 +508,13 @@ trait Init[Scope] case None => this case Some(const) => new Value(() => transform(const)) } + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init } private[this] final class GetValue[S,T](val scopedKey: ScopedKey[S], val transform: S => T) extends Keyed[S, T] trait KeyedInitialize[T] extends Keyed[T, T] { final val transform = idFun[T] } + private[sbt] final class TransformCapture(val f: Initialize ~> Initialize) extends Initialize[Initialize ~> Initialize] { def dependencies = Nil @@ -492,7 +522,21 @@ trait Init[Scope] def evaluate(ss: Settings[Scope]): Initialize ~> Initialize = f 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)) + def validateKeyReferenced(g: ValidateKeyRef) = Right(new TransformCapture(getValidated ∙ validateKeyReferencedT(g) ∙ f)) + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init + } + private[sbt] final class ValidationCapture[T](val key: ScopedKey[T], val selfRefOk: Boolean) extends Initialize[ScopedKey[T]] { + def dependencies = Nil + def apply[Z](g2: ScopedKey[T] => Z): Initialize[Z] = map(this)(g2) + def evaluate(ss: Settings[Scope]) = key + def mapReferenced(g: MapScoped) = new ValidationCapture(g(key), selfRefOk) + def mapConstant(g: MapConstant) = this + def validateKeyReferenced(g: ValidateKeyRef) = g(key, selfRefOk) match { + case Left(un) => Left(un :: Nil) + case Right(k) => Right(new ValidationCapture(k, selfRefOk)) + } + + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init } private[sbt] final class Bind[S,T](val f: S => Initialize[T], val in: Initialize[S]) extends Initialize[T] { @@ -500,42 +544,49 @@ trait Init[Scope] 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 validateKeyReferenced(g: ValidateKeyRef) = (in validateKeyReferenced g).right.map { validIn => + new Bind[S,T](s => handleUndefined( f(s) validateKeyReferenced g), validIn) } def mapConstant(g: MapConstant) = new Bind[S,T](s => f(s) mapConstant g, in mapConstant g) + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = in.processAttributes(init)(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 validateReferenced(g: ValidateRef) = a match { + def validateKeyReferenced(g: ValidateKeyRef) = a match { case None => Right(this) - case Some(i) => Right( new Optional(i.validateReferenced(g).right.toOption, f) ) + case Some(i) => Right( new Optional(i.validateKeyReferenced(g).right.toOption, f) ) } def mapConstant(g: MapConstant): Initialize[T] = new Optional(a map mapConstantT(g).fn, f) def evaluate(ss: Settings[Scope]): T = f( a.flatMap( i => trapBadRef(evaluateT(ss)(i)) ) ) // 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] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = a match { + case None => init + case Some(i) => i.processAttributes(init)(f) + } } private[sbt] final class Value[T](val value: () => T) extends Initialize[T] { def dependencies = Nil def mapReferenced(g: MapScoped) = this - def validateReferenced(g: ValidateRef) = Right(this) + def validateKeyReferenced(g: ValidateKeyRef) = 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] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init } private[sbt] final object StaticScopes extends Initialize[Set[Scope]] { def dependencies = Nil def mapReferenced(g: MapScoped) = this - def validateReferenced(g: ValidateRef) = Right(this) + def validateKeyReferenced(g: ValidateKeyRef) = Right(this) def apply[S](g: Set[Scope] => S) = map(this)(g) def mapConstant(g: MapConstant) = this def evaluate(map: Settings[Scope]) = map.scopes + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = init } private[sbt] final class Apply[K[L[x]], T](val f: K[Id] => T, val inputs: K[Initialize], val alist: AList[K]) extends Initialize[T] { @@ -545,13 +596,16 @@ trait Init[Scope] def mapConstant(g: MapConstant) = mapInputs( mapConstantT(g) ) def mapInputs(g: Initialize ~> Initialize): Initialize[T] = new Apply(f, alist.transform(inputs, g), alist) def evaluate(ss: Settings[Scope]) = f(alist.transform(inputs, evaluateT(ss))) - def validateReferenced(g: ValidateRef) = + def validateKeyReferenced(g: ValidateKeyRef) = { - val tx = alist.transform(inputs, validateReferencedT(g)) + val tx = alist.transform(inputs, validateKeyReferencedT(g)) val undefs = alist.toList(tx).flatMap(_.left.toSeq.flatten) val get = new (ValidatedInit ~> Initialize) { def apply[T](vr: ValidatedInit[T]) = vr.right.get } if(undefs.isEmpty) Right(new Apply(f, alist.transform(tx, get), alist)) else Left(undefs) } + + private[sbt] def processAttributes[S](init: S)(f: (S, AttributeMap) => S): S = + (init /: alist.toList(inputs)) { (v, i) => i.processAttributes(v)(f) } } private def remove[T](s: Seq[T], v: T) = s filterNot (_ == v) }