diff --git a/main/Aggregation.scala b/main/Aggregation.scala index 18fca7e7f..a680a9ee8 100644 --- a/main/Aggregation.scala +++ b/main/Aggregation.scala @@ -72,9 +72,8 @@ final object Aggregation def applyTasks[T](s: State, structure: BuildStructure, ps: Values[Parser[Task[T]]], show: Boolean)(implicit display: Show[ScopedKey[_]]): Parser[() => State] = Command.applyEffect(seqParser(ps)) { ts => runTasks(s, structure, ts, Dummies(KNil, HNil), show) - s } - def runTasks[HL <: HList, T](s: State, structure: Load.BuildStructure, ts: Values[Task[T]], extra: Dummies[HL], show: Boolean)(implicit display: Show[ScopedKey[_]]) + def runTasks[HL <: HList, T](s: State, structure: Load.BuildStructure, ts: Values[Task[T]], extra: Dummies[HL], show: Boolean)(implicit display: Show[ScopedKey[_]]): State = { import EvaluateTask._ import std.TaskExtra._ @@ -82,13 +81,15 @@ final object Aggregation val toRun = ts map { case KeyValue(k,t) => t.map(v => KeyValue(k,v)) } join; val workers = maxWorkers(extracted, structure) val start = System.currentTimeMillis - val result = withStreams(structure){ str => runTask(toRun, str, structure.index.triggers, maxWorkers = workers)(nodeView(s, str, extra.tasks, extra.values)) } + val (newS, result) = withStreams(structure){ str => runTask(toRun, s,str, structure.index.triggers, maxWorkers = workers)(nodeView(s, str, extra.tasks, extra.values)) } val stop = System.currentTimeMillis - val log = logger(s) + val log = logger(newS) val success = result match { case Value(_) => true; case Inc(_) => false } try { onResult(result, log) { results => if(show) printSettings(results, log) } } finally { printSuccess(start, stop, extracted, success, log) } + + newS } def maxWorkers(extracted: Extracted, structure: Load.BuildStructure): Int = (Keys.parallelExecution in extracted.currentRef get structure.data) match { @@ -138,7 +139,6 @@ final object Aggregation val dummies = Dummies( InputTask.inputMap :^: KNil, inputMap :+: HNil) val roots = inputs.map { case KeyValue(k,t) => KeyValue(k,t.task) } runTasks(s, structure, roots, dummies, show) - s } } def valueParser(s: State, structure: BuildStructure, show: Boolean)(key: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): Parser[() => State] = diff --git a/main/Defaults.scala b/main/Defaults.scala index 6371d5e96..9d550c4cd 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -1125,4 +1125,18 @@ trait BuildCommon val newConfigs = cs filter { c => !existingName(c.name) } overridden ++ newConfigs } + + // these are intended for use in input tasks for creating parsers + def getFromContext[T](task: ScopedTask[T], context: ScopedKey[_], s: State): Option[T] = + SessionVar.get(SessionVar.resolveContext(task.scopedKey, context.scope, s), s) + + def loadFromContext[T](task: ScopedTask[T], context: ScopedKey[_], s: State)(implicit f: sbinary.Format[T]): Option[T] = + SessionVar.load(SessionVar.resolveContext(task.scopedKey, context.scope, s), s) + + // these are for use in tasks + def loadPrevious[T](task: ScopedTask[T])(implicit f: sbinary.Format[T]): Initialize[Task[Option[T]]] = + (state, resolvedScoped) map { (s, ctx) => loadFromContext(task, ctx, s)(f) } + + def getPrevious[T](task: ScopedTask[T]): Initialize[Task[Option[T]]] = + (state, resolvedScoped) map { (s, ctx) => getFromContext(task, ctx, s) } } \ No newline at end of file diff --git a/main/EvaluateTask.scala b/main/EvaluateTask.scala index c4f368444..b909598cb 100644 --- a/main/EvaluateTask.scala +++ b/main/EvaluateTask.scala @@ -6,7 +6,7 @@ package sbt import java.io.File import Project.{ScopedKey, Setting} import Keys.{globalLogging, streams, Streams, TaskStreams} - import Keys.{dummyState, dummyStreamsManager, streamsManager, taskDefinitionKey} + import Keys.{dummyState, dummyStreamsManager, streamsManager, taskDefinitionKey, transformState} import Scope.{GlobalScope, ThisScope} import scala.Console.{RED, RESET} @@ -29,14 +29,19 @@ object EvaluateTask { val root = ProjectRef(pluginDef.root, Load.getRootProject(pluginDef.units)(pluginDef.root)) val pluginKey = Keys.fullClasspath in Configurations.Runtime - val evaluated = evaluateTask(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root) - val result = evaluated getOrElse error("Plugin classpath does not exist for plugin definition at " + pluginDef.root) + val evaluated = apply(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root) + val (newS, result) = evaluated getOrElse error("Plugin classpath does not exist for plugin definition at " + pluginDef.root) + Project.runUnloadHooks(newS) // discard state processResult(result, log) } + + @deprecated("This method does not apply state changes requested during task execution. Use 'apply' instead, which does.", "0.11.1") def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] = + apply(structure, taskKey, state, ref, checkCycles, maxWorkers).map(_._2) + def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[(State, Result[T])] = withStreams(structure) { str => for( (task, toNode) <- getTask(structure, taskKey, state, str, ref) ) yield - runTask(task, str, structure.index.triggers, checkCycles, maxWorkers)(toNode) + runTask(task, state, str, structure.index.triggers, checkCycles, maxWorkers)(toNode) } def logIncResult(result: Result[_], streams: Streams) = result match { case Inc(i) => logIncomplete(i, streams); case _ => () } def logIncomplete(result: Incomplete, streams: Streams) @@ -73,16 +78,30 @@ object EvaluateTask def nodeView[HL <: HList](state: State, streams: Streams, extraDummies: KList[Task, HL] = KNil, extraValues: HL = HNil): Execute.NodeView[Task] = Transform(dummyStreamsManager :^: KCons(dummyState, extraDummies), streams :+: HCons(state, extraValues)) - def runTask[Task[_] <: AnyRef, T](root: Task[T], streams: Streams, triggers: Triggers[Task], checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): Result[T] = + def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): (State, Result[T]) = { val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers) val x = new Execute[Task](checkCycles, triggers)(taskToNode) - val result = try { x.run(root)(service) } finally { shutdown() } + val (newState, result) = + try applyResults(x.runKeep(root)(service), state, root) + catch { case inc: Incomplete => (state, Inc(inc)) } + finally shutdown() val replaced = transformInc(result) logIncResult(replaced, streams) - replaced + (newState, replaced) } + + 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 = + Function.chain( + results.toTypedSeq flatMap { + case results.TPair(Task(info, _), Value(v)) => info.post(v) get transformState + 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/GlobalPlugin.scala b/main/GlobalPlugin.scala index aed867043..a241f4c61 100644 --- a/main/GlobalPlugin.scala +++ b/main/GlobalPlugin.scala @@ -32,10 +32,11 @@ object GlobalPlugin def load(base: File, s: State, config: LoadBuildConfiguration): GlobalPlugin = { val (structure, state) = build(base, s, config) - val data = extract(state, structure) + val (newS, data) = extract(state, structure) + Project.runUnloadHooks(newS) // discard state GlobalPlugin(data, structure, inject(data), base) } - def extract(state: State, structure: BuildStructure): GlobalPluginData = + def extract(state: State, structure: BuildStructure): (State, GlobalPluginData) = { import structure.{data, root, rootProject} val p: Scope = Scope.GlobalScope in ProjectRef(root, rootProject(root)) @@ -47,12 +48,13 @@ object GlobalPlugin val task = taskInit mapReferenced Project.mapScope(Scope replaceThis p) evaluate data evaluate(state, structure, task) } - def evaluate[T](state: State, structure: BuildStructure, t: Task[T]): T = + def evaluate[T](state: State, structure: BuildStructure, t: Task[T]): (State, T) = { import EvaluateTask._ withStreams(structure) { str => val nv = nodeView(state, str) - processResult(runTask(t, str, structure.index.triggers)(nv), log(state)) + val (newS, result) = runTask(t, state, str, structure.index.triggers)(nv) + (newS, processResult(result, log(newS))) } } private[this] def log(s: State) = CommandSupport.logger(s) diff --git a/main/Keys.scala b/main/Keys.scala index 86cb3b7a3..9cbf6474d 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -15,6 +15,7 @@ package sbt import descriptor.ModuleDescriptor, id.ModuleRevisionId import org.scalatools.testing.Framework import Configurations.CompilerPlugin + import Types.Id object Keys { @@ -47,6 +48,7 @@ object Keys val onLoad = SettingKey[State => State]("on-load", "Transformation to apply to the build state when the build is loaded.") val onUnload = SettingKey[State => State]("on-unload", "Transformation to apply to the build state when the build is unloaded.") val onLoadMessage = SettingKey[String]("on-load-message", "Message to display when the project is loaded.") + val transformState = AttributeKey[State => State]("transform-state", "State transformation to apply after tasks run.") 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.") // https://issues.scala-lang.org/browse/SI-2915 @@ -291,6 +293,7 @@ object Keys val skip = TaskKey[Boolean]("skip", "For tasks that support it (currently only 'compile'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.") // special + val sessionVars = AttributeKey[SessionVar.Map]("session-vars", "Bindings that exist for the duration of the session.") val parallelExecution = SettingKey[Boolean]("parallel-execution", "Enables (true) or disables (false) parallel execution of tasks.") val settings = TaskKey[Settings[Scope]]("settings", "Provides access to the project data for the build.") val streams = TaskKey[TaskStreams]("streams", "Provides streams for logging and persisting data.") diff --git a/main/Project.scala b/main/Project.scala index 80d4bf1d1..e98b1f1ce 100644 --- a/main/Project.scala +++ b/main/Project.scala @@ -6,11 +6,11 @@ package sbt import java.io.File import java.net.URI import Project._ - import Keys.{appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, streams, thisProject, thisProjectRef, watch} + import Keys.{appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, sessionVars, shellPrompt, thisProject, thisProjectRef, watch} import Scope.{GlobalScope,ThisScope} import Load.BuildStructure import CommandSupport.logger - import Types.idFun + import Types.{idFun, Id} sealed trait ProjectDefinition[PR <: ProjectReference] { @@ -71,13 +71,15 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings, def get[T](key: ScopedSetting[T]) = getOrError(inCurrent(key), key.key) def getOpt[T](key: ScopedSetting[T]): Option[T] = structure.data.get(inCurrent(key), key.key) private[this] def inCurrent[T](key: ScopedSetting[T]): Scope = if(key.scope.project == This) key.scope.copy(project = Select(currentRef)) else key.scope - def evalTask[T](key: ScopedTask[T], state: State): T = + @deprecated("This method does not apply state changes requested during task execution. Use 'runTask' instead, which does.", "0.11.1") + def evalTask[T](key: ScopedTask[T], state: State): T = runTask(key, state)._2 + def runTask[T](key: ScopedTask[T], state: State): (State, T) = { import EvaluateTask._ val rkey = Project.mapScope(Scope.resolveScope(GlobalScope, currentRef.build, rootProject) )( key.scopedKey ) - val value: Option[Result[T]] = evaluateTask(structure, key.task.scopedKey, state, currentRef) - val result = getOrError(rkey.scope, rkey.key, value) - processResult(result, ConsoleLogger()) + val value: Option[(State, Result[T])] = apply(structure, key.task.scopedKey, state, currentRef) + val (newS, result) = getOrError(rkey.scope, rkey.key, value) + (newS, processResult(result, logger(newS))) } private def getOrError[T](scope: Scope, key: AttributeKey[_], value: Option[T])(implicit display: Show[ScopedKey[_]]): T = value getOrElse error(display(ScopedKey(scope, key)) + " is undefined.") @@ -153,12 +155,16 @@ object Project extends Init[Scope] with ProjectExtra def getProject(ref: ProjectRef, units: Map[URI, Load.LoadedBuildUnit]): Option[ResolvedProject] = (units get ref.build).flatMap(_.defined get ref.project) - def setProject(session: SessionSettings, structure: BuildStructure, s: State): State = + def runUnloadHooks(s: State): State = { val previousOnUnload = orIdentity(s get Keys.onUnload.key) - val unloaded = previousOnUnload(s.runExitHooks()) + previousOnUnload(s.runExitHooks()) + } + def setProject(session: SessionSettings, structure: BuildStructure, s: State): State = + { + val unloaded = runUnloadHooks(s) val (onLoad, onUnload) = getHooks(structure.data) - val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload) + val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload).put(sessionVars, SessionVar.emptyMap) val newState = unloaded.copy(attributes = newAttrs) onLoad(updateCurrent( newState )) } @@ -323,11 +329,13 @@ object Project extends Init[Scope] with ProjectExtra val newS = setProjectReturn(s, newBase :: projectReturn(s)) (newS, newBase) } - + @deprecated("This method does not apply state changes requested during task execution. Use 'runTask' instead, which does.", "0.11.1") def evaluateTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false, maxWorkers: Int = EvaluateTask.SystemProcessors): Option[Result[T]] = + runTask(taskKey, state, checkCycles, maxWorkers).map(_._2) + def runTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false, maxWorkers: Int = EvaluateTask.SystemProcessors): Option[(State, Result[T])] = { val extracted = Project.extract(state) - EvaluateTask.evaluateTask(extracted.structure, taskKey, state, extracted.currentRef, checkCycles, maxWorkers) + EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, checkCycles, maxWorkers) } // this is here instead of Scoped so that it is considered without need for import (because of Project.Initialize) implicit def richInitializeTask[T](init: Initialize[Task[T]]): Scoped.RichInitializeTask[T] = new Scoped.RichInitializeTask(init) @@ -345,4 +353,66 @@ trait ProjectExtra inScope(ThisScope.copy(task = Select(t.key)) )( ss ) def inScope(scope: Scope)(ss: Seq[Setting[_]]): Seq[Setting[_]] = Project.transform(Scope.replaceThis(scope), ss) +} + + import sbinary.{Format, Operations} +object SessionVar +{ + // these are required because of inference+manifest limitations + final case class Key[T](key: ScopedKey[Task[T]]) + final case class Map(map: IMap[Key, Id]) { + def get[T](k: ScopedKey[Task[T]]): Option[T] = map get Key(k) + def put[T](k: ScopedKey[Task[T]], v: T): Map = Map(map put (Key(k), v)) + } + def emptyMap = Map(IMap.empty) + + def persistAndSet[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: sbinary.Format[T]): State = + { + persist(key, state, value)(f) + set(key, state, value) + } + + def persist[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: sbinary.Format[T]): Unit = + Project.structure(state).streams.use(key)( s => + Operations.write(s.binary(TaskData.DefaultDataID), value)(f) + ) + + def get[T](key: ScopedKey[Task[T]], state: State): Option[T] = orEmpty(state get sessionVars) get key + + def set[T](key: ScopedKey[Task[T]], state: State, value: T): State = state.update(sessionVars)(om => orEmpty(om) put (key, value)) + + def orEmpty(opt: Option[Map]) = opt getOrElse emptyMap + + def transform[S](task: Task[S], f: (State, S) => State): Task[S] = + { + val g = (s: S, map: AttributeMap) => map.put(Keys.transformState, (state: State) => f(state, s)) + task.copy(info = task.info.postTransform(g)) + } + + def resolveContext[T](key: ScopedKey[Task[T]], context: Scope, state: State): ScopedKey[Task[T]] = + { + val subScope = Scope.replaceThis(context)(key.scope) + val scope = Project.structure(state).data.definingScope(subScope, key.key) getOrElse subScope + ScopedKey(scope, key.key) + } + + def read[T](key: ScopedKey[Task[T]], state: State)(implicit f: Format[T]): Option[T] = + Project.structure(state).streams.use(key) { s => + try { Some(Operations.read(s.readBinary(key, TaskData.DefaultDataID))) } + catch { case e: Exception => None } + } + + def load[T](key: ScopedKey[Task[T]], state: State)(implicit f: Format[T]): Option[T] = + get(key, state) orElse read(key, state)(f) + + def loadAndSet[T](key: ScopedKey[Task[T]], state: State, setIfUnset: Boolean = true)(implicit f: Format[T]): (State, Option[T]) = + get(key, state) match { + case s: Some[T] => (state, s) + case None => read(key, state)(f) match { + case s @ Some(t) => + val newState = if(setIfUnset && get(key, state).isDefined) state else set(key, state, t) + (newState, s) + case None => (state, None) + } + } } \ No newline at end of file diff --git a/main/SessionSettings.scala b/main/SessionSettings.scala index cc8a7a86e..94a87bb7f 100644 --- a/main/SessionSettings.scala +++ b/main/SessionSettings.scala @@ -166,4 +166,4 @@ save, save-all case c: Clear => if(c.all) clearAllSettings(s) else clearSettings(s) case r: Remove => removeSettings(s,r.ranges) } -} +} \ No newline at end of file diff --git a/main/State.scala b/main/State.scala index 7ea7bcdfe..2dd2cc37e 100644 --- a/main/State.scala +++ b/main/State.scala @@ -40,6 +40,7 @@ trait StateOps { def get[T](key: AttributeKey[T]): Option[T] def put[T](key: AttributeKey[T], value: T): State def remove(key: AttributeKey[_]): State + def update[T](key: AttributeKey[T])(f: Option[T] => T): State def has(key: AttributeKey[_]): Boolean def baseDir: File def locked[T](file: File)(t: => T): T @@ -73,6 +74,7 @@ object State def exit(ok: Boolean) = setResult(Some(Exit(if(ok) 0 else 1))) def get[T](key: AttributeKey[T]) = s.attributes get key def put[T](key: AttributeKey[T], value: T) = s.copy(attributes = s.attributes.put(key, value)) + def update[T](key: AttributeKey[T])(f: Option[T] => T): State = put(key, f(get(key))) def has(key: AttributeKey[_]) = s.attributes contains key def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key) def fail = diff --git a/main/Structure.scala b/main/Structure.scala index 5d1b07ffd..e27e6aa55 100644 --- a/main/Structure.scala +++ b/main/Structure.scala @@ -190,6 +190,15 @@ object Scoped def dependsOn(tasks: AnyInitTask*): Initialize[Task[S]] = (i, Initialize.joinAny(tasks)) { (thisTask, deps) => thisTask.dependsOn(deps : _*) } + import SessionVar.{persistAndSet, resolveContext, set, transform} + + def updateState(f: (State, S) => State): Initialize[Task[S]] = i(t => transform(t, f)) + def storeAs(key: ScopedTask[S])(implicit f: sbinary.Format[S]): Initialize[Task[S]] = (Keys.resolvedScoped, i) { (scoped, task) => + transform(task, (state, value) => persistAndSet( resolveContext(key, scoped.scope, state), state, value)(f)) + } + def keepAs(key: ScopedTask[S]): Initialize[Task[S]] = + (i, Keys.resolvedScoped)( (t,scoped) => transform(t, (state,value) => set(resolveContext(key, scoped.scope, state), state, value) ) ) + def triggeredBy(tasks: AnyInitTask*): Initialize[Task[S]] = nonLocal(tasks, Keys.triggeredBy) def runBefore(tasks: AnyInitTask*): Initialize[Task[S]] = nonLocal(tasks, Keys.runBefore) private[this] def nonLocal(tasks: Seq[AnyInitTask], key: AttributeKey[Seq[Task[_]]]): Initialize[Task[S]] = diff --git a/sbt/src/sbt-test/actions/state/project/Build.scala b/sbt/src/sbt-test/actions/state/project/Build.scala new file mode 100644 index 000000000..23c7f1ab0 --- /dev/null +++ b/sbt/src/sbt-test/actions/state/project/Build.scala @@ -0,0 +1,50 @@ +import sbt._ +import Keys._ +import complete.Parser +import complete.DefaultParsers._ +import sbinary.DefaultProtocol._ +import Project.Initialize + +object MyBuild extends Build +{ + val keep = TaskKey[Int]("keep") + val persisted = TaskKey[Int]("persist") + val checkKeep = InputKey[Unit]("check-keep") + val checkPersisted = InputKey[Unit]("check-persist") + + val updateDemo = TaskKey[Int]("demo") + val check = InputKey[Unit]("check") + val sample = AttributeKey[Int]("demo-key") + + def updateDemoInit = state map { s => (s get sample getOrElse 9) + 1 } + + lazy val root = Project("root", file(".")) settings( + updateDemo <<= updateDemoInit updateState demoState, + check <<= checkInit, + inMemorySetting, + persistedSetting, + inMemoryCheck, + persistedCheck + ) + def demoState(s: State, i: Int): State = s put (sample, i + 1) + + def checkInit: Initialize[InputTask[Unit]] = InputTask( (_: State) => token(Space ~> IntBasic) ~ token(Space ~> IntBasic).?) { key => + (key, updateDemo, state) map { case ((curExpected, prevExpected), value, s) => + val prev = s get sample + assert(value == curExpected, "Expected current value to be " + curExpected + ", got " + value) + assert(prev == prevExpected, "Expected previous value to be " + prevExpected + ", got " + prev) + } + } + + def inMemorySetting = keep <<= getPrevious(keep) map { case None => 3; case Some(x) => x + 1} keepAs(keep) + def persistedSetting = persisted <<= loadPrevious(persisted) map { case None => 17; case Some(x) => x + 1} storeAs(keep) + + def inMemoryCheck = checkKeep <<= inputCheck( (ctx, s) => Space ~> str(getFromContext(keep, ctx, s)) ) + def persistedCheck = checkPersisted <<= inputCheck( (ctx, s) => Space ~> str(loadFromContext(persisted, ctx, s)) ) + + def inputCheck[T](f: (ScopedKey[_], State) => Parser[T]): Initialize[InputTask[Unit]] = + InputTask( resolvedScoped(ctx => (s: State) => f(ctx, s)) )( dummyTask ) + + def dummyTask = (key: Any) => maxErrors map { _ => () } + def str(o: Option[Int]) = o match { case None => "blue"; case Some(i) => i.toString } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/actions/state/test b/sbt/src/sbt-test/actions/state/test new file mode 100644 index 000000000..3042edd51 --- /dev/null +++ b/sbt/src/sbt-test/actions/state/test @@ -0,0 +1,39 @@ +> check 10 +> check 12 11 +> check 14 13 +> reload +> check 16 15 + + +-> check-keep 3 +> check-keep blue + +> keep +> check-keep 3 +-> check-keep 4 +> check-keep 3 + +> keep +-> check-keep 3 +> check-keep 4 +> check-keep 4 + +> reload +> check-keep blue +> keep +> check-keep 3 + + +-> check-persist 17 +> check-persist blue + +> persist +> check-persist 17 +-> check-persist blue + +> persist +> check-persist 18 +> reload +> check-persist 18 +> persist +> check-persist 19 diff --git a/tasks/standard/Action.scala b/tasks/standard/Action.scala index 9b13404af..c80b1de03 100644 --- a/tasks/standard/Action.scala +++ b/tasks/standard/Action.scala @@ -22,13 +22,12 @@ object Task type Results[HL <: HList] = KList[Result, HL] } -final case class Task[T](info: Info, work: Action[T]) +final case class Task[T](info: Info[T], work: Action[T]) { override def toString = info.name getOrElse ("Task(" + info + ")") override def hashCode = info.hashCode } -/** `original` is used during transformation only.*/ -final case class Info(attributes: AttributeMap = AttributeMap.empty) +final case class Info[T](attributes: AttributeMap = AttributeMap.empty, post: T => AttributeMap = const(AttributeMap.empty)) { import Info._ def name = attributes.get(Name) @@ -36,12 +35,9 @@ final case class Info(attributes: AttributeMap = AttributeMap.empty) def setName(n: String) = set(Name, n) def setDescription(d: String) = set(Description, d) def set[T](key: AttributeKey[T], value: T) = copy(attributes = this.attributes.put(key, value)) + def postTransform[A](f: (T, AttributeMap) => AttributeMap) = copy(post = (t: T) => f(t, post(t)) ) - override def toString = - if(attributes.isEmpty) - "_" - else - attributes.toString + override def toString = if(attributes.isEmpty) "_" else attributes.toString } object Info { diff --git a/tasks/standard/Streams.scala b/tasks/standard/Streams.scala index d92ea34a5..f39cee152 100644 --- a/tasks/standard/Streams.scala +++ b/tasks/standard/Streams.scala @@ -23,6 +23,7 @@ sealed trait TaskStreams[Key] final def readText(a: Key, sid: Option[String]): BufferedReader = readText(a, getID(sid)) final def readBinary(a: Key, sid: Option[String]): BufferedInputStream = readBinary(a, getID(sid)) + def key: Key def text(sid: String = default): PrintWriter def binary(sid: String = default): BufferedOutputStream @@ -91,7 +92,8 @@ object Streams opened ::= t t } - + + def key: Key = a def open() {} def close(): Unit = synchronized {