diff --git a/README.md b/README.md index 521b0edd0..de17fcd87 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ [asking]: https://stackoverflow.com/questions/ask?tags=sbt [LICENSE]: LICENSE [sbt/io]: https://github.com/sbt/io - [sbt/util]: https://github.com/sbt/util [sbt/librarymanagement]: https://github.com/sbt/librarymanagement [sbt/zinc]: https://github.com/sbt/zinc [sbt/sbt]: https://github.com/sbt/sbt @@ -30,7 +29,6 @@ This is the 1.x series of sbt. The source code of sbt is split across several GitHub repositories, including this one. - [sbt/io][sbt/io] hosts `sbt.io` module. -- [sbt/util][sbt/util] hosts a collection of internally used modules. - [sbt/librarymanagement][sbt/librarymanagement] hosts `sbt.librarymanagement` module that wraps Ivy. - [sbt/zinc][sbt/zinc] hosts Zinc, an incremental compiler for Scala. - [sbt/sbt][sbt/sbt], this repository hosts modules that implements the build tool. diff --git a/build.sbt b/build.sbt index 920e63c9c..13224463f 100644 --- a/build.sbt +++ b/build.sbt @@ -419,6 +419,10 @@ lazy val utilTracking = (project in file("util-tracking")) name := "Util Tracking", libraryDependencies ++= Seq(scalatest % "test"), utilMimaSettings, + mimaBinaryIssueFilters ++= Seq( + // Private final class constructors changed + ProblemFilters.exclude[IncompatibleMethTypeProblem]("sbt.util.Tracked#CacheHelp.this"), + ) ) .configure(addSbtIO) diff --git a/util-tracking/src/main/scala/sbt/util/Tracked.scala b/util-tracking/src/main/scala/sbt/util/Tracked.scala index 1e6cd607e..883872f1d 100644 --- a/util-tracking/src/main/scala/sbt/util/Tracked.scala +++ b/util-tracking/src/main/scala/sbt/util/Tracked.scala @@ -14,7 +14,7 @@ import sbt.io.IO import sbt.io.syntax._ import sbt.internal.util.EmptyCacheError -import sjsonnew.JsonFormat +import sjsonnew.{ JsonFormat, JsonWriter } import sjsonnew.support.murmurhash.Hasher object Tracked { @@ -83,8 +83,8 @@ object Tracked { * Creates a tracker that indicates whether the output returned from `p` has changed or not. * * {{{ - * val cachedTask = inputChanged(cache / "inputs") { (inChanged, in: Inputs) => - * Tracked.outputChanged(cache / "output") { (outChanged, outputs: FilesInfo[PlainFileInfo]) => + * val cachedTask = inputChanged(cacheStoreFactory.make("inputs")) { (inChanged, in: Inputs) => + * Tracked.outputChanged(cacheStoreFactory.make("output")) { (outChanged, outputs: FilesInfo[PlainFileInfo]) => * if (inChanged || outChanged) { * doSomething(label, sources, classpath, outputDirectory, options, log) * } @@ -95,6 +95,28 @@ object Tracked { */ def outputChanged[A1: JsonFormat, A2](store: CacheStore)( f: (Boolean, A1) => A2 + ): (() => A1) => A2 = { + outputChangedW(store)(f) + } + + /** + * Creates a tracker that indicates whether the output returned from `p` has changed or not. + * + * {{{ + * val cachedTask = inputChanged(cacheStoreFactory.make("inputs")) { (inChanged, in: Inputs) => + * Tracked.outputChanged(cacheStoreFactory.make("output")) { (outChanged, outputs: FilesInfo[PlainFileInfo]) => + * if (inChanged || outChanged) { + * doSomething(label, sources, classpath, outputDirectory, options, log) + * } + * } + * } + * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) + * }}} + * + * This is a variant of `outputChanged` that takes `A1: JsonWriter` as opposed to `A1: JsonFormat`. + */ + def outputChangedW[A1: JsonWriter, A2](store: CacheStore)( + f: (Boolean, A1) => A2 ): (() => A1) => A2 = p => { val cache: SingletonCache[Long] = { import CacheImplicits.LongJsonFormat @@ -128,8 +150,7 @@ object Tracked { outputChanged[A1, A2](CacheStore(cacheFile))(f) /** - * Creates a tracker that indicates whether the arguments given to f have changed since the most - * recent invocation. + * Creates a tracker that indicates whether the output returned from `p` has changed or not. * * {{{ * val cachedTask = inputChanged(cache / "inputs") { (inChanged, in: Inputs) => @@ -141,9 +162,53 @@ object Tracked { * } * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) * }}} + * + * This is a variant of `outputChanged` that takes `A1: JsonWriter` as opposed to `A1: JsonFormat`. + */ + def outputChangedW[A1: JsonWriter, A2]( + cacheFile: File + )(f: (Boolean, A1) => A2): (() => A1) => A2 = + outputChangedW[A1, A2](CacheStore(cacheFile))(f) + + /** + * Creates a tracker that indicates whether the arguments given to f have changed since the most + * recent invocation. + * + * {{{ + * val cachedTask = inputChanged(cacheStoreFactory.make("inputs")) { (inChanged, in: Inputs) => + * Tracked.outputChanged(cacheStoreFactory.make("output")) { (outChanged, outputs: FilesInfo[PlainFileInfo]) => + * if (inChanged || outChanged) { + * doSomething(label, sources, classpath, outputDirectory, options, log) + * } + * } + * } + * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) + * }}} */ def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)( f: (Boolean, I) => O + ): I => O = + inputChangedW(store)(f) + + /** + * Creates a tracker that indicates whether the arguments given to f have changed since the most + * recent invocation. + * + * {{{ + * val cachedTask = inputChanged(cacheStoreFactory.make("inputs")) { (inChanged, in: Inputs) => + * Tracked.outputChanged(cacheStoreFactory.make("output")) { (outChanged, outputs: FilesInfo[PlainFileInfo]) => + * if (inChanged || outChanged) { + * doSomething(label, sources, classpath, outputDirectory, options, log) + * } + * } + * } + * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) + * }}} + * + * This is a variant of `inputChanged` that takes `I: JsonWriter` as opposed to `I: JsonFormat`. + */ + def inputChangedW[I: JsonWriter, O](store: CacheStore)( + f: (Boolean, I) => O ): I => O = { in => val cache: SingletonCache[Long] = { import CacheImplicits.LongJsonFormat @@ -177,7 +242,29 @@ object Tracked { ): I => O = inputChanged(CacheStore(cacheFile))(f) - private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) { + /** + * Creates a tracker that indicates whether the arguments given to f have changed since the most + * recent invocation. + * + * {{{ + * val cachedTask = inputChanged(cache / "inputs") { (inChanged, in: Inputs) => + * Tracked.outputChanged(cache / "output") { (outChanged, outputs: FilesInfo[PlainFileInfo]) => + * if (inChanged || outChanged) { + * doSomething(label, sources, classpath, outputDirectory, options, log) + * } + * } + * } + * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) + * }}} + * + * This is a variant of `inputChanged` that takes `I: JsonWriter` as opposed to `I: JsonFormat`. + */ + def inputChangedW[I: JsonWriter, O](cacheFile: File)( + f: (Boolean, I) => O + ): I => O = + inputChangedW(CacheStore(cacheFile))(f) + + private final class CacheHelp[I: JsonWriter](val sc: SingletonCache[Long]) { import CacheImplicits.implicitHashWriter import CacheImplicits.LongJsonFormat def save(store: CacheStore, value: I): Unit = { diff --git a/util-tracking/src/test/scala/sbt/util/TrackedSpec.scala b/util-tracking/src/test/scala/sbt/util/TrackedSpec.scala index ec8d5b824..f456931f0 100644 --- a/util-tracking/src/test/scala/sbt/util/TrackedSpec.scala +++ b/util-tracking/src/test/scala/sbt/util/TrackedSpec.scala @@ -11,6 +11,7 @@ import org.scalatest.FlatSpec import sbt.io.IO import sbt.io.syntax._ import sbt.util.CacheImplicits._ +import sjsonnew.{ Builder, JsonWriter } import scala.concurrent.Promise @@ -56,6 +57,27 @@ class TrackedSpec extends FlatSpec { } } + "inputChangedW" should "not require the input to have a JsonReader instance" in { + case class Input(v: Int) + + implicit val writer = new JsonWriter[Input] { + override def write[J](obj: Input, builder: Builder[J]): Unit = builder.writeInt(obj.v) + } + + withStore { store => + val input0 = Input(1) + + val cachedFun = Tracked.inputChangedW[Input, Int](store) { + case (_, in) => in.v + } + + val res0 = cachedFun(input0) + assert(res0 === input0.v) + () + } + + } + "inputChanged" should "detect that the input has not changed" in { withStore { store => val input0 = "foo" @@ -113,6 +135,27 @@ class TrackedSpec extends FlatSpec { } } + "outputChangedW" should "not require the input to have a JsonReader instance" in { + case class Input(v: Int) + + implicit val writer = new JsonWriter[Input] { + override def write[J](obj: Input, builder: Builder[J]): Unit = builder.writeInt(obj.v) + } + + withStore { store => + val input0 = Input(1) + + val cachedFun = Tracked.outputChangedW[Input, Int](store) { + case (_, in) => in.v + } + + val res0 = cachedFun(() => input0) + assert(res0 === input0.v) + () + } + + } + "outputChanged" should "detect that the output has not changed" in { withStore { store => val beforeCompletion: String = "before-completion"