mirror of https://github.com/sbt/sbt.git
Caching based on sjsonnew
This commit is contained in:
parent
d760b15193
commit
4cffccc8c8
12
build.sbt
12
build.sbt
|
|
@ -111,22 +111,24 @@ lazy val utilLogic = (project in internalPath / "util-logic").
|
|||
name := "Util Logic"
|
||||
)
|
||||
|
||||
// Persisted caching based on SBinary
|
||||
// Persisted caching based on sjson-new
|
||||
lazy val utilCache = (project in internalPath / "util-cache").
|
||||
dependsOn(utilCollection).
|
||||
dependsOn(utilCollection, utilTesting % Test).
|
||||
settings(
|
||||
commonSettings,
|
||||
name := "Util Cache",
|
||||
libraryDependencies ++= Seq(sbinary, sbtSerialization, scalaReflect.value, sbtIO) ++ scalaXml.value
|
||||
libraryDependencies ++= Seq(datatypeCodecs, sbtSerialization, scalaReflect.value, sbtIO) ++ scalaXml.value,
|
||||
libraryDependencies += sjsonnewScalaJson % Test
|
||||
)
|
||||
|
||||
// Builds on cache to provide caching for filesystem-related operations
|
||||
lazy val utilTracking = (project in internalPath / "util-tracking").
|
||||
dependsOn(utilCache).
|
||||
dependsOn(utilCache, utilTesting % Test).
|
||||
settings(
|
||||
commonSettings,
|
||||
name := "Util Tracking",
|
||||
libraryDependencies += sbtIO
|
||||
libraryDependencies += sbtIO,
|
||||
libraryDependencies += sjsonnewScalaJson % Test
|
||||
)
|
||||
|
||||
// Internal utility for testing
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.datatype.StringFormat
|
||||
import sbt.internal.util.Types.:+:
|
||||
|
||||
import sjsonnew.{ Builder, deserializationError, JsonFormat, Unbuilder }
|
||||
import sjsonnew.BasicJsonProtocol.{ wrap, asSingleton }
|
||||
|
||||
import java.io.File
|
||||
|
||||
import java.net.{ URI, URL }
|
||||
|
||||
trait URIFormat { self: StringFormat =>
|
||||
implicit def URIFormat: JsonFormat[URI] = wrap(_.toString, new URI(_: String))
|
||||
}
|
||||
|
||||
trait URLFormat { self: StringFormat =>
|
||||
implicit def URLFormat: JsonFormat[URL] = wrap(_.toString, new URL(_: String))
|
||||
}
|
||||
|
||||
trait FileFormat { self: StringFormat =>
|
||||
implicit def FileFormat: JsonFormat[File] = wrap(_.toString, new File(_: String))
|
||||
}
|
||||
|
||||
trait HListFormat {
|
||||
implicit def HConsFormat[H: JsonFormat, T <: HList: JsonFormat]: JsonFormat[H :+: T] =
|
||||
new JsonFormat[H :+: T] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): H :+: T =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val h = unbuilder.readField[H]("h")
|
||||
val t = unbuilder.readField[T]("t")
|
||||
unbuilder.endObject()
|
||||
|
||||
HCons(h, t)
|
||||
|
||||
case None =>
|
||||
deserializationError("Expect JValue but found None")
|
||||
}
|
||||
|
||||
override def write[J](obj: H :+: T, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("h", obj.head)
|
||||
builder.addField("t", obj.tail)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
implicit val HNilFormat: JsonFormat[HNil] = asSingleton(HNil)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.datatype.{ ArrayFormat, BooleanFormat, ByteFormat, IntFormat }
|
||||
|
||||
import java.net.{ URI, URL }
|
||||
|
||||
import sjsonnew.JsonFormat
|
||||
import sjsonnew.BasicJsonProtocol.asSingleton
|
||||
|
||||
trait BasicCacheImplicits { self: ArrayFormat with BooleanFormat with ByteFormat with IntFormat =>
|
||||
|
||||
implicit def basicCache[I: JsonFormat: Equiv, O: JsonFormat]: Cache[I, O] =
|
||||
new BasicCache[I, O]()
|
||||
|
||||
def defaultEquiv[T]: Equiv[T] =
|
||||
new Equiv[T] { def equiv(a: T, b: T) = a == b }
|
||||
|
||||
def wrapEquiv[S, T](f: S => T)(implicit eqT: Equiv[T]): Equiv[S] =
|
||||
new Equiv[S] {
|
||||
def equiv(a: S, b: S) =
|
||||
eqT.equiv(f(a), f(b))
|
||||
}
|
||||
|
||||
implicit def optEquiv[T](implicit t: Equiv[T]): Equiv[Option[T]] =
|
||||
new Equiv[Option[T]] {
|
||||
def equiv(a: Option[T], b: Option[T]) =
|
||||
(a, b) match {
|
||||
case (None, None) => true
|
||||
case (Some(va), Some(vb)) => t.equiv(va, vb)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
implicit def urlEquiv(implicit uriEq: Equiv[URI]): Equiv[URL] = wrapEquiv[URL, URI](_.toURI)(uriEq)
|
||||
implicit def uriEquiv: Equiv[URI] = defaultEquiv
|
||||
implicit def stringSetEquiv: Equiv[Set[String]] = defaultEquiv
|
||||
implicit def stringMapEquiv: Equiv[Map[String, String]] = defaultEquiv
|
||||
|
||||
implicit def arrEquiv[T](implicit t: Equiv[T]): Equiv[Array[T]] =
|
||||
wrapEquiv((x: Array[T]) => x: Seq[T])(seqEquiv[T](t))
|
||||
|
||||
implicit def seqEquiv[T](implicit t: Equiv[T]): Equiv[Seq[T]] =
|
||||
new Equiv[Seq[T]] {
|
||||
def equiv(a: Seq[T], b: Seq[T]) =
|
||||
a.length == b.length &&
|
||||
((a, b).zipped forall t.equiv)
|
||||
}
|
||||
|
||||
def wrapIn[I, J](implicit f: I => J, g: J => I, jCache: SingletonCache[J]): SingletonCache[I] =
|
||||
new SingletonCache[I] {
|
||||
override def read(from: Input): I = g(jCache.read(from))
|
||||
override def write(to: Output, value: I) = jCache.write(to, f(value))
|
||||
override def equiv: Equiv[I] = wrapEquiv(f)(jCache.equiv)
|
||||
}
|
||||
|
||||
def singleton[T](t: T): SingletonCache[T] =
|
||||
SingletonCache.basicSingletonCache(asSingleton(t), trueEquiv)
|
||||
|
||||
def trueEquiv[T] = new Equiv[T] { def equiv(a: T, b: T) = true }
|
||||
}
|
||||
|
|
@ -3,247 +3,72 @@
|
|||
*/
|
||||
package sbt.internal.util
|
||||
|
||||
import sbinary.{ CollectionTypes, DefaultProtocol, Format, Input, JavaFormats, Output => Out }
|
||||
import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, File, InputStream, OutputStream }
|
||||
import java.net.{ URI, URL }
|
||||
import Types.:+:
|
||||
import DefaultProtocol.{ asSingleton, BooleanFormat, ByteFormat, IntFormat, wrap }
|
||||
import scala.xml.NodeSeq
|
||||
import scala.language.existentials
|
||||
/** The result of a cache query */
|
||||
sealed trait CacheResult[K]
|
||||
|
||||
/** A successful hit on the cache */
|
||||
case class Hit[O](value: O) extends CacheResult[O]
|
||||
|
||||
/**
|
||||
* A cache miss.
|
||||
* `update` associates the missing key with `O` in the cache.
|
||||
*/
|
||||
case class Miss[O](update: O => Unit) extends CacheResult[O]
|
||||
|
||||
/**
|
||||
* A simple cache with keys of type `I` and values of type `O`
|
||||
*/
|
||||
trait Cache[I, O] {
|
||||
def apply(file: File)(i: I): Either[O, O => Unit]
|
||||
/**
|
||||
* Queries the cache backed with store `store` for key `key`.
|
||||
*/
|
||||
def apply(store: CacheStore)(key: I): CacheResult[O]
|
||||
}
|
||||
trait SBinaryFormats extends CollectionTypes with JavaFormats {
|
||||
implicit def urlFormat: Format[URL] = DefaultProtocol.UrlFormat
|
||||
implicit def uriFormat: Format[URI] = DefaultProtocol.UriFormat
|
||||
}
|
||||
object Cache extends CacheImplicits {
|
||||
|
||||
object Cache {
|
||||
|
||||
/**
|
||||
* Materializes a cache.
|
||||
*/
|
||||
def cache[I, O](implicit c: Cache[I, O]): Cache[I, O] = c
|
||||
|
||||
def cached[I, O](file: File)(f: I => O)(implicit cache: Cache[I, O]): I => O =
|
||||
in =>
|
||||
cache(file)(in) match {
|
||||
case Left(value) => value
|
||||
case Right(store) =>
|
||||
val out = f(in)
|
||||
store(out)
|
||||
out
|
||||
/**
|
||||
* Returns a function that represents a cache that inserts on miss.
|
||||
*
|
||||
* @param store The store that backs this cache.
|
||||
* @param default A function that computes a default value to insert on
|
||||
*/
|
||||
def cached[I, O](store: CacheStore)(default: I => O)(implicit cache: Cache[I, O]): I => O =
|
||||
key =>
|
||||
cache(store)(key) match {
|
||||
case Hit(value) =>
|
||||
value
|
||||
|
||||
case Miss(update) =>
|
||||
val result = default(key)
|
||||
update(result)
|
||||
result
|
||||
}
|
||||
|
||||
def debug[I](label: String, c: InputCache[I]): InputCache[I] =
|
||||
new InputCache[I] {
|
||||
type Internal = c.Internal
|
||||
def convert(i: I) = c.convert(i)
|
||||
def read(from: Input) =
|
||||
{
|
||||
val v = c.read(from)
|
||||
println(label + ".read: " + v)
|
||||
v
|
||||
}
|
||||
def write(to: Out, v: Internal): Unit = {
|
||||
println(label + ".write: " + v)
|
||||
c.write(to, v)
|
||||
def debug[I](label: String, cache: SingletonCache[I]): SingletonCache[I] =
|
||||
new SingletonCache[I] {
|
||||
override def read(from: Input): I = {
|
||||
val value = cache.read(from)
|
||||
println(label + ".read: " + value)
|
||||
value
|
||||
}
|
||||
def equiv: Equiv[Internal] = new Equiv[Internal] {
|
||||
def equiv(a: Internal, b: Internal) =
|
||||
{
|
||||
val equ = c.equiv.equiv(a, b)
|
||||
println(label + ".equiv(" + a + ", " + b + "): " + equ)
|
||||
equ
|
||||
}
|
||||
|
||||
override def write(to: Output, value: I): Unit = {
|
||||
println(label + ".write: " + value)
|
||||
cache.write(to, value)
|
||||
}
|
||||
|
||||
override def equiv: Equiv[I] = new Equiv[I] {
|
||||
def equiv(a: I, b: I) = {
|
||||
val equ = cache.equiv.equiv(a, b)
|
||||
println(label + ".equiv(" + a + ", " + b + "): " + equ)
|
||||
equ
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
trait CacheImplicits extends BasicCacheImplicits with SBinaryFormats with HListCacheImplicits with UnionImplicits
|
||||
trait BasicCacheImplicits {
|
||||
implicit def basicCache[I, O](implicit in: InputCache[I], outFormat: Format[O]): Cache[I, O] =
|
||||
new BasicCache()(in, outFormat)
|
||||
def basicInput[I](implicit eq: Equiv[I], fmt: Format[I]): InputCache[I] = InputCache.basicInputCache(fmt, eq)
|
||||
|
||||
def defaultEquiv[T]: Equiv[T] = new Equiv[T] { def equiv(a: T, b: T) = a == b }
|
||||
|
||||
implicit def optInputCache[T](implicit t: InputCache[T]): InputCache[Option[T]] =
|
||||
new InputCache[Option[T]] {
|
||||
type Internal = Option[t.Internal]
|
||||
def convert(v: Option[T]): Internal = v.map(x => t.convert(x))
|
||||
def read(from: Input) =
|
||||
{
|
||||
val isDefined = BooleanFormat.reads(from)
|
||||
if (isDefined) Some(t.read(from)) else None
|
||||
}
|
||||
def write(to: Out, j: Internal): Unit =
|
||||
{
|
||||
BooleanFormat.writes(to, j.isDefined)
|
||||
j foreach { x => t.write(to, x) }
|
||||
}
|
||||
def equiv = optEquiv(t.equiv)
|
||||
}
|
||||
|
||||
def wrapEquiv[S, T](f: S => T)(implicit eqT: Equiv[T]): Equiv[S] =
|
||||
new Equiv[S] {
|
||||
def equiv(a: S, b: S) =
|
||||
eqT.equiv(f(a), f(b))
|
||||
}
|
||||
|
||||
implicit def optEquiv[T](implicit t: Equiv[T]): Equiv[Option[T]] =
|
||||
new Equiv[Option[T]] {
|
||||
def equiv(a: Option[T], b: Option[T]) =
|
||||
(a, b) match {
|
||||
case (None, None) => true
|
||||
case (Some(va), Some(vb)) => t.equiv(va, vb)
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
implicit def urlEquiv(implicit uriEq: Equiv[URI]): Equiv[URL] = wrapEquiv[URL, URI](_.toURI)(uriEq)
|
||||
implicit def uriEquiv: Equiv[URI] = defaultEquiv
|
||||
implicit def stringSetEquiv: Equiv[Set[String]] = defaultEquiv
|
||||
implicit def stringMapEquiv: Equiv[Map[String, String]] = defaultEquiv
|
||||
|
||||
def streamFormat[T](write: (T, OutputStream) => Unit, f: InputStream => T): Format[T] =
|
||||
{
|
||||
val toBytes = (t: T) => { val bos = new ByteArrayOutputStream; write(t, bos); bos.toByteArray }
|
||||
val fromBytes = (bs: Array[Byte]) => f(new ByteArrayInputStream(bs))
|
||||
wrap(toBytes, fromBytes)(DefaultProtocol.ByteArrayFormat)
|
||||
}
|
||||
|
||||
implicit def xmlInputCache(implicit strEq: InputCache[String]): InputCache[NodeSeq] = wrapIn[NodeSeq, String](_.toString, strEq)
|
||||
|
||||
implicit def seqCache[T](implicit t: InputCache[T]): InputCache[Seq[T]] =
|
||||
new InputCache[Seq[T]] {
|
||||
type Internal = Seq[t.Internal]
|
||||
def convert(v: Seq[T]) = v.map(x => t.convert(x))
|
||||
def read(from: Input) =
|
||||
{
|
||||
val size = IntFormat.reads(from)
|
||||
def next(left: Int, acc: List[t.Internal]): Internal =
|
||||
if (left <= 0) acc.reverse else next(left - 1, t.read(from) :: acc)
|
||||
next(size, Nil)
|
||||
}
|
||||
def write(to: Out, vs: Internal): Unit = {
|
||||
val size = vs.length
|
||||
IntFormat.writes(to, size)
|
||||
for (v <- vs) t.write(to, v)
|
||||
}
|
||||
def equiv: Equiv[Internal] = seqEquiv(t.equiv)
|
||||
}
|
||||
|
||||
implicit def arrEquiv[T](implicit t: Equiv[T]): Equiv[Array[T]] =
|
||||
wrapEquiv((x: Array[T]) => x: Seq[T])(seqEquiv[T](t))
|
||||
|
||||
implicit def seqEquiv[T](implicit t: Equiv[T]): Equiv[Seq[T]] =
|
||||
new Equiv[Seq[T]] {
|
||||
def equiv(a: Seq[T], b: Seq[T]) =
|
||||
a.length == b.length &&
|
||||
((a, b).zipped forall t.equiv)
|
||||
}
|
||||
implicit def seqFormat[T](implicit t: Format[T]): Format[Seq[T]] =
|
||||
wrap[Seq[T], List[T]](_.toList, _.toSeq)(DefaultProtocol.listFormat)
|
||||
|
||||
def wrapIn[I, J](implicit f: I => J, jCache: InputCache[J]): InputCache[I] =
|
||||
new InputCache[I] {
|
||||
type Internal = jCache.Internal
|
||||
def convert(i: I) = jCache.convert(f(i))
|
||||
def read(from: Input) = jCache.read(from)
|
||||
def write(to: Out, j: Internal) = jCache.write(to, j)
|
||||
def equiv = jCache.equiv
|
||||
}
|
||||
|
||||
def singleton[T](t: T): InputCache[T] =
|
||||
basicInput(trueEquiv, asSingleton(t))
|
||||
|
||||
def trueEquiv[T] = new Equiv[T] { def equiv(a: T, b: T) = true }
|
||||
}
|
||||
|
||||
trait HListCacheImplicits {
|
||||
implicit def hConsCache[H, T <: HList](implicit head: InputCache[H], tail: InputCache[T]): InputCache[H :+: T] =
|
||||
new InputCache[H :+: T] {
|
||||
type Internal = (head.Internal, tail.Internal)
|
||||
def convert(in: H :+: T) = (head.convert(in.head), tail.convert(in.tail))
|
||||
def read(from: Input) =
|
||||
{
|
||||
val h = head.read(from)
|
||||
val t = tail.read(from)
|
||||
(h, t)
|
||||
}
|
||||
def write(to: Out, j: Internal): Unit = {
|
||||
head.write(to, j._1)
|
||||
tail.write(to, j._2)
|
||||
}
|
||||
def equiv = new Equiv[Internal] {
|
||||
def equiv(a: Internal, b: Internal) =
|
||||
head.equiv.equiv(a._1, b._1) &&
|
||||
tail.equiv.equiv(a._2, b._2)
|
||||
}
|
||||
}
|
||||
|
||||
implicit def hNilCache: InputCache[HNil] = Cache.singleton(HNil: HNil)
|
||||
|
||||
implicit def hConsFormat[H, T <: HList](implicit head: Format[H], tail: Format[T]): Format[H :+: T] = new Format[H :+: T] {
|
||||
def reads(from: Input) =
|
||||
{
|
||||
val h = head.reads(from)
|
||||
val t = tail.reads(from)
|
||||
HCons(h, t)
|
||||
}
|
||||
def writes(to: Out, hc: H :+: T): Unit = {
|
||||
head.writes(to, hc.head)
|
||||
tail.writes(to, hc.tail)
|
||||
}
|
||||
}
|
||||
|
||||
implicit def hNilFormat: Format[HNil] = asSingleton(HNil)
|
||||
}
|
||||
trait UnionImplicits {
|
||||
def unionInputCache[UB, HL <: HList](implicit uc: UnionCache[HL, UB]): InputCache[UB] =
|
||||
new InputCache[UB] {
|
||||
type Internal = Found[_]
|
||||
def convert(in: UB) = uc.find(in)
|
||||
def read(in: Input) =
|
||||
{
|
||||
val index = ByteFormat.reads(in).toInt
|
||||
val (cache, clazz) = uc.at(index)
|
||||
val value = cache.read(in)
|
||||
new Found[cache.Internal](cache, clazz, value, index)
|
||||
}
|
||||
def write(to: Out, i: Internal): Unit = {
|
||||
def write0[I](f: Found[I]): Unit = {
|
||||
ByteFormat.writes(to, f.index.toByte)
|
||||
f.cache.write(to, f.value)
|
||||
}
|
||||
write0(i)
|
||||
}
|
||||
def equiv: Equiv[Internal] = new Equiv[Internal] {
|
||||
def equiv(a: Internal, b: Internal): Boolean =
|
||||
{
|
||||
if (a.clazz == b.clazz)
|
||||
force(a.cache.equiv, a.value, b.value)
|
||||
else
|
||||
false
|
||||
}
|
||||
def force[T <: UB2, UB2](e: Equiv[T], a: UB2, b: UB2): Boolean = e.equiv(a.asInstanceOf[T], b.asInstanceOf[T])
|
||||
}
|
||||
}
|
||||
|
||||
implicit def unionCons[H <: UB, UB, T <: HList](implicit head: InputCache[H], mf: Manifest[H], t: UnionCache[T, UB]): UnionCache[H :+: T, UB] =
|
||||
new UnionCache[H :+: T, UB] {
|
||||
val size = 1 + t.size
|
||||
def c = mf.runtimeClass
|
||||
def find(value: UB): Found[_] =
|
||||
if (c.isInstance(value)) new Found[head.Internal](head, c, head.convert(value.asInstanceOf[H]), size - 1) else t.find(value)
|
||||
def at(i: Int): (InputCache[_ <: UB], Class[_]) = if (size == i + 1) (head, c) else t.at(i)
|
||||
}
|
||||
|
||||
implicit def unionNil[UB]: UnionCache[HNil, UB] = new UnionCache[HNil, UB] {
|
||||
def size = 0
|
||||
def find(value: UB) = sys.error("No valid sum type for " + value)
|
||||
def at(i: Int) = sys.error("Invalid union index " + i)
|
||||
}
|
||||
|
||||
final class Found[I](val cache: InputCache[_] { type Internal = I }, val clazz: Class[_], val value: I, val index: Int)
|
||||
sealed trait UnionCache[HL <: HList, UB] {
|
||||
def size: Int
|
||||
def at(i: Int): (InputCache[_ <: UB], Class[_])
|
||||
def find(forValue: UB): Found[_]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.File
|
||||
import sbinary.{ DefaultProtocol, Format, Operations }
|
||||
import scala.reflect.Manifest
|
||||
import sbt.io.IO
|
||||
|
||||
object CacheIO {
|
||||
def toBytes[T](format: Format[T])(value: T)(implicit mf: Manifest[Format[T]]): Array[Byte] =
|
||||
toBytes[T](value)(format, mf)
|
||||
def toBytes[T](value: T)(implicit format: Format[T], mf: Manifest[Format[T]]): Array[Byte] =
|
||||
Operations.toByteArray(value)(stampedFormat(format))
|
||||
def fromBytes[T](format: Format[T], default: => T)(bytes: Array[Byte])(implicit mf: Manifest[Format[T]]): T =
|
||||
fromBytes(default)(bytes)(format, mf)
|
||||
def fromBytes[T](default: => T)(bytes: Array[Byte])(implicit format: Format[T], mf: Manifest[Format[T]]): T =
|
||||
if (bytes.isEmpty) default else Operations.fromByteArray(bytes)(stampedFormat(format))
|
||||
|
||||
def fromFile[T](format: Format[T], default: => T)(file: File)(implicit mf: Manifest[Format[T]]): T =
|
||||
fromFile(file, default)(format, mf)
|
||||
def fromFile[T](file: File, default: => T)(implicit format: Format[T], mf: Manifest[Format[T]]): T =
|
||||
fromFile[T](file) getOrElse default
|
||||
def fromFile[T](file: File)(implicit format: Format[T], mf: Manifest[Format[T]]): Option[T] =
|
||||
try { Some(Operations.fromFile(file)(stampedFormat(format))) }
|
||||
catch { case e: Exception => None }
|
||||
|
||||
def toFile[T](format: Format[T])(value: T)(file: File)(implicit mf: Manifest[Format[T]]): Unit =
|
||||
toFile(value)(file)(format, mf)
|
||||
def toFile[T](value: T)(file: File)(implicit format: Format[T], mf: Manifest[Format[T]]): Unit =
|
||||
{
|
||||
IO.createDirectory(file.getParentFile)
|
||||
Operations.toFile(value)(file)(stampedFormat(format))
|
||||
}
|
||||
def stampedFormat[T](format: Format[T])(implicit mf: Manifest[Format[T]]): Format[T] =
|
||||
{
|
||||
import DefaultProtocol._
|
||||
withStamp(stamp(format))(format)
|
||||
}
|
||||
def stamp[T](format: Format[T])(implicit mf: Manifest[Format[T]]): Int = typeHash(mf)
|
||||
def typeHash[T](implicit mf: Manifest[T]) = mf.toString.hashCode
|
||||
def manifest[T](implicit mf: Manifest[T]): Manifest[T] = mf
|
||||
def objManifest[T](t: T)(implicit mf: Manifest[T]): Manifest[T] = mf
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.datatype.{ ArrayFormat, BooleanFormat, ByteFormat, IntFormat, LongFormat, StringFormat }
|
||||
import sjsonnew.{ CollectionFormats, TupleFormats }
|
||||
|
||||
object CacheImplicits extends BasicCacheImplicits
|
||||
with ArrayFormat
|
||||
with BooleanFormat
|
||||
with ByteFormat
|
||||
with FileFormat
|
||||
with IntFormat
|
||||
with LongFormat
|
||||
with StringFormat
|
||||
with URIFormat
|
||||
with URLFormat
|
||||
with TupleFormats
|
||||
with CollectionFormats
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sjsonnew.{ IsoString, JsonReader, JsonWriter, SupportConverter }
|
||||
|
||||
import java.io.{ File, InputStream, OutputStream }
|
||||
|
||||
import sbt.io.{ IO, Using }
|
||||
import sbt.io.syntax.fileToRichFile
|
||||
|
||||
/**
|
||||
* A `CacheStore` is used by the caching infrastructure to persist cached information.
|
||||
*/
|
||||
trait CacheStore extends Input with Output {
|
||||
/** Delete the persisted information. */
|
||||
def delete(): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory that can derive new stores.
|
||||
*/
|
||||
trait CacheStoreFactory {
|
||||
/** Create a new store. */
|
||||
def derive(identifier: String): CacheStore
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory that creates new stores persisted in `base`.
|
||||
*/
|
||||
class DirectoryStoreFactory[J: IsoString](base: File, converter: SupportConverter[J]) extends CacheStoreFactory {
|
||||
|
||||
IO.createDirectory(base)
|
||||
|
||||
override def derive(identifier: String): CacheStore =
|
||||
new FileBasedStore(base / identifier, converter)
|
||||
}
|
||||
|
||||
/**
|
||||
* A `CacheStore` that persists information in `file`.
|
||||
*/
|
||||
class FileBasedStore[J: IsoString](file: File, converter: SupportConverter[J]) extends CacheStore {
|
||||
|
||||
IO.touch(file, setModified = false)
|
||||
|
||||
override def delete(): Unit =
|
||||
IO.delete(file)
|
||||
|
||||
override def read[T: JsonReader](): T =
|
||||
Using.fileInputStream(file) { stream =>
|
||||
val input = new PlainInput(stream, converter)
|
||||
input.read()
|
||||
}
|
||||
|
||||
override def read[T: JsonReader](default: => T): T =
|
||||
try read[T]()
|
||||
catch { case _: Exception => default }
|
||||
|
||||
override def write[T: JsonWriter](value: T): Unit =
|
||||
Using.fileOutputStream(append = false)(file) { stream =>
|
||||
val output = new PlainOutput(stream, converter)
|
||||
output.write(value)
|
||||
}
|
||||
|
||||
override def close(): Unit = ()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A store that reads from `inputStream` and writes to `outputStream
|
||||
*/
|
||||
class StreamBasedStore[J: IsoString](inputStream: InputStream, outputStream: OutputStream, converter: SupportConverter[J]) extends CacheStore {
|
||||
|
||||
override def delete(): Unit = ()
|
||||
|
||||
override def read[T: JsonReader](): T = {
|
||||
val input = new PlainInput(inputStream, converter)
|
||||
input.read()
|
||||
}
|
||||
|
||||
override def read[T: JsonReader](default: => T): T =
|
||||
try read[T]()
|
||||
catch { case _: Exception => default }
|
||||
|
||||
override def write[T: JsonWriter](value: T): Unit = {
|
||||
val output = new PlainOutput(outputStream, converter)
|
||||
output.write(value)
|
||||
}
|
||||
|
||||
override def close(): Unit = {
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,135 +3,172 @@
|
|||
*/
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.File
|
||||
import sbinary.{ DefaultProtocol, Format }
|
||||
import DefaultProtocol._
|
||||
import scala.reflect.Manifest
|
||||
import sbt.io.Hash
|
||||
import sbt.serialization._
|
||||
|
||||
import java.io.File
|
||||
import sjsonnew.{ Builder, deserializationError, JsonFormat, Unbuilder }
|
||||
import CacheImplicits._
|
||||
|
||||
sealed trait FileInfo {
|
||||
val file: File
|
||||
def file: File
|
||||
}
|
||||
@directSubclasses(Array(classOf[FileHash], classOf[HashModifiedFileInfo]))
|
||||
|
||||
sealed trait HashFileInfo extends FileInfo {
|
||||
val hash: List[Byte]
|
||||
def hash: List[Byte]
|
||||
}
|
||||
object HashFileInfo {
|
||||
implicit val pickler: Pickler[HashFileInfo] with Unpickler[HashFileInfo] = PicklerUnpickler.generate[HashFileInfo]
|
||||
}
|
||||
@directSubclasses(Array(classOf[FileModified], classOf[HashModifiedFileInfo]))
|
||||
|
||||
sealed trait ModifiedFileInfo extends FileInfo {
|
||||
val lastModified: Long
|
||||
def lastModified: Long
|
||||
}
|
||||
object ModifiedFileInfo {
|
||||
implicit val pickler: Pickler[ModifiedFileInfo] with Unpickler[ModifiedFileInfo] = PicklerUnpickler.generate[ModifiedFileInfo]
|
||||
}
|
||||
@directSubclasses(Array(classOf[PlainFile]))
|
||||
|
||||
sealed trait PlainFileInfo extends FileInfo {
|
||||
def exists: Boolean
|
||||
}
|
||||
object PlainFileInfo {
|
||||
implicit val pickler: Pickler[PlainFileInfo] with Unpickler[PlainFileInfo] = PicklerUnpickler.generate[PlainFileInfo]
|
||||
}
|
||||
@directSubclasses(Array(classOf[FileHashModified]))
|
||||
sealed trait HashModifiedFileInfo extends HashFileInfo with ModifiedFileInfo
|
||||
object HashModifiedFileInfo {
|
||||
implicit val pickler: Pickler[HashModifiedFileInfo] with Unpickler[HashModifiedFileInfo] = PicklerUnpickler.generate[HashModifiedFileInfo]
|
||||
}
|
||||
|
||||
private[sbt] final case class PlainFile(file: File, exists: Boolean) extends PlainFileInfo
|
||||
private[sbt] object PlainFile {
|
||||
implicit val pickler: Pickler[PlainFile] with Unpickler[PlainFile] = PicklerUnpickler.generate[PlainFile]
|
||||
}
|
||||
private[sbt] final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo
|
||||
private[sbt] object FileHash {
|
||||
implicit val pickler: Pickler[FileHash] with Unpickler[FileHash] = PicklerUnpickler.generate[FileHash]
|
||||
}
|
||||
private[sbt] final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
|
||||
private[sbt] object FileModified {
|
||||
implicit val pickler: Pickler[FileModified] with Unpickler[FileModified] = PicklerUnpickler.generate[FileModified]
|
||||
}
|
||||
private[sbt] final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo
|
||||
private[sbt] object FileHashModified {
|
||||
implicit val pickler: Pickler[FileHashModified] with Unpickler[FileHashModified] = PicklerUnpickler.generate[FileHashModified]
|
||||
}
|
||||
sealed trait HashModifiedFileInfo extends HashFileInfo with ModifiedFileInfo
|
||||
private final case class PlainFile(file: File, exists: Boolean) extends PlainFileInfo
|
||||
private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
|
||||
private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo
|
||||
private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo
|
||||
|
||||
object FileInfo {
|
||||
implicit def existsInputCache: InputCache[PlainFileInfo] = exists.infoInputCache
|
||||
implicit def modifiedInputCache: InputCache[ModifiedFileInfo] = lastModified.infoInputCache
|
||||
implicit def hashInputCache: InputCache[HashFileInfo] = hash.infoInputCache
|
||||
implicit def fullInputCache: InputCache[HashModifiedFileInfo] = full.infoInputCache
|
||||
implicit val pickler: Pickler[FileInfo] with Unpickler[FileInfo] = PicklerUnpickler.generate[FileInfo]
|
||||
|
||||
sealed trait Style {
|
||||
type F <: FileInfo
|
||||
implicit def apply(file: File): F
|
||||
implicit def unapply(info: F): File = info.file
|
||||
implicit val format: Format[F]
|
||||
import Cache._
|
||||
implicit def fileInfoEquiv: Equiv[F] = defaultEquiv
|
||||
def infoInputCache: InputCache[F] = basicInput
|
||||
implicit def fileInputCache: InputCache[File] = wrapIn[File, F]
|
||||
implicit val format: JsonFormat[F]
|
||||
|
||||
def apply(file: File): F
|
||||
|
||||
def apply(files: Set[File]): FilesInfo[F] = FilesInfo(files map apply)
|
||||
|
||||
def unapply(info: F): File = info.file
|
||||
|
||||
def unapply(infos: FilesInfo[F]): Set[File] = infos.files map (_.file)
|
||||
}
|
||||
|
||||
object full extends Style {
|
||||
type F = HashModifiedFileInfo
|
||||
implicit def apply(file: File): HashModifiedFileInfo = make(file, Hash(file).toList, file.lastModified)
|
||||
def make(file: File, hash: List[Byte], lastModified: Long): HashModifiedFileInfo = FileHashModified(file.getAbsoluteFile, hash, lastModified)
|
||||
implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), (make _).tupled)
|
||||
override type F = HashModifiedFileInfo
|
||||
|
||||
override implicit val format: JsonFormat[HashModifiedFileInfo] = new JsonFormat[HashModifiedFileInfo] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): HashModifiedFileInfo =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val file = unbuilder.readField[File]("file")
|
||||
val hash = unbuilder.readField[List[Byte]]("hash")
|
||||
val lastModified = unbuilder.readField[Long]("lastModified")
|
||||
unbuilder.endObject()
|
||||
FileHashModified(file, hash, lastModified)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
|
||||
override def write[J](obj: HashModifiedFileInfo, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("file", obj.file)
|
||||
builder.addField("hash", obj.hash)
|
||||
builder.addField("lastModified", obj.lastModified)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
override implicit def apply(file: File): HashModifiedFileInfo =
|
||||
FileHashModified(file.getAbsoluteFile, Hash(file).toList, file.lastModified)
|
||||
}
|
||||
|
||||
object hash extends Style {
|
||||
type F = HashFileInfo
|
||||
implicit def apply(file: File): HashFileInfo = make(file, computeHash(file))
|
||||
def make(file: File, hash: List[Byte]): HashFileInfo = FileHash(file.getAbsoluteFile, hash)
|
||||
implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), (make _).tupled)
|
||||
private def computeHash(file: File): List[Byte] = try { Hash(file).toList } catch { case e: Exception => Nil }
|
||||
override type F = HashFileInfo
|
||||
|
||||
override implicit val format: JsonFormat[HashFileInfo] = new JsonFormat[HashFileInfo] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): HashFileInfo =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val file = unbuilder.readField[File]("file")
|
||||
val hash = unbuilder.readField[List[Byte]]("hash")
|
||||
unbuilder.endObject()
|
||||
FileHash(file, hash)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
|
||||
override def write[J](obj: HashFileInfo, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("file", obj.file)
|
||||
builder.addField("hash", obj.hash)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
override implicit def apply(file: File): HashFileInfo =
|
||||
FileHash(file.getAbsoluteFile, computeHash(file))
|
||||
|
||||
private def computeHash(file: File): List[Byte] =
|
||||
try Hash(file).toList
|
||||
catch { case _: Exception => Nil }
|
||||
}
|
||||
|
||||
object lastModified extends Style {
|
||||
type F = ModifiedFileInfo
|
||||
implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified)
|
||||
def make(file: File, lastModified: Long): ModifiedFileInfo = FileModified(file.getAbsoluteFile, lastModified)
|
||||
implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), (make _).tupled)
|
||||
override type F = ModifiedFileInfo
|
||||
|
||||
override implicit val format: JsonFormat[ModifiedFileInfo] = new JsonFormat[ModifiedFileInfo] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): ModifiedFileInfo =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val file = unbuilder.readField[File]("file")
|
||||
val lastModified = unbuilder.readField[Long]("lastModified")
|
||||
unbuilder.endObject()
|
||||
FileModified(file, lastModified)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
|
||||
override def write[J](obj: ModifiedFileInfo, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("file", obj.file)
|
||||
builder.addField("lastModified", obj.lastModified)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
override implicit def apply(file: File): ModifiedFileInfo =
|
||||
FileModified(file.getAbsoluteFile, file.lastModified)
|
||||
}
|
||||
|
||||
object exists extends Style {
|
||||
type F = PlainFileInfo
|
||||
implicit def apply(file: File): PlainFileInfo = make(file)
|
||||
def make(file: File): PlainFileInfo = { val abs = file.getAbsoluteFile; PlainFile(abs, abs.exists) }
|
||||
implicit val format: Format[PlainFileInfo] = asProduct2[PlainFileInfo, File, Boolean](PlainFile.apply)(x => (x.file, x.exists))
|
||||
override type F = PlainFileInfo
|
||||
|
||||
override implicit val format: JsonFormat[PlainFileInfo] = new JsonFormat[PlainFileInfo] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): PlainFileInfo =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val file = unbuilder.readField[File]("file")
|
||||
val exists = unbuilder.readField[Boolean]("exists")
|
||||
unbuilder.endObject()
|
||||
PlainFile(file, exists)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
|
||||
override def write[J](obj: PlainFileInfo, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("file", obj.file)
|
||||
builder.addField("exists", obj.exists)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
override implicit def apply(file: File): PlainFileInfo = {
|
||||
val abs = file.getAbsoluteFile
|
||||
PlainFile(abs, abs.exists)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final case class FilesInfo[F <: FileInfo] private (files: Set[F])
|
||||
object FilesInfo {
|
||||
sealed abstract class Style {
|
||||
type F <: FileInfo
|
||||
val fileStyle: FileInfo.Style { type F = Style.this.F }
|
||||
|
||||
//def manifest: Manifest[F] = fileStyle.manifest
|
||||
implicit def apply(files: Set[File]): FilesInfo[F]
|
||||
implicit def unapply(info: FilesInfo[F]): Set[File] = info.files.map(_.file)
|
||||
implicit val formats: Format[FilesInfo[F]]
|
||||
val manifest: Manifest[Format[FilesInfo[F]]]
|
||||
def empty: FilesInfo[F] = new FilesInfo[F](Set.empty)
|
||||
import Cache._
|
||||
def infosInputCache: InputCache[FilesInfo[F]] = basicInput
|
||||
implicit def filesInputCache: InputCache[Set[File]] = wrapIn[Set[File], FilesInfo[F]]
|
||||
implicit def filesInfoEquiv: Equiv[FilesInfo[F]] = defaultEquiv
|
||||
}
|
||||
private final class BasicStyle[FI <: FileInfo](style: FileInfo.Style { type F = FI })(implicit val manifest: Manifest[Format[FilesInfo[FI]]]) extends Style {
|
||||
type F = FI
|
||||
val fileStyle: FileInfo.Style { type F = FI } = style
|
||||
private implicit val infoFormat: Format[FI] = fileStyle.format
|
||||
implicit def apply(files: Set[File]): FilesInfo[F] = FilesInfo(files.map(_.getAbsoluteFile).map(fileStyle.apply))
|
||||
implicit val formats: Format[FilesInfo[F]] = wrap(_.files, (fs: Set[F]) => new FilesInfo(fs))
|
||||
}
|
||||
lazy val full: Style { type F = HashModifiedFileInfo } = new BasicStyle(FileInfo.full)
|
||||
lazy val hash: Style { type F = HashFileInfo } = new BasicStyle(FileInfo.hash)
|
||||
lazy val lastModified: Style { type F = ModifiedFileInfo } = new BasicStyle(FileInfo.lastModified)
|
||||
lazy val exists: Style { type F = PlainFileInfo } = new BasicStyle(FileInfo.exists)
|
||||
|
||||
implicit def existsInputsCache: InputCache[FilesInfo[PlainFileInfo]] = exists.infosInputCache
|
||||
implicit def hashInputsCache: InputCache[FilesInfo[HashFileInfo]] = hash.infosInputCache
|
||||
implicit def modifiedInputsCache: InputCache[FilesInfo[ModifiedFileInfo]] = lastModified.infosInputCache
|
||||
implicit def fullInputsCache: InputCache[FilesInfo[HashModifiedFileInfo]] = full.infosInputCache
|
||||
implicit def format[F <: FileInfo]: JsonFormat[FilesInfo[F]] = implicitly
|
||||
def empty[F <: FileInfo]: FilesInfo[F] = FilesInfo(Set.empty[F])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.io.{ IO, Using }
|
||||
|
||||
import java.io.{ Closeable, InputStream }
|
||||
|
||||
import scala.util.{ Failure, Success }
|
||||
|
||||
import sjsonnew.{ IsoString, JsonReader, SupportConverter }
|
||||
|
||||
trait Input extends Closeable {
|
||||
def read[T: JsonReader](): T
|
||||
def read[T: JsonReader](default: => T): T
|
||||
}
|
||||
|
||||
class PlainInput[J: IsoString](input: InputStream, converter: SupportConverter[J]) extends Input {
|
||||
val isoFormat: IsoString[J] = implicitly
|
||||
private def readFully(): String = {
|
||||
Using.streamReader(input, IO.utf8) { reader =>
|
||||
val builder = new StringBuilder()
|
||||
val bufferSize = 1024
|
||||
val buffer = new Array[Char](bufferSize)
|
||||
var read = 0
|
||||
while ({ read = reader.read(buffer, 0, bufferSize); read != -1 }) {
|
||||
builder.append(String.valueOf(buffer.take(read)))
|
||||
}
|
||||
builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
override def read[T: JsonReader](): T = {
|
||||
val string = readFully()
|
||||
val json = isoFormat.from(string)
|
||||
converter.fromJson(json) match {
|
||||
case Success(value) => value
|
||||
case Failure(ex) => throw ex
|
||||
}
|
||||
}
|
||||
|
||||
override def read[T: JsonReader](default: => T): T =
|
||||
try read[T]()
|
||||
catch { case _: Exception => default }
|
||||
|
||||
override def close(): Unit = input.close()
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.io.Using
|
||||
|
||||
import java.io.{ Closeable, OutputStream }
|
||||
|
||||
import scala.util.{ Failure, Success }
|
||||
|
||||
import sjsonnew.{ IsoString, JsonWriter, SupportConverter }
|
||||
|
||||
trait Output extends Closeable {
|
||||
def write[T: JsonWriter](value: T): Unit
|
||||
}
|
||||
|
||||
class PlainOutput[J: IsoString](output: OutputStream, converter: SupportConverter[J]) extends Output {
|
||||
val isoFormat: IsoString[J] = implicitly
|
||||
override def write[T: JsonWriter](value: T): Unit = {
|
||||
converter.toJson(value) match {
|
||||
case Success(js) =>
|
||||
val asString = isoFormat.to(js)
|
||||
Using.bufferedOutputStream(output) { writer =>
|
||||
val out = new java.io.PrintWriter(writer)
|
||||
out.print(asString)
|
||||
out.flush()
|
||||
}
|
||||
case Failure(ex) =>
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
override def close(): Unit = output.close()
|
||||
}
|
||||
|
|
@ -3,59 +3,61 @@
|
|||
*/
|
||||
package sbt.internal.util
|
||||
|
||||
import sbinary.{ Format, Input, Output => Out }
|
||||
import java.io.File
|
||||
import sbt.io.Using
|
||||
import scala.util.Try
|
||||
|
||||
trait InputCache[I] {
|
||||
type Internal
|
||||
def convert(i: I): Internal
|
||||
def read(from: Input): Internal
|
||||
def write(to: Out, j: Internal): Unit
|
||||
def equiv: Equiv[Internal]
|
||||
import sjsonnew.JsonFormat
|
||||
|
||||
import CacheImplicits._
|
||||
|
||||
/**
|
||||
* A cache that stores a single value.
|
||||
*/
|
||||
trait SingletonCache[T] {
|
||||
/** Reads the cache from the backing `from`. */
|
||||
def read(from: Input): T
|
||||
|
||||
/** Writes `value` to the backing `to`. */
|
||||
def write(to: Output, value: T): Unit
|
||||
|
||||
/** Equivalence for elements of type `T`. */
|
||||
def equiv: Equiv[T]
|
||||
}
|
||||
object InputCache {
|
||||
implicit def basicInputCache[I](implicit fmt: Format[I], eqv: Equiv[I]): InputCache[I] =
|
||||
new InputCache[I] {
|
||||
type Internal = I
|
||||
def convert(i: I) = i
|
||||
def read(from: Input): I = fmt.reads(from)
|
||||
def write(to: Out, i: I) = fmt.writes(to, i)
|
||||
def equiv = eqv
|
||||
|
||||
object SingletonCache {
|
||||
|
||||
implicit def basicSingletonCache[T: JsonFormat: Equiv]: SingletonCache[T] =
|
||||
new SingletonCache[T] {
|
||||
override def read(from: Input): T = from.read[T]
|
||||
override def write(to: Output, value: T) = to.write(value)
|
||||
override def equiv: Equiv[T] = implicitly
|
||||
}
|
||||
def lzy[I](mkIn: => InputCache[I]): InputCache[I] =
|
||||
new InputCache[I] {
|
||||
lazy val ic = mkIn
|
||||
type Internal = ic.Internal
|
||||
def convert(i: I) = ic convert i
|
||||
def read(from: Input): ic.Internal = ic.read(from)
|
||||
def write(to: Out, i: ic.Internal) = ic.write(to, i)
|
||||
def equiv = ic.equiv
|
||||
|
||||
/** A lazy `SingletonCache` */
|
||||
def lzy[T: JsonFormat: Equiv](mkCache: => SingletonCache[T]): SingletonCache[T] =
|
||||
new SingletonCache[T] {
|
||||
lazy val cache = mkCache
|
||||
override def read(from: Input): T = cache.read(from)
|
||||
override def write(to: Output, value: T) = cache.write(to, value)
|
||||
override def equiv = cache.equiv
|
||||
}
|
||||
}
|
||||
|
||||
class BasicCache[I, O](implicit input: InputCache[I], outFormat: Format[O]) extends Cache[I, O] {
|
||||
def apply(file: File)(in: I) =
|
||||
{
|
||||
val j = input.convert(in)
|
||||
try { applyImpl(file, j) }
|
||||
catch { case e: Exception => Right(update(file)(j)) }
|
||||
}
|
||||
protected def applyImpl(file: File, in: input.Internal) =
|
||||
{
|
||||
Using.fileInputStream(file) { stream =>
|
||||
val previousIn = input.read(stream)
|
||||
if (input.equiv.equiv(in, previousIn))
|
||||
Left(outFormat.reads(stream))
|
||||
else
|
||||
Right(update(file)(in))
|
||||
}
|
||||
}
|
||||
protected def update(file: File)(in: input.Internal) = (out: O) =>
|
||||
{
|
||||
Using.fileOutputStream(false)(file) { stream =>
|
||||
input.write(stream, in)
|
||||
outFormat.writes(stream, out)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Simple key-value cache.
|
||||
*/
|
||||
class BasicCache[I: JsonFormat: Equiv, O: JsonFormat] extends Cache[I, O] {
|
||||
private val singletonCache: SingletonCache[(I, O)] = implicitly
|
||||
val equiv: Equiv[I] = implicitly
|
||||
override def apply(store: CacheStore)(key: I): CacheResult[O] =
|
||||
Try {
|
||||
val (previousKey, previousValue) = singletonCache.read(store)
|
||||
if (equiv.equiv(key, previousKey))
|
||||
Hit(previousValue)
|
||||
else
|
||||
Miss(update(store)(key))
|
||||
} getOrElse Miss(update(store)(key))
|
||||
|
||||
private def update(store: CacheStore)(key: I) = (value: O) => {
|
||||
singletonCache.write(store, (key, value))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import scala.reflect.Manifest
|
||||
|
||||
import sbt.datatype.IntFormat
|
||||
|
||||
import sjsonnew.{ Builder, deserializationError, JsonFormat, Unbuilder }
|
||||
|
||||
object StampedFormat extends IntFormat {
|
||||
|
||||
def apply[T](format: JsonFormat[T])(implicit mf: Manifest[JsonFormat[T]]): JsonFormat[T] = {
|
||||
withStamp(stamp(format))(format)
|
||||
}
|
||||
|
||||
def withStamp[T, S](stamp: S)(format: JsonFormat[T])(implicit formatStamp: JsonFormat[S], equivStamp: Equiv[S]): JsonFormat[T] =
|
||||
new JsonFormat[T] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T =
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.extractArray(js) match {
|
||||
case Vector(readStamp, readValue) =>
|
||||
val actualStamp = formatStamp.read(Some(readStamp), unbuilder)
|
||||
if (equivStamp.equiv(actualStamp, stamp)) format.read(Some(readValue), unbuilder)
|
||||
else sys.error(s"Incorrect stamp. Expected: $stamp, Found: $readStamp")
|
||||
|
||||
case other =>
|
||||
deserializationError(s"Expected JsArray of size 2, but found JsArray of size ${other.size}")
|
||||
}
|
||||
|
||||
case None =>
|
||||
deserializationError("Expected JsArray but found None.")
|
||||
}
|
||||
|
||||
override def write[J](obj: T, builder: Builder[J]): Unit = {
|
||||
builder.beginArray()
|
||||
formatStamp.write(stamp, builder)
|
||||
format.write(obj, builder)
|
||||
builder.endArray()
|
||||
}
|
||||
}
|
||||
private def stamp[T](format: JsonFormat[T])(implicit mf: Manifest[JsonFormat[T]]): Int = typeHash(mf)
|
||||
private def typeHash[T](implicit mf: Manifest[T]) = mf.toString.hashCode
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
|
||||
import CacheImplicits._
|
||||
|
||||
import sjsonnew.{ Builder, deserializationError, IsoString, JsonFormat, Unbuilder }
|
||||
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, FixedParser }
|
||||
|
||||
import scala.json.ast.unsafe.JValue
|
||||
|
||||
class CacheSpec extends UnitSpec {
|
||||
|
||||
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, FixedParser.parseUnsafe)
|
||||
|
||||
"A cache" should "NOT throw an exception if read without being written previously" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
cache(store)("missing") match {
|
||||
case Hit(_) => fail
|
||||
case Miss(_) => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "write a very simple value" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
cache(store)("missing") match {
|
||||
case Hit(_) => fail
|
||||
case Miss(update) => update(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be updatable" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
val value = 5
|
||||
cache(store)("someKey") match {
|
||||
case Hit(_) => fail
|
||||
case Miss(update) => update(value)
|
||||
}
|
||||
|
||||
cache(store)("someKey") match {
|
||||
case Hit(read) => assert(read === value)
|
||||
case Miss(_) => fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "return the value that has been previously written" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
val key = "someKey"
|
||||
val value = 5
|
||||
cache(store)(key) match {
|
||||
case Hit(_) => fail
|
||||
case Miss(update) => update(value)
|
||||
}
|
||||
|
||||
cache(store)(key) match {
|
||||
case Hit(read) => assert(read === value)
|
||||
case Miss(_) => fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(implicit cache: Cache[K, V]): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store", Converter)
|
||||
f(cache, store)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import java.io.File
|
||||
import Types.:+:
|
||||
|
||||
object CacheTest // extends Properties("Cache test")
|
||||
{
|
||||
val lengthCache = new File("/tmp/length-cache")
|
||||
val cCache = new File("/tmp/c-cache")
|
||||
|
||||
import Cache._
|
||||
import FileInfo.hash._
|
||||
import Ordering._
|
||||
import sbinary.DefaultProtocol.FileFormat
|
||||
def test(): Unit = {
|
||||
lazy val create = new File("test")
|
||||
|
||||
val length = cached(lengthCache) {
|
||||
(f: File) => { println("File length: " + f.length); f.length }
|
||||
}
|
||||
|
||||
lazy val fileLength = length(create)
|
||||
|
||||
val c = cached(cCache) { (in: (File :+: Long :+: HNil)) =>
|
||||
val file :+: len :+: HNil = in
|
||||
println("File: " + file + " (" + file.exists + "), length: " + len)
|
||||
(len + 1) :+: file :+: HNil
|
||||
}
|
||||
c(create :+: fileLength :+: HNil)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
|
||||
import CacheImplicits._
|
||||
|
||||
import sjsonnew.{ Builder, deserializationError, IsoString, JsonFormat, Unbuilder }
|
||||
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, FixedParser }
|
||||
|
||||
import scala.json.ast.unsafe.JValue
|
||||
|
||||
class SingletonCacheSpec extends UnitSpec {
|
||||
|
||||
case class ComplexType(val x: Int, y: String, z: List[Int])
|
||||
object ComplexType {
|
||||
implicit val format: JsonFormat[ComplexType] =
|
||||
new JsonFormat[ComplexType] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): ComplexType = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val x = unbuilder.readField[Int]("x")
|
||||
val y = unbuilder.readField[String]("y")
|
||||
val z = unbuilder.readField[List[Int]]("z")
|
||||
unbuilder.endObject()
|
||||
ComplexType(x, y, z)
|
||||
|
||||
case None =>
|
||||
deserializationError("Exception JObject but found None")
|
||||
}
|
||||
}
|
||||
|
||||
override def write[J](obj: ComplexType, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("x", obj.x)
|
||||
builder.addField("y", obj.y)
|
||||
builder.addField("z", obj.z)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, FixedParser.parseUnsafe)
|
||||
|
||||
"A singleton cache" should "throw an exception if read without being written previously" in {
|
||||
testCache[Int] {
|
||||
case (cache, store) =>
|
||||
intercept[Exception] {
|
||||
cache.read(store)
|
||||
}
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
it should "write a very simple value" in {
|
||||
testCache[Int] {
|
||||
case (cache, store) =>
|
||||
cache.write(store, 5)
|
||||
}
|
||||
}
|
||||
|
||||
it should "return the simple value that has been previously written" in {
|
||||
testCache[Int] {
|
||||
case (cache, store) =>
|
||||
val value = 5
|
||||
cache.write(store, value)
|
||||
val read = cache.read(store)
|
||||
|
||||
assert(read === value)
|
||||
}
|
||||
}
|
||||
|
||||
it should "write a complex value" in {
|
||||
testCache[ComplexType] {
|
||||
case (cache, store) =>
|
||||
val value = ComplexType(1, "hello, world!", (1 to 10 by 3).toList)
|
||||
cache.write(store, value)
|
||||
val read = cache.read(store)
|
||||
|
||||
assert(read === value)
|
||||
}
|
||||
}
|
||||
|
||||
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(implicit cache: SingletonCache[T]): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store", Converter)
|
||||
f(cache, store)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package sjsonnew
|
||||
package support.scalajson.unsafe
|
||||
|
||||
import scala.json.ast.unsafe._
|
||||
import scala.collection.mutable
|
||||
import jawn.{ SupportParser, MutableFacade }
|
||||
|
||||
object FixedParser extends SupportParser[JValue] {
|
||||
implicit val facade: MutableFacade[JValue] =
|
||||
new MutableFacade[JValue] {
|
||||
def jnull() = JNull
|
||||
def jfalse() = JTrue
|
||||
def jtrue() = JFalse
|
||||
def jnum(s: String) = JNumber(s)
|
||||
def jint(s: String) = JNumber(s)
|
||||
def jstring(s: String) = JString(s)
|
||||
def jarray(vs: mutable.ArrayBuffer[JValue]) = JArray(vs.toArray)
|
||||
def jobject(vs: mutable.Map[String, JValue]) = {
|
||||
val array = new Array[JField](vs.size)
|
||||
var i = 0
|
||||
vs.foreach {
|
||||
case (key, value) =>
|
||||
array(i) = JField(key, value)
|
||||
i += 1
|
||||
}
|
||||
JObject(array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,133 +3,76 @@
|
|||
*/
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.{ File, IOException }
|
||||
import CacheIO.{ fromFile, toFile }
|
||||
import sbinary.Format
|
||||
import scala.pickling.PicklingException
|
||||
import scala.reflect.Manifest
|
||||
import sbt.io.IO.{ delete, read, write }
|
||||
import scala.util.{ Failure, Try, Success }
|
||||
|
||||
import java.io.File
|
||||
import sbt.io.IO
|
||||
import sbt.io.Using
|
||||
import sbt.io.syntax._
|
||||
import sbt.serialization._
|
||||
|
||||
import sjsonnew.JsonFormat
|
||||
|
||||
object Tracked {
|
||||
|
||||
import CacheImplicits.LongFormat
|
||||
|
||||
/**
|
||||
* Creates a tracker that provides the last time it was evaluated.
|
||||
* If 'useStartTime' is true, the recorded time is the start of the evaluated function.
|
||||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||
* In both cases, the timestamp is not updated if the function throws an exception.
|
||||
*/
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean = true): Timestamp = new Timestamp(cacheFile, useStartTime)
|
||||
/** Creates a tracker that only evaluates a function when the input has changed.*/
|
||||
//def changed[O](cacheFile: File)(implicit format: Format[O], equiv: Equiv[O]): Changed[O] =
|
||||
// new Changed[O](cacheFile)
|
||||
def tstamp(store: CacheStore, useStartTime: Boolean = true): Timestamp = new Timestamp(store, useStartTime)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
|
||||
def diffInputs(cache: File, style: FilesInfo.Style): Difference =
|
||||
Difference.inputs(cache, style)
|
||||
def diffInputs(store: CacheStore, style: FileInfo.Style): Difference =
|
||||
Difference.inputs(store, style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
|
||||
def diffOutputs(cache: File, style: FilesInfo.Style): Difference =
|
||||
Difference.outputs(cache, style)
|
||||
def diffOutputs(store: CacheStore, style: FileInfo.Style): Difference =
|
||||
Difference.outputs(store, style)
|
||||
|
||||
def lastOutput[I, O](cacheFile: File)(f: (I, Option[O]) => O)(implicit o: Format[O], mf: Manifest[Format[O]]): I => O = in =>
|
||||
{
|
||||
val previous: Option[O] = fromFile[O](cacheFile)
|
||||
val next = f(in, previous)
|
||||
toFile(next)(cacheFile)
|
||||
next
|
||||
/** Creates a tracker that provides the output of the most recent invocation of the function */
|
||||
def lastOutput[I, O: JsonFormat](store: CacheStore)(f: (I, Option[O]) => O): I => O = { in =>
|
||||
val previous = Try { store.read[O] }.toOption
|
||||
val next = f(in, previous)
|
||||
store.write(next)
|
||||
next
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tracker that indicates whether the arguments given to f have changed since the most
|
||||
* recent invocation.
|
||||
*/
|
||||
def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(f: (Boolean, I) => O): I => O = { in =>
|
||||
val cache: SingletonCache[I] = implicitly
|
||||
val help = new CacheHelp(cache)
|
||||
val changed = help.changed(store, in)
|
||||
val result = f(changed, in)
|
||||
if (changed)
|
||||
help.save(store, in)
|
||||
result
|
||||
}
|
||||
|
||||
private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[I]) {
|
||||
def save(store: CacheStore, value: I): Unit = {
|
||||
store.write(value)
|
||||
}
|
||||
// Todo: This function needs more testing.
|
||||
private[sbt] def lastOutputWithJson[I, O: Pickler: Unpickler](cacheFile: File)(f: (I, Option[O]) => O): I => O = in =>
|
||||
{
|
||||
val previous: Option[O] = try {
|
||||
fromJsonFile[O](cacheFile).toOption
|
||||
} catch {
|
||||
case e: PicklingException => None
|
||||
case e: IOException => None
|
||||
|
||||
def changed(store: CacheStore, value: I): Boolean =
|
||||
Try { store.read[I] } match {
|
||||
case Success(prev) => !sc.equiv.equiv(value, prev)
|
||||
case Failure(_) => true
|
||||
}
|
||||
val next = f(in, previous)
|
||||
IO.createDirectory(cacheFile.getParentFile)
|
||||
toJsonFile(next, cacheFile)
|
||||
next
|
||||
}
|
||||
def inputChanged[I, O](cacheFile: File)(f: (Boolean, I) => O)(implicit ic: InputCache[I]): I => O = in =>
|
||||
{
|
||||
val help = new CacheHelp(ic)
|
||||
val conv = help.convert(in)
|
||||
val changed = help.changed(cacheFile, conv)
|
||||
val result = f(changed, in)
|
||||
|
||||
if (changed)
|
||||
help.save(cacheFile, conv)
|
||||
|
||||
result
|
||||
}
|
||||
private[sbt] def inputChangedWithJson[I: Pickler: Unpickler, O](cacheFile: File)(f: (Boolean, I) => O): I => O = in =>
|
||||
{
|
||||
val help = new JsonCacheHelp[I]
|
||||
val conv = help.convert(in)
|
||||
val changed = help.changed(cacheFile, conv)
|
||||
val result = f(changed, in)
|
||||
|
||||
if (changed)
|
||||
help.save(cacheFile, conv)
|
||||
|
||||
result
|
||||
}
|
||||
def outputChanged[I, O](cacheFile: File)(f: (Boolean, I) => O)(implicit ic: InputCache[I]): (() => I) => O = in =>
|
||||
{
|
||||
val initial = in()
|
||||
val help = new CacheHelp(ic)
|
||||
val changed = help.changed(cacheFile, help.convert(initial))
|
||||
val result = f(changed, initial)
|
||||
|
||||
if (changed)
|
||||
help.save(cacheFile, help.convert(in()))
|
||||
|
||||
result
|
||||
}
|
||||
private[sbt] def outputChangedWithJson[I: Pickler, O](cacheFile: File)(f: (Boolean, I) => O): (() => I) => O = in =>
|
||||
{
|
||||
val initial = in()
|
||||
val help = new JsonCacheHelp[I]
|
||||
val changed = help.changed(cacheFile, help.convert(initial))
|
||||
val result = f(changed, initial)
|
||||
|
||||
if (changed)
|
||||
help.save(cacheFile, help.convert(in()))
|
||||
|
||||
result
|
||||
}
|
||||
final class CacheHelp[I](val ic: InputCache[I]) {
|
||||
def convert(i: I): ic.Internal = ic.convert(i)
|
||||
def save(cacheFile: File, value: ic.Internal): Unit =
|
||||
Using.fileOutputStream()(cacheFile)(out => ic.write(out, value))
|
||||
def changed(cacheFile: File, converted: ic.Internal): Boolean =
|
||||
try {
|
||||
val prev = Using.fileInputStream(cacheFile)(x => ic.read(x))
|
||||
!ic.equiv.equiv(converted, prev)
|
||||
} catch { case e: Exception => true }
|
||||
}
|
||||
private[sbt] final class JsonCacheHelp[I: Pickler] {
|
||||
def convert(i: I): String = toJsonString(i)
|
||||
def save(cacheFile: File, value: String): Unit =
|
||||
IO.write(cacheFile, value, IO.utf8)
|
||||
def changed(cacheFile: File, converted: String): Boolean =
|
||||
try {
|
||||
val prev = IO.read(cacheFile, IO.utf8)
|
||||
converted != prev
|
||||
} catch { case e: Exception => true }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait Tracked {
|
||||
/** Cleans outputs and clears the cache.*/
|
||||
def clean(): Unit
|
||||
}
|
||||
class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked {
|
||||
def clean() = delete(cacheFile)
|
||||
class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: JsonFormat[Long]) extends Tracked {
|
||||
def clean() = store.delete()
|
||||
/**
|
||||
* Reads the previous timestamp, evaluates the provided function,
|
||||
* and then updates the timestamp if the function completes normally.
|
||||
|
|
@ -138,17 +81,16 @@ class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked {
|
|||
{
|
||||
val start = now()
|
||||
val result = f(readTimestamp)
|
||||
write(cacheFile, (if (useStartTime) start else now()).toString)
|
||||
store.write(if (useStartTime) start else now())
|
||||
result
|
||||
}
|
||||
private def now() = System.currentTimeMillis
|
||||
def readTimestamp: Long =
|
||||
try { read(cacheFile).toLong }
|
||||
catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 }
|
||||
Try { store.read[Long] } getOrElse 0
|
||||
}
|
||||
|
||||
class Changed[O](val cacheFile: File)(implicit equiv: Equiv[O], format: Format[O]) extends Tracked {
|
||||
def clean() = delete(cacheFile)
|
||||
class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
|
||||
def clean() = store.delete()
|
||||
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value =>
|
||||
{
|
||||
if (uptodate(value))
|
||||
|
|
@ -159,19 +101,15 @@ class Changed[O](val cacheFile: File)(implicit equiv: Equiv[O], format: Format[O
|
|||
}
|
||||
}
|
||||
|
||||
def update(value: O): Unit = Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
||||
def uptodate(value: O): Boolean =
|
||||
try {
|
||||
Using.fileInputStream(cacheFile) {
|
||||
stream => equiv.equiv(value, format.reads(stream))
|
||||
}
|
||||
} catch {
|
||||
case _: Exception => false
|
||||
}
|
||||
def update(value: O): Unit = store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
||||
def uptodate(value: O): Boolean = {
|
||||
val equiv: Equiv[O] = implicitly
|
||||
equiv.equiv(value, store.read[O])
|
||||
}
|
||||
}
|
||||
object Difference {
|
||||
def constructor(defineClean: Boolean, filesAreOutputs: Boolean): (File, FilesInfo.Style) => Difference =
|
||||
(cache, style) => new Difference(cache, style, defineClean, filesAreOutputs)
|
||||
def constructor(defineClean: Boolean, filesAreOutputs: Boolean): (CacheStore, FileInfo.Style) => Difference =
|
||||
(store, style) => new Difference(store, style, defineClean, filesAreOutputs)
|
||||
|
||||
/**
|
||||
* Provides a constructor for a Difference that removes the files from the previous run on a call to 'clean' and saves the
|
||||
|
|
@ -185,15 +123,15 @@ object Difference {
|
|||
*/
|
||||
val inputs = constructor(false, false)
|
||||
}
|
||||
class Difference(val cache: File, val style: FilesInfo.Style, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked {
|
||||
class Difference(val store: CacheStore, val style: FileInfo.Style, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked {
|
||||
def clean() =
|
||||
{
|
||||
if (defineClean) delete(raw(cachedFilesInfo)) else ()
|
||||
if (defineClean) IO.delete(raw(cachedFilesInfo)) else ()
|
||||
clearCache()
|
||||
}
|
||||
private def clearCache() = delete(cache)
|
||||
private def clearCache() = store.delete()
|
||||
|
||||
private def cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files
|
||||
private def cachedFilesInfo = store.read(default = FilesInfo.empty[style.F]).files //(style.formats).files
|
||||
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
|
||||
|
||||
def apply[T](files: Set[File])(f: ChangeReport[File] => T): T =
|
||||
|
|
@ -225,7 +163,9 @@ class Difference(val cache: File, val style: FilesInfo.Style, val defineClean: B
|
|||
|
||||
val result = f(report)
|
||||
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
|
||||
toFile(style.formats)(info)(cache)(style.manifest)
|
||||
|
||||
store.write(info)
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
@ -240,24 +180,24 @@ object FileFunction {
|
|||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in
|
||||
* cacheBaseDirectory. On each invocation, the state of the input and output
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
*
|
||||
* @param cacheBaseDirectory The folder in which to store
|
||||
* @param storeFactory The factory to use to get stores for the input and output files.
|
||||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param outStyle The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(cacheBaseDirectory: File, inStyle: FilesInfo.Style = FilesInfo.lastModified, outStyle: FilesInfo.Style = FilesInfo.exists)(action: Set[File] => Set[File]): Set[File] => Set[File] =
|
||||
cached(cacheBaseDirectory)(inStyle, outStyle)((in, out) => action(in.checked))
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style = FileInfo.lastModified, outStyle: FileInfo.Style = FileInfo.exists)(action: Set[File] => Set[File]): Set[File] => Set[File] =
|
||||
cached(storeFactory)(inStyle, outStyle)((in, out) => action(in.checked))
|
||||
|
||||
def cached(cacheBaseDirectory: File)(inStyle: FilesInfo.Style, outStyle: FilesInfo.Style)(action: UpdateFunction): Set[File] => Set[File] =
|
||||
def cached(storeFactory: CacheStoreFactory)(inStyle: FileInfo.Style, outStyle: FileInfo.Style)(action: UpdateFunction): Set[File] => Set[File] =
|
||||
{
|
||||
lazy val inCache = Difference.inputs(cacheBaseDirectory / "in-cache", inStyle)
|
||||
lazy val outCache = Difference.outputs(cacheBaseDirectory / "out-cache", outStyle)
|
||||
lazy val inCache = Difference.inputs(storeFactory.derive("in-cache"), inStyle)
|
||||
lazy val outCache = Difference.outputs(storeFactory.derive("out-cache"), outStyle)
|
||||
inputs =>
|
||||
{
|
||||
inCache(inputs) { inReport =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
|
||||
import CacheImplicits._
|
||||
|
||||
import sjsonnew.{ Builder, deserializationError, IsoString, JsonFormat, Unbuilder }
|
||||
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, FixedParser }
|
||||
|
||||
import scala.json.ast.unsafe.JValue
|
||||
|
||||
class TrackedSpec extends UnitSpec {
|
||||
|
||||
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, FixedParser.parseUnsafe)
|
||||
|
||||
"lastOutput" should "store the last output" in {
|
||||
withStore { store =>
|
||||
|
||||
val value = 5
|
||||
val otherValue = 10
|
||||
|
||||
val res0 =
|
||||
Tracked.lastOutput[Int, Int](store) {
|
||||
case (in, None) =>
|
||||
assert(in === value)
|
||||
in
|
||||
case (in, Some(_)) =>
|
||||
fail()
|
||||
}(implicitly)(value)
|
||||
assert(res0 === value)
|
||||
|
||||
val res1 =
|
||||
Tracked.lastOutput[Int, Int](store) {
|
||||
case (in, None) =>
|
||||
fail()
|
||||
case (in, Some(read)) =>
|
||||
assert(in === otherValue)
|
||||
assert(read === value)
|
||||
read
|
||||
}(implicitly)(otherValue)
|
||||
assert(res1 === value)
|
||||
|
||||
val res2 =
|
||||
Tracked.lastOutput[Int, Int](store) {
|
||||
case (in, None) =>
|
||||
fail()
|
||||
case (in, Some(read)) =>
|
||||
assert(in === otherValue)
|
||||
assert(read === value)
|
||||
read
|
||||
}(implicitly)(otherValue)
|
||||
assert(res2 === value)
|
||||
}
|
||||
}
|
||||
|
||||
"inputChanged" should "detect that the input has not changed" in {
|
||||
withStore { store =>
|
||||
val input0 = 0
|
||||
|
||||
val res0 =
|
||||
Tracked.inputChanged[Int, Int](store) {
|
||||
case (true, in) =>
|
||||
assert(in === input0)
|
||||
in
|
||||
case (false, in) =>
|
||||
fail()
|
||||
}(implicitly, implicitly)(input0)
|
||||
assert(res0 === input0)
|
||||
|
||||
val res1 =
|
||||
Tracked.inputChanged[Int, Int](store) {
|
||||
case (true, in) =>
|
||||
fail()
|
||||
case (false, in) =>
|
||||
assert(in === input0)
|
||||
in
|
||||
}(implicitly, implicitly)(input0)
|
||||
assert(res1 === input0)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
it should "detect that the input has changed" in {
|
||||
withStore { store =>
|
||||
val input0 = 0
|
||||
val input1 = 1
|
||||
|
||||
val res0 =
|
||||
Tracked.inputChanged[Int, Int](store) {
|
||||
case (true, in) =>
|
||||
assert(in === input0)
|
||||
in
|
||||
case (false, in) =>
|
||||
fail()
|
||||
}(implicitly, implicitly)(input0)
|
||||
assert(res0 === input0)
|
||||
|
||||
val res1 =
|
||||
Tracked.inputChanged[Int, Int](store) {
|
||||
case (true, in) =>
|
||||
assert(in === input1)
|
||||
in
|
||||
case (false, in) =>
|
||||
fail()
|
||||
}(implicitly, implicitly)(input1)
|
||||
assert(res1 === input1)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"tstamp tracker" should "have a timestamp of 0 on first invocation" in {
|
||||
withStore { store =>
|
||||
Tracked.tstamp(store) { last =>
|
||||
assert(last === 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "provide the last time a function has been evaluated" in {
|
||||
withStore { store =>
|
||||
|
||||
Tracked.tstamp(store) { last =>
|
||||
assert(last === 0)
|
||||
}
|
||||
|
||||
Tracked.tstamp(store) { last =>
|
||||
val difference = System.currentTimeMillis - last
|
||||
assert(difference < 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def withStore(f: CacheStore => Unit): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store", Converter)
|
||||
f(store)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package sjsonnew
|
||||
package support.scalajson.unsafe
|
||||
|
||||
import scala.json.ast.unsafe._
|
||||
import scala.collection.mutable
|
||||
import jawn.{ SupportParser, MutableFacade }
|
||||
|
||||
object FixedParser extends SupportParser[JValue] {
|
||||
implicit val facade: MutableFacade[JValue] =
|
||||
new MutableFacade[JValue] {
|
||||
def jnull() = JNull
|
||||
def jfalse() = JTrue
|
||||
def jtrue() = JFalse
|
||||
def jnum(s: String) = JNumber(s)
|
||||
def jint(s: String) = JNumber(s)
|
||||
def jstring(s: String) = JString(s)
|
||||
def jarray(vs: mutable.ArrayBuffer[JValue]) = JArray(vs.toArray)
|
||||
def jobject(vs: mutable.Map[String, JValue]) = {
|
||||
val array = new Array[JField](vs.size)
|
||||
var i = 0
|
||||
vs.foreach {
|
||||
case (key, value) =>
|
||||
array(i) = JField(key, value)
|
||||
i += 1
|
||||
}
|
||||
JObject(array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ object Dependencies {
|
|||
lazy val sbtIO = "org.scala-sbt" %% "io" % "1.0.0-M6"
|
||||
lazy val jline = "jline" % "jline" % "2.13"
|
||||
lazy val sbtSerialization = "org.scala-sbt" %% "serialization" % "0.1.2"
|
||||
lazy val sbinary = "org.scala-sbt" %% "sbinary" % "0.4.3"
|
||||
|
||||
lazy val scalaCompiler = Def.setting { "org.scala-lang" % "scala-compiler" % scalaVersion.value }
|
||||
lazy val scalaReflect = Def.setting { "org.scala-lang" % "scala-reflect" % scalaVersion.value }
|
||||
|
|
@ -27,4 +26,7 @@ object Dependencies {
|
|||
val scalatest = "org.scalatest" %% "scalatest" % "2.2.6"
|
||||
|
||||
lazy val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
|
||||
|
||||
lazy val datatypeCodecs = "org.scala-sbt" %% "datatype-codecs" % "1.0.0-SNAPSHOT"
|
||||
lazy val sjsonnewScalaJson = "com.eed3si9n" %% "sjson-new-scalajson" % "0.4.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue