TaskKey[T].previous: Option[T], which returns the value of the task the last time it executed.

This requires a Format[T] to be implicitly available at the call site and requires the task
to be referenced statically (not in a settingDyn call).  References to previous task values
in the form of a ScopedKey[Task[T]] + Format[T] are collected at setting load time in the
'references' setting.  These are used to know which tasks should be persisted (the ScopedKey)
and how to persist them (the Format).

When checking/delegating previous references, rules are slightly different.

A normal reference from a task t in scope s cannot refer to t in s unless
there is an earlier definition of t in s.  However, a previous reference
does not have this restriction.  This commit modifies validateReferenced
to allow this.

TODO: user documentation
TODO: stable selection of the Format when there are multiple .previous calls on the same task
TODO: make it usable in InputTasks, specifically Parsers
This commit is contained in:
Mark Harrah 2013-12-06 20:43:48 -05:00
parent 5d49fcbe78
commit c669606999
14 changed files with 340 additions and 54 deletions

View File

@ -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

View File

@ -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) }
}

View File

@ -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
}

View File

@ -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 `<s>.<wrapName>` 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 <task: TaskKey[T]>.previous(format) to Previous.runtime(<task>)(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 {

View File

@ -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])
}

View File

@ -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,

View File

@ -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 ) }

View File

@ -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)

View File

@ -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[_]])

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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

View File

@ -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]

View File

@ -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)
}