From 21b5c3b8dfac6e0342685577badc107458abab4c Mon Sep 17 00:00:00 2001 From: MkDev11 Date: Sun, 11 Jan 2026 21:01:05 -0500 Subject: [PATCH] [2.x] fix: Fix snapshot sbt version invalidation 7713 (#8486) When using a SNAPSHOT version of sbt, if the sbt jars are republished with breaking binary changes, the build definition should be recompiled. Previously, the cache key only considered the source content, not the classpath, causing NoSuchMethodError when cached classes referenced methods that no longer exist. This fix includes a hash of SNAPSHOT and -bin- jars (including their modification times) in the cache key. When these jars change, the build definition is recompiled. Fixes #7713 --- .../src/main/scala/sbt/internal/Eval.scala | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/buildfile/src/main/scala/sbt/internal/Eval.scala b/buildfile/src/main/scala/sbt/internal/Eval.scala index 7c8622e18..1af9eb356 100644 --- a/buildfile/src/main/scala/sbt/internal/Eval.scala +++ b/buildfile/src/main/scala/sbt/internal/Eval.scala @@ -45,6 +45,24 @@ class Eval( .map(_.toString) .mkString(java.io.File.pathSeparator) + // Compute a hash of SNAPSHOT jars to invalidate cache when sbt SNAPSHOT version changes. + // Include modification time to detect republished snapshots with the same version string. + // This fixes #7713: build.sbt not recompiled when SNAPSHOT sbt breaks binary compatibility. + private val snapshotClasspathHash: String = + val snapshotJars = classpath.filter { path => + val name = path.getFileName.toString + name.contains("SNAPSHOT") || name.contains("-bin-") + } + if snapshotJars.isEmpty then "" + else + val digester = MessageDigest.getInstance("SHA") + snapshotJars.sorted.foreach { path => + val file = path.toFile + digester.update(bytes(path.toString)) + if file.exists then digester.update(bytes(file.lastModified.toString)) + } + Hash.toHex(digester.digest()) + final class EvalDriver(reporter: EvalReporter) extends Driver: val compileCtx0 = initCtx.fresh val options = nonCpOptions ++ Seq("-classpath", classpathString, "dummy.scala") @@ -196,6 +214,8 @@ class Eval( digester.update(bytes(tpe)) } digester.update(bytes(ev.extraHash)) + // Include SNAPSHOT classpath hash to invalidate cache when sbt version changes (fixes #7713) + digester.update(bytes(snapshotClasspathHash)) val d = digester.digest() val hash = Hash.toHex(d) val moduleName = makeModuleName(hash)