[2.x] fix: Fixes cache invaliation on version change

**Problem**
packageBin includes version into the file name, which ends up
invalidating the cache.

**Solution**
Define packageInternal, which does not include version in the
file name.
This commit is contained in:
Eugene Yokota 2026-07-18 01:08:14 -04:00
parent b76ac09d44
commit 7ef5a3b2d7
9 changed files with 77 additions and 4 deletions

View File

@ -112,6 +112,25 @@ private[librarymanagement] abstract class ArtifactFunctions {
base + "-" + module.revision + classifierStr + "." + artifact.extension
}
/**
* Like `artifactName`, but omits the module's version.
*/
def internalArtifactName(
scalaVersion: ScalaVersion,
module: ModuleID,
artifact: Artifact
): String =
import artifact.*
val classifierStr = classifier match
case None => ""
case Some(c) => s"-${c}"
val cross = CrossVersion(module.crossVersion, scalaVersion.full, scalaVersion.binary)
val withPlatform = module.crossVersion match
case _: Disabled => artifact.name
case _ => CrossVersion.addPlatformSuffix(artifact.name, module.platformOpt, None)
val base = CrossVersion.applyCross(withPlatform, cross)
s"${base}${classifierStr}.${artifact.extension}"
val classifierTypeMap = Map(SourceClassifier -> SourceType, DocClassifier -> DocType)
@deprecated("Configuration should not be decided from the classifier.", "1.0")
def classifierConf(classifier: String): Configuration =

View File

@ -1770,6 +1770,8 @@ object Defaults extends BuildCommon with DefExtra {
packageTaskSettings(packageBin, packageBinMappings) ++
packageTaskSettings(packageSrc, packageSrcMappings) ++
packageTaskSettings(packageDoc, packageDocMappings) ++
packageTaskSettings(packageInternal, packageBinMappings) ++
inTask(packageInternal)(Seq(artifactName :== Artifact.internalArtifactName)) ++
Seq(Keys.`package` := packageBin.value)
def packageBinMappings: Initialize[Task[Seq[(HashedVirtualFileRef, String)]]] =

View File

@ -324,6 +324,7 @@ object Keys {
// package keys
val packageBin = taskKey[HashedVirtualFileRef]("Produces a main artifact, such as a binary jar.").withRank(ATask)
val packageInternal = taskKey[HashedVirtualFileRef]("Produces a binary JAR for internal use (inter-project classpaths, BSP).").withRank(DTask)
val `package` = taskKey[HashedVirtualFileRef]("Produces the main artifact, such as a binary jar. This is typically an alias for the task that actually does the packaging.").withRank(APlusTask)
val packageDoc = taskKey[HashedVirtualFileRef]("Produces a documentation artifact, such as a jar containing API documentation.").withRank(AMinusTask)
val packageSrc = taskKey[HashedVirtualFileRef]("Produces a source artifact, such as a jar containing sources and resources.").withRank(AMinusTask)

View File

@ -132,7 +132,7 @@ private[sbt] object ClasspathImpl {
(Def
.task {
val converter = fileConverter.value
val vf = (packageBin / artifactPath).value
val vf = (packageInternal / artifactPath).value
val jar = converter.toPath(vf)
(TrackLevel.intersection(track, exportToInternal.value), vf, jar)
})
@ -141,13 +141,13 @@ private[sbt] object ClasspathImpl {
Def.task {
val converter = fileConverter.value
val analysisFile = converter.toVirtualFile(compileAnalysisFile.value.toPath)
Seq((packageBin.value, analysisFile))
Seq((packageInternal.value, analysisFile))
}
case (TrackLevel.TrackIfMissing, _, jar) if !jar.toFile().exists =>
Def.task {
val converter = fileConverter.value
val analysisFile = converter.toVirtualFile(compileAnalysisFile.value.toPath)
Seq((packageBin.value, analysisFile))
Seq((packageInternal.value, analysisFile))
}
case (_, vf, _) =>
Def.task {

View File

@ -824,7 +824,7 @@ object BuildServerProtocol {
val internalDependencyClasspath = for {
(ref, configs) <- bspInternalDependencyConfigurations.value
config <- configs
} yield ref / config / Keys.packageBin
} yield ref / config / Keys.packageInternal
(
target,
scalacOptions,

View File

@ -0,0 +1,5 @@
package example
@main
def main(args: String*): Unit =
println(Foo.greeting)

View File

@ -0,0 +1,27 @@
import sbt.internal.util.CacheEventSummary
import complete.DefaultParsers.*
Global / localCacheDirectory := baseDirectory.value / "diskcache"
scalaVersion := "3.8.4"
lazy val checkMiss = inputKey[Unit]("Assert the exact onsite/miss count of the previous run")
lazy val foo = project
lazy val app = project.dependsOn(foo)
lazy val root = (project in file("."))
.aggregate(foo, app)
.settings(
checkMiss := {
val expected: Int = (Space ~> NatBasic).parsed
val s = streams.value
val config = Def.cacheConfiguration.value
val prev = config.cacheEventLog.previous match
case d: CacheEventSummary.Data => d
case CacheEventSummary.Empty => sys.error("empty event log")
s.log.info(s"missCount = ${prev.missCount}")
assert(prev.missCount == expected, s"prev.missCount = ${prev.missCount} (expected $expected)")
}
)

View File

@ -0,0 +1,5 @@
package example
object Foo:
def greeting: String = "hello"
end Foo

View File

@ -0,0 +1,14 @@
# warm the disk cache
> app/compile
# after a clean, recompiling app (and foo) should fully restore from the disk cache
> clean
> app/compile
> checkMiss 0
# bumping foo's version alone (e.g. what sbt-dynver would do on a new commit) must not
# invalidate app's compile cache entry, since foo's jar content hasn't changed
> set foo / version := "0.2.0"
> clean
> app/compile
> checkMiss 0