From c13c25fdff8755793e3b6249b0e83f2685099128 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 15 Sep 2024 18:13:56 -0400 Subject: [PATCH] fix: Invalidate testQuick on resource file changes **Problem** testQuick currently does not invalidate on resource file changes. **Solution** This includes resource digests into the input. --- main/src/main/scala/sbt/Defaults.scala | 9 ++++++++- main/src/main/scala/sbt/Keys.scala | 1 + .../src/main/scala/sbt/internal/IncrementalTest.scala | 3 ++- main/src/main/scala/sbt/nio/FileStamp.scala | 11 +++++++++++ sbt-app/src/sbt-test/classloader-cache/resources/test | 6 +++--- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 031dd52a5..ee8e80bdb 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -652,7 +652,14 @@ object Defaults extends BuildCommon { PluginDiscovery.writeDescriptors(discoveredSbtPlugins.value, resourceManaged.value) }).taskValue, managedResources := generate(resourceGenerators).value, - resources := Classpaths.concat(managedResources, unmanagedResources).value + resources := Classpaths.concat(managedResources, unmanagedResources).value, + resourceDigests := { + val uifs = (unmanagedResources / inputFileStamps).value + val mifs = (managedResources / inputFileStamps).value + (uifs ++ mifs).sortBy(_._1.toString()).map { case (p, fileStamp) => + FileStamp.toDigest(p, fileStamp) + } + }, ) // This exists for binary compatibility and probably never should have been public. def addBaseSources: Seq[Def.Setting[Task[Seq[File]]]] = Nil diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 3b7b0e6e1..b1fb00a79 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -180,6 +180,7 @@ object Keys { val managedResources = taskKey[Seq[File]]("Resources generated by the build.").withRank(BTask) val resourceDirectories = settingKey[Seq[File]]("List of all resource directories, both managed and unmanaged.").withRank(BPlusSetting) val resources = taskKey[Seq[File]]("All resource files, both managed and unmanaged.").withRank(BTask) + private[sbt] val resourceDigests = taskKey[Seq[Digest]]("All resource files, both managed and unmanaged.").withRank(BTask) // Output paths @cacheLevel(include = Array.empty) diff --git a/main/src/main/scala/sbt/internal/IncrementalTest.scala b/main/src/main/scala/sbt/internal/IncrementalTest.scala index 68bf7ab75..32da1a6dd 100644 --- a/main/src/main/scala/sbt/internal/IncrementalTest.scala +++ b/main/src/main/scala/sbt/internal/IncrementalTest.scala @@ -49,11 +49,12 @@ object IncrementalTest: val cp = (Keys.test / fullClasspath).value val testNames = Keys.definedTests.value.map(_.name).toVector.distinct val converter = fileConverter.value + val rds = Keys.resourceDigests.value val extra = Keys.extraTestDigests.value val stamper = ClassStamper(cp, converter) // TODO: Potentially do something about JUnit 5 and others which might not use class name Map((testNames.flatMap: name => - stamper.transitiveStamp(name, extra) match + stamper.transitiveStamp(name, extra ++ rds) match case Some(ts) => Seq(name -> ts) case None => Nil ): _*) diff --git a/main/src/main/scala/sbt/nio/FileStamp.scala b/main/src/main/scala/sbt/nio/FileStamp.scala index 4194c9dc2..6e8ddaf3a 100644 --- a/main/src/main/scala/sbt/nio/FileStamp.scala +++ b/main/src/main/scala/sbt/nio/FileStamp.scala @@ -13,8 +13,10 @@ import java.nio.file.{ Path, Paths } import java.util.concurrent.ConcurrentHashMap import sbt.internal.inc.{ EmptyStamp, Stamper, Hash => IncHash, LastModified => IncLastModified } +import sbt.internal.inc.JavaInterfaceUtil.given import sbt.io.IO import sbt.nio.file.FileAttributes +import sbt.util.Digest import sjsonnew.{ Builder, JsonFormat, Unbuilder, deserializationError } import xsbti.compile.analysis.{ Stamp => XStamp } import xsbti.VirtualFileRef @@ -103,6 +105,15 @@ object FileStamp { private[sbt] final case class LastModified private[sbt] (time: Long) extends FileStamp private[sbt] final case class Error(exception: IOException) extends FileStamp + def toDigest(path: Path, stamp: FileStamp): Digest = stamp match + case f: FileHashImpl => + f.xstamp.getHash().toOption match + case Some(hash) => Digest.sha256Hash(hash.getBytes("UTF-8")) + case None => Digest.sha256Hash(path) + case FileStamp.Hash(hex) => Digest.sha256Hash(hex.getBytes("UTF-8")) + case FileStamp.Error(_) => Digest.zero + case FileStamp.LastModified(_) => Digest.sha256Hash(path) + object Formats { implicit val seqPathJsonFormatter: JsonFormat[Seq[Path]] = asStringArray(_.toString, Paths.get(_)) diff --git a/sbt-app/src/sbt-test/classloader-cache/resources/test b/sbt-app/src/sbt-test/classloader-cache/resources/test index 63aa35f05..a41abee03 100644 --- a/sbt-app/src/sbt-test/classloader-cache/resources/test +++ b/sbt-app/src/sbt-test/classloader-cache/resources/test @@ -4,12 +4,12 @@ $ copy-file changes/updated-main.txt src/main/resources/foo.txt > run foo.txt updated-main -> test +> testQuick $ copy-file changes/updated-test.txt src/test/resources/bar.txt --> test +-> testQuick $ copy-file changes/UpdatedResourceTest.scala src/test/scala/scripted/ResourceTest.scala -> test +> testQuick