Gradle build cache: stale entry detection for externally modified files
0 reputation · 26 Nov 2022, 15:23 UTC
0 reputation · 26 Nov 2022, 15:23 UTC
In many projects, external tools or manual edits modify files that are normally outputs of a Gradle task. Since the build cache only hashes declared task inputs, these external changes are invisible to Gradle, leading to stale artifacts being reused.
Constraints: Gradle’s current cache policy does not provide automatic detection or eviction for such changes, and the local cache has no retention policy. Users must manually clean the cache, risking removal of valid entries.
Questions:
1) Does Gradle offer a configuration to monitor file modification timestamps for cache invalidation?
2) Can we declare external files as implicit inputs or use a custom input mechanism?
3) Is there a way to set a retention or eviction policy for the local build cache?
33275 reputation · 27 Nov 2022, 02:06 UTC
1. Timestamp monitoring: Gradle does not provide a configuration to watch file modification timestamps for cache invalidation. Cache keys are computed solely from declared task inputs and outputs; any file not listed as an input is invisible to the cache.
2. Declaring external files as inputs: Yes. Add every external dependency to the task's inputs using inputs.file(), inputs.files(), or a custom input property that stores a content hash (e.g., SHA‑256). This makes the file part of the cache key.
3. Retention/eviction policy: No built-in TTL, LRU, or size-based eviction exists for the local build cache. You must manually run gradle cleanBuildCache or delete entries from ~/.gradle/caches/build-cache-*/.
Gradle's build cache assumes a pure functional model: the same inputs always produce the same outputs. When an external tool or a manual edit changes a file that a task reads but does not declare, the input hash stays the same, so Gradle reuses the cached output. This is by design, not a bug.
tasks.named('yourTask') {
inputs.file(layout.projectDirectory.file('path/to/external-file'))
// or for multiple files
inputs.files(fileTree('external-dir').matching { include '*.txt' })
}
.sha256 alongside the external file and declare that checksum file as the input instead. This keeps key computation fast.
// Example: generate checksum in a preceding task
tasks.register('checksumExternal') {
doLast {
def file = layout.projectDirectory.file('external-data.bin')
def hash = file.bytes.encodeHex() // replace with real SHA-256
layout.buildDirectory.file('external-data.bin.sha256').write(hash)
}
}
tasks.named('yourTask') {
inputs.file(layout.buildDirectory.file('external-data.bin.sha256'))
}
./gradlew yourTask --info
# or
./gradlew yourTask -Dorg.gradle.caching.debug=true
Look for "Cache miss" after modifying the external file. You can also print the input hash in a doLast block:
tasks.named('yourTask') {
doLast {
println "Input hash: " + inputs.files.hashCode()
}
}
./gradlew cleanBuildCache
Then rebuild to populate fresh entries.What produces the external files — a separate Gradle task, an external CLI tool, or manual edits? The answer determines whether you should wire the producer task as a dependency (preferred) or rely on checksum files.
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 27 Nov 2022, 02:32 UTC
When an external file is declared as a Gradle task input via inputs.file() or inputs.files(), the build cache includes its content hash in the cache key. If the file path changes between builds without updating the declaration, the cache may not invalidate, causing stale artifact reuse. To verify, run a build, modify the file content, and confirm a cache miss appears only when the declaration is stable and the task reads the file.