How do you correctly produce derived files inside a TransformAction using TransformOutputs, and what rule governs where those files may live?
answer
- outputs.file vs outputs.dir
- relative path -> managed workspace
- absolute File must equal input (identity)
- registration order preserved
- arbitrary path = error / breaks cache
basics
~20 sCall outputs.file(name) or outputs.dir(name) to get a location, then write your derived content there. Outputs must be either the input artifact itself or a path under the directory Gradle gives you — never an arbitrary location.
solid answer
~40 s`TransformOutputs` is the only sanctioned way to declare a transform's results. Two methods matter: - `outputs.file(path)` — registers a regular file output and returns the `File` you write to. - `outputs.dir(path)` — registers a directory output; Gradle creates it and you fill it. The critical rule: an output location must be **either** the input artifact itself (identity/pass-through) **or** a relative path resolved inside the transform's own output directory that Gradle provisions. You cannot point an output at some arbitrary absolute path. If you pass a relative name, Gradle places it under a managed workspace; if you pass an absolute `File`, it must equal the input artifact. This guarantees outputs are content-addressable and cacheable. Order matters too: the order in which you register outputs is the order consumers see them.
code
kotlin · 9 linesoverride fun transform(outputs: TransformOutputs) {
val input = inputArtifact.get().asFile
if (input.name.endsWith("-no-op.jar")) {
outputs.file(input) // identity / pass-through
return
}
val dir = outputs.dir("unzipped") // relative -> workspace
unzipTo(input, dir)
}go deeper
Knows to call outputs.file/outputs.dir and write into the returned File.
States the location rule (input itself or relative workspace path) and that multiple outputs are allowed.
Connects the workspace-ownership rule to content-addressed caching and build-cache replay.
Reasons about transform chains and how identity pass-through interacts with multi-step derivation and cache reuse across modules.
## TransformOutputs in depth `TransformOutputs` is the callback object Gradle hands to `transform()`. Every file or directory your transform produces **must** be announced through it; files you create elsewhere are invisible to resolution and break caching. ### The two registration methods ```kotlin override fun transform(outputs: TransformOutputs) { val input = inputArtifact.get().asFile // a directory output val classesDir = outputs.dir("classes") extractInto(input, classesDir) // a file output val manifest = outputs.file("manifest.txt") manifest.writeText("name=" + input.name) } ``` - `outputs.dir(name)` returns a `File` pointing at a directory Gradle has created for you; populate it. - `outputs.file(name)` returns a `File`; you write the content. ### The location rule An output **must** be one of: 1. **The input artifact itself** — `outputs.file(inputArtifact.get().asFile)`. This is the identity case: the transform passes the artifact through unchanged (common when one transform in a chain is a no-op for certain inputs). 2. **A path relative to the transform's output directory** — when you pass a *relative* name like `"classes"`, Gradle resolves it inside a managed, content-addressed workspace dedicated to this invocation. Passing an absolute `File` that is neither the input nor inside the workspace is an error. The reason is determinism and caching: Gradle must own the output location so it can hash, store, and replay it from the cache. If you wrote to `/tmp/whatever`, Gradle could neither track nor reuse it. ### Ordering and multiplicity A single input artifact can yield **many** outputs — call `file`/`dir` repeatedly. The registration order is preserved and is what downstream consumers iterate. Producing zero outputs filters the artifact away entirely. ### Why this enables caching Because the output lives in a Gradle-owned workspace and the inputs (input artifact content + normalized parameters) are hashed, Gradle stores the result keyed by that hash. The next build — or another project, via the build cache — skips re-running the action and replays the stored outputs. Writing outside the workspace would defeat all of this.
- Can a single input artifact produce multiple outputs?Yes — call outputs.file/dir as many times as needed; the registration order is what consumers iterate over.
- What's the one valid absolute-path output?The input artifact itself, via outputs.file(inputArtifact.get().asFile) — the identity/pass-through case.
- Why can't you just write to /tmp and ignore TransformOutputs?Gradle wouldn't track, hash, or cache those files; resolution would miss them and the result couldn't be replayed from the build cache.
saying these in an interview costs you the question
- Writing derived files to a hand-picked absolute path instead of the location TransformOutputs returns.
- Believing only one output per input is allowed.
- Thinking outputs.dir requires you to mkdir it yourself — Gradle creates it.