You're authoring a custom task that takes a set of jars. How do you decide between @Classpath, @CompileClasspath, and @InputFiles with @PathSensitive for that property?
answer
- is it an ordered classpath?
- API only → @CompileClasspath
- runtime bytes → @Classpath
- file set → @InputFiles + @PathSensitive
- weakest correct path sensitivity; avoid ABSOLUTE
basics
~20 sUse @CompileClasspath if you only need the jars' public API, @Classpath if you need real runtime bytes but order matters and names/paths don't, and @InputFiles with an explicit @PathSensitive (RELATIVE/NAME_ONLY/NONE) for non-classpath file inputs where order is irrelevant.
solid answer
~50 sThe decision hinges on **what semantics the jars carry** for your task: - **@CompileClasspath** — you compile/analyze against the *API* only. Most relocatable and cache-friendly; impl-only upstream changes won't invalidate you. Order respected, names/paths and method bodies ignored. - **@Classpath** — you need the *actual runtime contents* (packaging a fat jar, running, classloading). Order is significant; names/paths ignored; full jar-entry content hashed. - **@InputFiles + @PathSensitive(RELATIVE | NAME_ONLY | NONE)** — the files are *not* an ordered classpath; they're just a file set. Choose the weakest path sensitivity that's still correct: `NONE` (content only), `NAME_ONLY`, or `RELATIVE` (relative layout matters). Avoid the `ABSOLUTE` default — it breaks relocatability. Rule of thumb: reach for the **most permissive** annotation that's still correct, because more normalization → more cache hits. Classpath annotations also let you apply settings-level runtime-classpath normalization on top.
code
kotlin · 14 linesabstract class AnalyzeApis : DefaultTask() {
@get:CompileClasspath
abstract val apiJars: ConfigurableFileCollection
@get:Classpath
abstract val runtimeJars: ConfigurableFileCollection
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val configFiles: ConfigurableFileCollection
@get:OutputDirectory
abstract val report: DirectoryProperty
}go deeper
Pick @Classpath for jars consumed as a classpath; know @InputFiles exists for plain files.
Distinguish @CompileClasspath vs @Classpath by API-vs-runtime need and set explicit @PathSensitive.
Apply the 'most permissive correct' principle and justify the relocatability/correctness trade-off.
Define authoring guidelines so teams' custom tasks are cacheable and relocatable by default.
## The core question: is it a classpath? A *classpath* has two semantic properties — **order matters**, **names/paths don't**. If your jars behave that way (they're consumed as a JVM/compiler classpath), use a classpath annotation. If order is irrelevant and they're just an arbitrary file set, use `@InputFiles` and pick a path sensitivity. ## The choices ### @CompileClasspath Fingerprints only the **ABI** (public signatures, inlined constants, visible annotations). Best for tasks that merely compile or do API-level analysis against the jars. Maximum normalization → fewest invalidations. ### @Classpath Fingerprints the **full logical contents** of each jar entry, preserves order, ignores names/paths. Use when the runtime bytes genuinely matter: assembling distributions, running the code, generating something that depends on implementation, not just signatures. ### @InputFiles with @PathSensitive For a non-ordered file collection. `@PathSensitive` controls how much of the path is part of the fingerprint: - `NONE` — only file **content** matters; paths fully ignored. - `NAME_ONLY` — file name + content. - `RELATIVE` — path relative to the input root + content (good for directory trees where layout is meaningful). - `ABSOLUTE` — the default; full absolute path. **Avoid** for cacheable/relocatable tasks. ## Decision flow ``` Is it consumed as an ordered classpath? ├── yes │ ├── need only the API? -> @CompileClasspath │ └── need runtime bytes? -> @Classpath └── no (just a file set) -> @InputFiles + @PathSensitive(weakest that is still correct: NONE < NAME_ONLY < RELATIVE) ``` ## Why 'most permissive that's correct' Every bit of path/name/body you let into the fingerprint is another way to get a needless cache miss or out-of-date result. The relocatable, normalized annotations (`@CompileClasspath` > `@Classpath` > `@InputFiles NONE` ...) maximize cache hits — but only choose one that doesn't *under*-fingerprint, or you risk stale outputs. ## Code ```kotlin abstract class AnalyzeApis : DefaultTask() { @get:CompileClasspath abstract val apiJars: ConfigurableFileCollection @get:Classpath abstract val runtimeJars: ConfigurableFileCollection @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val configFiles: ConfigurableFileCollection @get:OutputDirectory abstract val report: DirectoryProperty } ``` Pair classpath inputs with settings-level `normalization { runtimeClasspath { ignore(...) } }` to strip any residual volatility.
- Why prefer @PathSensitive(NONE) over the default when only content matters?The default is ABSOLUTE, which bakes machine-specific paths into the fingerprint and breaks relocatability/cache sharing. NONE fingerprints content only, maximizing cache hits while staying correct.
- Can you layer runtime-classpath normalization on a @Classpath property?Yes. settings-level `normalization { runtimeClasspath { ignore(...) } }` applies to runtime classpath fingerprints, letting you strip volatile entries even after choosing @Classpath.
saying these in an interview costs you the question
- Defaulting to @InputFiles with the implicit ABSOLUTE sensitivity for cacheable tasks.
- Using @CompileClasspath when the task actually needs runtime bytes (under-fingerprinting → stale outputs).
- Treating an unordered file set as a classpath and silently relying on order.