diff --git a/build.sbt b/build.sbt index a0eb78fa3..3f8885f73 100644 --- a/build.sbt +++ b/build.sbt @@ -113,7 +113,7 @@ lazy val utilCache = (project in file("util-cache")). settings( commonSettings, name := "Util Cache", - libraryDependencies ++= Seq(sjsonnewScalaJson.value, scalaReflect.value) + libraryDependencies ++= Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value) ). configure(addSbtIO) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 79c777367..9afeed255 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -46,6 +46,7 @@ object Dependencies { val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value } val sjsonnewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value } + val sjsonnewMurmurhash = Def.setting { "com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value } def log4jVersion = "2.8.1" val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion diff --git a/util-cache/src/main/scala/sbt/util/BasicCacheImplicits.scala b/util-cache/src/main/scala/sbt/util/BasicCacheImplicits.scala index 92e69bddb..1e2c74da8 100644 --- a/util-cache/src/main/scala/sbt/util/BasicCacheImplicits.scala +++ b/util-cache/src/main/scala/sbt/util/BasicCacheImplicits.scala @@ -1,56 +1,18 @@ package sbt.util -import java.net.{ URI, URL } - import sjsonnew.{ BasicJsonProtocol, JsonFormat } trait BasicCacheImplicits { self: BasicJsonProtocol => - implicit def basicCache[I: JsonFormat: Equiv, O: JsonFormat]: Cache[I, O] = + implicit def basicCache[I: JsonFormat, 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 } + SingletonCache.basicSingletonCache(asSingleton(t)) } diff --git a/util-cache/src/main/scala/sbt/util/Cache.scala b/util-cache/src/main/scala/sbt/util/Cache.scala index 3b85a8dc2..78d694b27 100644 --- a/util-cache/src/main/scala/sbt/util/Cache.scala +++ b/util-cache/src/main/scala/sbt/util/Cache.scala @@ -73,13 +73,5 @@ object Cache { 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 - } - } } } diff --git a/util-cache/src/main/scala/sbt/util/SeparatedCache.scala b/util-cache/src/main/scala/sbt/util/SeparatedCache.scala index c8772e034..0dc8dfceb 100644 --- a/util-cache/src/main/scala/sbt/util/SeparatedCache.scala +++ b/util-cache/src/main/scala/sbt/util/SeparatedCache.scala @@ -6,58 +6,54 @@ package sbt.util import scala.util.Try import sjsonnew.JsonFormat +import sjsonnew.support.murmurhash.Hasher import CacheImplicits._ /** * A cache that stores a single value. */ -trait SingletonCache[T] { +trait SingletonCache[A] { /** Reads the cache from the backing `from`. */ - def read(from: Input): T + def read(from: Input): A /** Writes `value` to the backing `to`. */ - def write(to: Output, value: T): Unit - - /** Equivalence for elements of type `T`. */ - def equiv: Equiv[T] + def write(to: Output, value: A): Unit } 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 + implicit def basicSingletonCache[A: JsonFormat]: SingletonCache[A] = + new SingletonCache[A] { + override def read(from: Input): A = from.read[A] + override def write(to: Output, value: A) = to.write(value) } /** A lazy `SingletonCache` */ - def lzy[T: JsonFormat: Equiv](mkCache: => SingletonCache[T]): SingletonCache[T] = - new SingletonCache[T] { + def lzy[A: JsonFormat](mkCache: => SingletonCache[A]): SingletonCache[A] = + new SingletonCache[A] { 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 + override def read(from: Input): A = cache.read(from) + override def write(to: Output, value: A) = cache.write(to, value) } } /** * 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] = +class BasicCache[I: JsonFormat, O: JsonFormat] extends Cache[I, O] { + private val singletonCache: SingletonCache[(Long, O)] = implicitly + val jsonFormat: JsonFormat[I] = implicitly + override def apply(store: CacheStore)(key: I): CacheResult[O] = { + val keyHash: Long = Hasher.hashUnsafe[I](key).toLong Try { - val (previousKey, previousValue) = singletonCache.read(store) - if (equiv.equiv(key, previousKey)) - Hit(previousValue) - else - Miss(update(store)(key)) - } getOrElse Miss(update(store)(key)) + val (previousKeyHash, previousValue) = singletonCache.read(store) + if (keyHash == previousKeyHash) Hit(previousValue) + else Miss(update(store)(keyHash)) + } getOrElse Miss(update(store)(keyHash)) + } - private def update(store: CacheStore)(key: I) = (value: O) => { - singletonCache.write(store, (key, value)) + private def update(store: CacheStore)(keyHash: Long) = (value: O) => { + singletonCache.write(store, (keyHash, value)) } } diff --git a/util-tracking/src/main/scala/sbt/util/Tracked.scala b/util-tracking/src/main/scala/sbt/util/Tracked.scala index dcd5658e9..66e84e8fb 100644 --- a/util-tracking/src/main/scala/sbt/util/Tracked.scala +++ b/util-tracking/src/main/scala/sbt/util/Tracked.scala @@ -10,11 +10,9 @@ import sbt.io.IO import sbt.io.syntax._ import sjsonnew.JsonFormat +import sjsonnew.support.murmurhash.Hasher object Tracked { - - import CacheImplicits.LongJsonFormat - /** * Creates a tracker that provides the last time it was evaluated. * If the function throws an exception. @@ -33,7 +31,10 @@ object Tracked { * 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(store: CacheStore, useStartTime: Boolean): Timestamp = new Timestamp(store, useStartTime) + def tstamp(store: CacheStore, useStartTime: Boolean): Timestamp = { + import CacheImplicits.LongJsonFormat + new Timestamp(store, useStartTime) + } /** * Creates a tracker that provides the last time it was evaluated. @@ -74,7 +75,10 @@ object Tracked { * recent invocation. */ def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(f: (Boolean, I) => O): I => O = { in => - val cache: SingletonCache[I] = implicitly + val cache: SingletonCache[Long] = { + import CacheImplicits.LongJsonFormat + implicitly + } val help = new CacheHelp(cache) val changed = help.changed(store, in) val result = f(changed, in) @@ -90,15 +94,20 @@ object Tracked { def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(f: (Boolean, I) => O): I => O = inputChanged(CacheStore(cacheFile))(f) - private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[I]) { + private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) { + import CacheImplicits.implicitHashWriter def save(store: CacheStore, value: I): Unit = { store.write(value) } def changed(store: CacheStore, value: I): Boolean = Try { store.read[I] } match { - case Success(prev) => !sc.equiv.equiv(value, prev) - case Failure(_) => true + case Success(prev) => + Hasher.hash(value) match { + case Success(keyHash) => keyHash.toLong != prev + case Failure(_) => true + } + case Failure(_) => true } }