How do you supply dynamic, cacheable compiler arguments to a JavaCompile task using a CommandLineArgumentProvider, and how does it differ from options.compilerArgs?
answer
- options.compilerArgs = opaque strings
- options.compilerArgumentProviders = @Nested, tracked
- annotation-processor -A options pointing at files
- @InputFile/@Classpath + @PathSensitive(NONE)
- static flags fine as compilerArgs
basics
~10 sAdd a CommandLineArgumentProvider to JavaCompile's options.compilerArgumentProviders. Like raw options.compilerArgs the strings reach javac, but the provider lets you annotate underlying file/value inputs so up-to-date checks and the build cache stay correct.
solid answer
~40 s`JavaCompile` exposes `options.compilerArgs` (a plain `List<String>`) and `options.compilerArgumentProviders` (a `List<CommandLineArgumentProvider>`). Both feed arguments to `javac`, but they differ in input tracking. `options.compilerArgs` are opaque strings. If a compiler argument references a file — e.g. an annotation-processor config, a module-path entry, or `-Aopt=/path/to/file` — Gradle has no idea that file is an input, so changing it won't recompile and the build cache can return stale `.class` files. With a provider added to `compilerArgumentProviders`, you declare the underlying file/value as an annotated nested input (`@InputFile`, `@Classpath`, `@Input` with proper `@PathSensitive`), and emit the actual `-A.../path` argument from `asArguments()`. Now the compile task's fingerprint includes that input, so edits trigger recompilation and the cache key is correct and relocatable. This is the canonical way to wire annotation-processor parameters that point at files.
code
kotlin · 15 linesabstract class AptArgs : CommandLineArgumentProvider {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mappingFile: RegularFileProperty
override fun asArguments(): Iterable<String> =
listOf("-Amapping=${mappingFile.get().asFile.absolutePath}")
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-Xlint:all") // static literal: fine
val apt = objects.newInstance(AptArgs::class.java)
apt.mappingFile.set(layout.projectDirectory.file("apt/mapping.properties"))
options.compilerArgumentProviders.add(apt) // file-backed: tracked
}go deeper
Know JavaCompile has options.compilerArgs for flags; awareness that a provider exists for file-backed args is a bonus.
Distinguish compilerArgs (opaque) from compilerArgumentProviders (tracked) and give the APT-options use case.
Model processor/module-path via @Classpath, reason about stale-bytecode/relocatability risk, and choose the right knob per argument.
Set a standard that any file-dependent compiler argument goes through a provider, protecting cache correctness on the build's heaviest tasks across the org.
## The two knobs on JavaCompile - `options.compilerArgs: List<String>` — literal arguments handed to `javac`. Simple, but opaque to input tracking. - `options.compilerArgumentProviders: List<CommandLineArgumentProvider>` — `@Nested` collection; each provider's annotated properties join the compile task's input fingerprint. Both ultimately append to the `javac` command line. The difference is *correctness*, not capability. ## When compilerArgs is enough Static flags with no file/identity meaning are fine as plain strings: `options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))`. These are value inputs Gradle already fingerprints as part of `compilerArgs` itself, and they carry no machine-specific path. ## When you need a provider Reach for `compilerArgumentProviders` whenever an argument depends on a **file** or a **computed value that must be tracked**, most commonly annotation-processor options: ```kotlin abstract class AptArgs : CommandLineArgumentProvider { @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val mappingFile: RegularFileProperty override fun asArguments(): Iterable<String> = listOf("-Amapping=${mappingFile.get().asFile.absolutePath}") } tasks.withType<JavaCompile>().configureEach { val apt = objects.newInstance(AptArgs::class.java) apt.mappingFile.set(layout.projectDirectory.file("apt/mapping.properties")) options.compilerArgumentProviders.add(apt) } ``` Now editing `mapping.properties` recompiles, and because the file is tracked by content (`@PathSensitive(NONE)`) while the absolute path lives only in the emitted string, the compile task stays cacheable and relocatable across machines. ## Module path / processor path nuance For jar/dir collections feeding `--processor-path` or `--module-path`, prefer `@Classpath` (content-normalized, order-insensitive where appropriate) so the fingerprint is stable. Don't list them in `compilerArgs` as raw absolute paths — that both under-tracks content and over-tracks location. ## Why this matters more for compile tasks `JavaCompile` is one of the heaviest cached tasks in a typical build; a stale cache hit there silently ships wrong bytecode, and a false cache miss costs real time on every CI run. Correct input modeling via providers is what makes incremental + cached compilation trustworthy. ## Summary - Static literal flags → `options.compilerArgs`. - File-dependent or value-tracked args (APT options, generated configs) → `options.compilerArgumentProviders` with annotated inputs. - Jar/dir collections → `@Classpath` inside the provider.
- Why is a raw -Aconfig=/abs/path string in options.compilerArgs dangerous?Gradle treats it as an opaque value: it doesn't see the file as an input (so edits won't recompile and a cache hit can be stale) and the absolute path pollutes the input value, hurting relocatable caching. A provider with @InputFile fixes both.
- How would you wire a processor path so it's content-tracked?Put the jar/dir collection on the provider as a `@Classpath`-annotated `ConfigurableFileCollection` and emit the `--processor-path` argument from asArguments(). @Classpath normalizes by content and is relocatable.
- Are static flags like -Werror better as compilerArgs or a provider?compilerArgs. They're pure literals with no file identity or machine-specific path, so Gradle already fingerprints them correctly and a provider adds no value.
saying these in an interview costs you the question
- Putting file-referencing -A options as raw strings in options.compilerArgs.
- Listing processor/module-path jars as absolute-path strings instead of @Classpath inputs.
- Claiming compilerArgumentProviders changes what javac receives versus compilerArgs (it doesn't — only how inputs are tracked).