sbt.Previous.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main-settings_2.12 Show documentation
Show all versions of main-settings_2.12 Show documentation
sbt is an interactive build tool
The newest version!
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
import sbt.Def.{ Initialize, ScopedKey }
import sbt.Previous._
import sbt.Scope.Global
import sbt.SlashSyntax0._
import sbt.internal.util._
import sbt.std.TaskExtra._
import sbt.util.StampedFormat
import sjsonnew.JsonFormat
import scala.util.control.NonFatal
/**
* 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[Previous.Key, Referenced]) {
private[this] var map = IMap.empty[Previous.Key, ReferencedValue]
// We can't use mapValues to transform the map because mapValues is lazy and evaluates the
// transformation function every time a value is fetched from the map, defeating the entire
// purpose of ReferencedValue.
for (referenced.TPair(k, v) <- referenced.toTypedSeq) map = map.put(k, new ReferencedValue(v))
private[this] final class ReferencedValue[T](referenced: Referenced[T]) {
lazy val previousValue: Option[T] = referenced.read(streams)
}
/** Used by the .previous runtime implementation to get the previous value for task `key`. */
private def get[T](key: Key[T]): Option[T] =
map.get(key).flatMap(_.previousValue)
}
object Previous {
import sjsonnew.BasicJsonProtocol.StringJsonFormat
private[sbt] type ScopedTaskKey[T] = ScopedKey[Task[T]]
private type AnyTaskKey = ScopedTaskKey[Any]
private type Streams = sbt.std.Streams[ScopedKey[_]]
/** The stream where the task value is persisted. */
private final val StreamName = "previous"
private[sbt] final val DependencyDirectory = "previous-dependencies"
/** Represents a reference task.previous*/
private[sbt] final class Referenced[T](val key: Key[T], val format: JsonFormat[T]) {
def this(task: ScopedTaskKey[T], format: JsonFormat[T]) = this(Key(task, task), format)
@deprecated("unused", "1.3.0")
private[sbt] def task: ScopedKey[Task[T]] = key.task
lazy val stamped: JsonFormat[T] =
StampedFormat.withStamp(key.task.key.manifest.toString)(format)
def setTask(newTask: ScopedKey[Task[T]]) = new Referenced(newTask, format)
private[sbt] def read(streams: Streams): Option[T] =
try Option(streams(key.cacheKey).cacheStoreFactory.make(StreamName).read[T]()(stamped))
catch { case NonFatal(_) => None }
}
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[sbt] class Key[T](val task: ScopedKey[Task[T]], val enclosing: AnyTaskKey) {
override def equals(o: Any): Boolean = o match {
case that: Key[_] => this.task == that.task && this.enclosing == that.enclosing
case _ => false
}
override def hashCode(): Int = (task.## * 31) ^ enclosing.##
def cacheKey: AnyTaskKey = {
if (task == enclosing) task.asInstanceOf[ScopedKey[Task[Any]]]
else {
val am = enclosing.scope.extra match {
case Select(a) => a.put(scopedKeyAttribute, task.asInstanceOf[AnyTaskKey])
case _ => AttributeMap.empty.put(scopedKeyAttribute, task.asInstanceOf[AnyTaskKey])
}
Def.ScopedKey(enclosing.scope.copy(extra = Select(am)), enclosing.key)
}
}.asInstanceOf[AnyTaskKey]
}
private[sbt] object Key {
def apply[T, U](key: ScopedKey[Task[T]], enclosing: ScopedKey[Task[U]]): Key[T] =
new Key(key, enclosing.asInstanceOf[AnyTaskKey])
}
/** 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[Key, Referenced]
@deprecated("unused", "1.3.0")
def recordReference[T](key: ScopedKey[Task[T]], format: JsonFormat[T]): Unit =
recordReference(Key(key, key), format)
// TODO: this arbitrarily chooses a JsonFormat.
// The need to choose is a fundamental problem with this approach, but this should at least make a stable choice.
def recordReference[T](key: Key[T], format: JsonFormat[T]): Unit = synchronized {
map = map.put(key, new Referenced(key, format))
}
def getReferences: IMap[Key, 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
val reverse = map.keys.groupBy(_.task)
// We first collect all of the successful tasks and write their scoped key into a map
// along with their values.
val successfulTaskResults = (for {
results.TPair(task, Value(v)) <- results.toTypedSeq
key <- task.info.attributes.get(Def.taskDefinitionKey).asInstanceOf[Option[AnyTaskKey]]
} yield key -> v).toMap
// We then traverse the successful results and look up all of the referenced values for
// each of these tasks. This can be a many to one relationship if multiple tasks refer
// the previous value of another task. For each reference we find, we check if the task has
// been successfully evaluated. If so, we write it to the appropriate previous cache for
// the completed task.
for {
(k, v) <- successfulTaskResults
keys <- reverse.get(k)
key <- keys if successfulTaskResults.contains(key.enclosing)
ref <- map.get(key.asInstanceOf[Key[Any]])
} {
val out = streams(key.cacheKey).cacheStoreFactory.make(StreamName)
try out.write(v)(ref.stamped)
catch { case NonFatal(_) => }
}
}
private[sbt] val scopedKeyAttribute = AttributeKey[AnyTaskKey](
"previous-scoped-key-attribute",
"Specifies a scoped key for a task on which .previous is called. Used to " +
"set the cache directory for the task-specific previous value: see Previous.runtimeInEnclosingTask."
)
/** Public as a macro implementation detail. Do not call directly. */
def runtime[T](skey: TaskKey[T])(implicit format: JsonFormat[T]): Initialize[Task[Option[T]]] = {
val inputs = (Global / cache) zip Def.validated(skey, selfRefOk = true) zip (Global / references)
inputs {
case ((prevTask, resolved), refs) =>
val key = Key(resolved, resolved)
refs.recordReference(key, format) // always evaluated on project load
prevTask.map(_.get(key)) // evaluated if this task is evaluated
}
}
/** Public as a macro implementation detail. Do not call directly. */
def runtimeInEnclosingTask[T](skey: TaskKey[T])(
implicit format: JsonFormat[T]
): Initialize[Task[Option[T]]] = {
val inputs = (Global / cache)
.zip(Def.validated(skey, selfRefOk = true))
.zip(Global / references)
.zip(Def.resolvedScoped)
inputs {
case (((prevTask, resolved), refs), inTask: ScopedKey[Task[_]] @unchecked) =>
val key = Key(resolved, inTask)
refs.recordReference(key, format) // always evaluated on project load
prevTask.map(_.get(key)) // evaluated if this task is evaluated
}
}
}