How do you parameterize a TransformAction, and how should parameter properties be annotated so the transform caches correctly?
answer
- interface extends TransformParameters
- managed Property/ConfigurableFileCollection
- @Input / @InputFiles + @PathSensitive
- params feed the cache key
- TransformParameters.None -> getParameters() throws
basics
~10 sDefine an interface extending TransformParameters with Property/ConfigurableFileCollection fields, annotate them like task inputs (@Input, @InputFiles + @PathSensitive), and reference them via getParameters(). Use TransformParameters.None when there's nothing to configure.
solid answer
~40 sConfiguration that the action needs at runtime goes into a `Parameters` type — an interface extending `TransformParameters` with managed properties (`Property<T>`, `ConfigurableFileCollection`, etc.). You access them inside `transform()` via `parameters` (the `getParameters()` accessor). Crucially, parameter properties are **part of the cache key**, so they must carry the same normalization annotations as task inputs: - `@get:Input` for simple value properties. - `@get:InputFiles` (or `@get:InputFile`) plus a `@get:PathSensitive(...)` and often `@get:Classpath`/`@get:Normalize` for file inputs. If no configuration is needed, type the action as `TransformAction<TransformParameters.None>` and never call `getParameters()`. You wire actual values when registering the transform with `registerTransform(...) { parameters { ... } }`. Correct annotations matter: a missing `@PathSensitive` can make the cache key absolute-path-dependent and silently kill cache hits across machines.
code
kotlin · 14 linesinterface Params : TransformParameters {
@get:Input
val minify: Property<Boolean>
}
abstract class Shrink : TransformAction<Params> {
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val out = outputs.file("shrunk-" + inputArtifact.get().asFile.name)
if (parameters.minify.get()) shrink(inputArtifact.get().asFile, out)
else inputArtifact.get().asFile.copyTo(out)
}
}go deeper
Knows a transform can have parameters and that None means no config.
Can declare a TransformParameters interface with managed Property fields and read them via parameters.
Annotates each parameter correctly for normalization and explains how params feed the cache key.
Audits parameter annotations across a plugin's transforms to guarantee cross-machine cache hits and minimal key bloat.
## Why parameters exist A transform often needs configuration: a tool version, a flag, an auxiliary classpath. That configuration is shared across all invocations of the transform, but distinct configurations must produce distinct cache entries. Gradle models this with a typed `Parameters` object. ## Declaring the Parameters type ```kotlin interface Params : TransformParameters { @get:Input val toolVersion: Property<String> @get:InputFiles @get:PathSensitive(PathSensitivity.NONE) val auxClasspath: ConfigurableFileCollection } abstract class Instrument : TransformAction<Params> { @get:InputArtifact abstract val inputArtifact: Provider<FileSystemLocation> override fun transform(outputs: TransformOutputs) { val version = parameters.toolVersion.get() // ... } } ``` The interface uses Gradle's *managed properties* — abstract getters of `Property`, `ListProperty`, `ConfigurableFileCollection`, etc. Gradle generates the implementation; you never write a backing field. ## Annotations = cache correctness Every parameter property feeds the transform's cache key, exactly like task inputs feed a task's up-to-date check. So you annotate them the same way: - `@Input` — value-typed inputs (strings, numbers, enums, booleans). - `@InputFiles` / `@InputFile` + `@PathSensitive(...)` — file inputs, with the path-sensitivity that's correct for your use. `@Classpath` / `@CompileClasspath` for classpath-like file sets so order/normalization is handled. - `@Nested` — for nested annotated objects. Getting these wrong has real consequences. Omit `@PathSensitive` and the key may depend on absolute paths, so a teammate or CI with a different checkout path gets a cache miss. Mark something `@Input` that's actually a large file and you bloat the key. ## Accessing values Inside `transform()`, call `parameters` (Kotlin) / `getParameters()` (Java) to read the resolved values. They are already finalized — you just `.get()` the providers. ## The None case When the action needs no configuration: ```kotlin abstract class Unzip : TransformAction<TransformParameters.None> { @get:InputArtifact abstract val inputArtifact: Provider<FileSystemLocation> override fun transform(outputs: TransformOutputs) { /* ... */ } } ``` `TransformParameters.None` is a marker that signals "no parameters"; calling `getParameters()` then throws. ## Wiring values Values are bound where the transform is registered (the sibling 'registering' concern), e.g. `parameters { toolVersion.set("1.2") }`. The action class only *declares* the shape; registration supplies the data.
- What breaks if you forget @PathSensitive on a file-typed parameter?The cache key can become sensitive to absolute paths, so identical content checked out at different paths (CI vs local) yields cache misses and re-runs the transform unnecessarily.
- How do you express a transform that takes no configuration?Type it as TransformAction<TransformParameters.None> and never call getParameters() — doing so would throw.
- Where are the actual parameter values set?At registration time via registerTransform(...) { parameters { ... } }; the action class only declares the property shapes.
saying these in an interview costs you the question
- Annotating file parameters with @Input instead of @InputFiles + @PathSensitive.
- Calling getParameters() on a TransformParameters.None action.
- Adding a backing field to the Parameters interface instead of leaving getters abstract (managed property).