skip to content

Why do cacheable tasks need input path sensitivity and normalization, and how do you configure them?

level: middleimportance: must knowfreq 55%

answer

  1. relocatability across machines
  2. @PathSensitive RELATIVE/NAME_ONLY/NONE
  3. @Classpath ignores order+timestamps
  4. @CompileClasspath ignores non-ABI
  5. normalization { runtimeClasspath { ignore } }

basics

~10 s

Without normalization, absolute paths and irrelevant file metadata leak into the cache key, so entries miss across machines. @PathSensitive(RELATIVE) and classpath normalization make keys stable and relocatable.

solid answer

~50 s

A build-cache entry produced on one checkout or machine should be reusable elsewhere — that's **relocatability**. It only works if the cache key ignores machine-specific noise. **Path sensitivity** controls how a file input's *path* contributes to the key: `@PathSensitive(PathSensitivity.RELATIVE)` keys on the file's relative path plus content, `NAME_ONLY` on just the filename, `NONE` on content alone. The default for file inputs is conservative, so you usually annotate inputs with `@PathSensitive(RELATIVE)` to drop absolute prefixes. For classpaths, `@Classpath`/`@CompileClasspath` apply **classpath normalization** — ignoring jar entry order and timestamps, and for compile classpath, ignoring changes that don't affect ABI. You can further strip volatile resources project-wide via the `normalization { runtimeClasspath { ignore("build-info.properties") } }` block. Get this wrong and you get phantom cache misses (paths leak in) or, worse, false hits (too aggressive). Correct normalization is what turns a theoretically cacheable task into one that actually gets hits on CI.

code

kotlin · 17 lines
kotlin
@CacheableTask
abstract class Bundle : DefaultTask() {
    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val sources: ConfigurableFileCollection

    @get:Classpath
    abstract val runtimeCp: ConfigurableFileCollection

    @get:OutputFile
    abstract val bundle: RegularFileProperty
}

// build.gradle.kts (project level)
normalization {
    runtimeClasspath { ignore("META-INF/build-info.properties") }
}

go deeper

for a junior

Know that absolute paths break cross-machine caching and that @PathSensitive(RELATIVE) is the usual fix.

for a middle

Explain path sensitivity modes and classpath normalization, and pick the right one for a given input to get real hits.

for a senior

Diagnose phantom misses vs false hits, set project-level normalization rules, and balance strictness against correctness.

for a principal

Define normalization conventions across the build so shared/remote caches actually hit across teams and agents without correctness risk.

## The relocatability problem The build cache becomes valuable when entries are **shared** — across a developer's clean checkout, between branches, and especially across CI agents and developer machines. That sharing only happens if the **cache key is identical** for inputs that are semantically the same. The enemy is machine-specific noise: absolute paths (`/home/alice/...` vs `/Users/bob/...`), jar entry ordering, file timestamps, and embedded build metadata. If any of that leaks into the key, two machines compute different keys for the same logical input and never share a hit. ## Path sensitivity For file inputs, **how the path contributes** to the key is controlled by `@PathSensitive(PathSensitivity.X)`: - `ABSOLUTE` — full absolute path matters. Almost never relocatable; avoid. - `RELATIVE` — relative path (from the input root) + content. The common choice for sources, where directory structure is meaningful. - `NAME_ONLY` — only the file name + content. - `NONE` — only content; path irrelevant (e.g. a single config file whose location doesn't matter). ```kotlin @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val sources: ConfigurableFileCollection ``` Choosing `RELATIVE` over `ABSOLUTE` is usually the single change that makes a custom task's cache entries portable. ## Classpath normalization Classpaths get special treatment because their on-disk form is noisy: - `@Classpath` — runtime classpath normalization: ignores the *order* of entries and ignores timestamps inside jars, hashing by content of the contained files. - `@CompileClasspath` — additionally ignores changes that don't affect the **ABI** (e.g. method bodies, private members), so editing an implementation detail of an upstream module doesn't bust downstream compile-cache entries. ## Project-level normalization rules Some inputs contain inherently volatile data (a generated `build-info.properties` with a timestamp/commit). You can tell Gradle to ignore such resources on the runtime classpath so they don't defeat caching: ```kotlin normalization { runtimeClasspath { ignore("META-INF/build-info.properties") metaInf { ignoreAttribute("Implementation-Version") } } } ``` ## Failure modes - **Too strict** (e.g. `ABSOLUTE`, or not ignoring a timestamp file): phantom misses, no sharing across machines. - **Too loose** (e.g. `NONE` when path matters, or ignoring a resource that actually affects behavior): false hits, restoring outputs that are wrong for the real inputs. The goal is to normalize away *exactly* the noise that doesn't affect outputs and nothing more.

  • What is the difference between @Classpath and @CompileClasspath normalization?
    @Classpath (runtime) ignores entry order and intra-jar timestamps, hashing by content. @CompileClasspath additionally ignores non-ABI changes — method bodies, private members — so implementation-only edits upstream don't invalidate downstream compile caching.
  • When would NAME_ONLY or NONE path sensitivity be appropriate?
    NAME_ONLY when only the filename (not its directory) is meaningful; NONE when only the file's content matters and its location is irrelevant, e.g. a single config file passed by path that the task reads wholesale.

saying these in an interview costs you the question

  • Leaving file inputs at ABSOLUTE path sensitivity and then wondering why CI never gets cache hits.
  • Over-normalizing (NONE / ignoring meaningful resources) and causing false cache hits.
  • Thinking normalization affects up-to-date checks but not the cache key — it affects both.

context