mirror of
https://github.com/sbt/sbt.git
synced 2026-09-05 01:03:36 +02:00
[2.x] feat: Add cacheVersion setting for global cache invalidation (#8993)
* [2.x] feat: Add cacheVersion setting for global cache invalidation **Problem** There was no escape hatch to invalidate all task caches when needed. **Solution** Add `Global / cacheVersion` setting that incorporates into the cache key hash. Changing it invalidates all caches. Defaults to reading system property `sbt.cacheversion`, or else 0L. When 0L, the hash is identical to the previous behavior (backward compatible). Fixes #8992 * [2.x] refactor: Simplify BuildWideCacheConfiguration and add cacheVersion test - Replace auxiliary constructors with default parameter values - Add unit test verifying cacheVersion invalidates the cache * [2.x] fix: Restore auxiliary constructors for binary compatibility * [2.x] test: Improve cacheVersion scripted test and add release note - Scripted test now verifies cache invalidation via a counter that increments only when the task body actually executes - Add release note documenting the cacheVersion setting
This commit is contained in:
@@ -41,9 +41,10 @@ object ActionCache:
|
||||
* all we need from the input is to generate some hash value.
|
||||
* - codeContentHash: This hash represents the Scala code of the task.
|
||||
* Even if the input tasks are the same, the code part needs to be tracked.
|
||||
* - extraHash: Reserved for later, which we might use to invalidate the cache.
|
||||
* - extraHash: Extra hash for cache invalidation (combined with config.cacheVersion).
|
||||
* - tags: Tags to track cache level.
|
||||
* - config: The configuration that's used to store where the cache backends are.
|
||||
* config.cacheVersion is incorporated into the cache key to allow global invalidation.
|
||||
* - action: The actual action to be cached.
|
||||
*/
|
||||
def cache[I: HashWriter, O: JsonFormat](
|
||||
@@ -62,7 +63,7 @@ object ActionCache:
|
||||
// This fixes https://github.com/sbt/sbt/issues/7662
|
||||
// Use the same input digest as success, distinguished by exitCode
|
||||
cacheEventLog.append(ActionCacheEvent.OnsiteTask)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion)
|
||||
val cachedFailure = CachedCompileFailure.fromException(e)
|
||||
val json = Converter.toJsonUnsafe(cachedFailure)
|
||||
val failureFile = StringVirtualFile1(valuePath, CompactPrinter(json))
|
||||
@@ -101,7 +102,7 @@ object ActionCache:
|
||||
result
|
||||
else
|
||||
cacheEventLog.append(ActionCacheEvent.OnsiteTask)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion)
|
||||
val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json))
|
||||
val newOutputs = Vector(valueFile) ++ outputs.toVector
|
||||
try
|
||||
@@ -160,7 +161,7 @@ object ActionCache:
|
||||
catch case _: Exception => None
|
||||
|
||||
// Optimization: Check if we can read directly from symlinked value file
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion)
|
||||
val resolvedValuePath = config.fileConverter.toPath(VirtualFileRef.of(valuePath))
|
||||
|
||||
def readFromSymlink(): Option[Either[Option[CachedCompileFailure], O]] =
|
||||
@@ -240,7 +241,7 @@ object ActionCache:
|
||||
): Either[Throwable, ActionResult] =
|
||||
// val logger = config.logger
|
||||
CacheImplicits.setCacheSize(config.localDigestCacheByteSize)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash)
|
||||
val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion)
|
||||
val getRequest =
|
||||
GetActionResultRequest(input, inlineStdout = false, inlineStderr = false, Vector(valuePath))
|
||||
config.store.get(getRequest)
|
||||
@@ -248,10 +249,18 @@ object ActionCache:
|
||||
private inline def mkInput[I: HashWriter](
|
||||
key: I,
|
||||
codeContentHash: Digest,
|
||||
extraHash: Digest
|
||||
extraHash: Digest,
|
||||
cacheVersion: Long,
|
||||
): (Digest, String) =
|
||||
val effectiveExtraHash =
|
||||
if cacheVersion != 0L then Digest.sha256Hash(extraHash, Digest.dummy(cacheVersion))
|
||||
else extraHash
|
||||
val input =
|
||||
Digest.sha256Hash(codeContentHash, extraHash, Digest.dummy(Hasher.hashUnsafe[I](key)))
|
||||
Digest.sha256Hash(
|
||||
codeContentHash,
|
||||
effectiveExtraHash,
|
||||
Digest.dummy(Hasher.hashUnsafe[I](key))
|
||||
)
|
||||
(input, s"$${OUT}/value/$input.json")
|
||||
|
||||
def manifestFromFile(manifest: Path): Manifest =
|
||||
@@ -332,13 +341,14 @@ class BuildWideCacheConfiguration(
|
||||
val logger: Logger,
|
||||
val cacheEventLog: CacheEventLog,
|
||||
val localDigestCacheByteSize: Long,
|
||||
val cacheVersion: Long,
|
||||
):
|
||||
def this(
|
||||
store: ActionCacheStore,
|
||||
outputDirectory: Path,
|
||||
fileConverter: FileConverter,
|
||||
logger: Logger,
|
||||
cacheEventLog: CacheEventLog
|
||||
cacheEventLog: CacheEventLog,
|
||||
) =
|
||||
this(
|
||||
store,
|
||||
@@ -346,9 +356,20 @@ class BuildWideCacheConfiguration(
|
||||
fileConverter,
|
||||
logger,
|
||||
cacheEventLog,
|
||||
CacheImplicits.defaultLocalDigestCacheByteSize
|
||||
CacheImplicits.defaultLocalDigestCacheByteSize,
|
||||
0L
|
||||
)
|
||||
|
||||
def this(
|
||||
store: ActionCacheStore,
|
||||
outputDirectory: Path,
|
||||
fileConverter: FileConverter,
|
||||
logger: Logger,
|
||||
cacheEventLog: CacheEventLog,
|
||||
localDigestCacheByteSize: Long,
|
||||
) =
|
||||
this(store, outputDirectory, fileConverter, logger, cacheEventLog, localDigestCacheByteSize, 0L)
|
||||
|
||||
override def toString(): String =
|
||||
s"BuildWideCacheConfiguration(store = $store, outputDirectory = $outputDirectory)"
|
||||
end BuildWideCacheConfiguration
|
||||
|
||||
Reference in New Issue
Block a user