How can consuming a task's outputs (task.outputs.files / outputs as a FileCollection) infer a dependency without naming a specific output property?
answer
- task.outputs.files = FileCollection built-by task
- inputs.files(producer) infers edge
- builtBy attaches deps to any collection
- TaskProvider accepted by files()
- Set<File> loses built-by
basics
~10 sA task's outputs (task.outputs.files) form a FileCollection that is built-by that task. Passing it as another task's input — e.g. inputs.files(producer) — makes Gradle infer the dependency, even without referencing a named output property.
solid answer
~40 sEvery task exposes `task.outputs.files`, a `FileCollection` that Gradle marks as *built by* the producing task. FileCollections carry their build dependencies, so when you feed a producer's outputs into a consumer — `consumer.inputs.files(producer)` or `inputs.files(producer.map { it.outputs.files })` — Gradle records that resolving those files requires the producer to run, and infers the edge. You don't have to reference a specific `@OutputFile` property; the whole output set works. This is the looser, collection-level cousin of property-to-property wiring. Passing a `TaskProvider` directly to `files(...)`/`inputs.files(...)` is shorthand that resolves to the task's outputs and carries the dependency. As always, avoid eagerly resolving to a plain `Set<File>`, which would drop the built-by metadata.
code
kotlin · 8 linesval generate = tasks.register<GenerateSources>("generateSources")
tasks.register("bundle") {
// consume ALL of generate's outputs; dependsOn(generate) inferred
inputs.files(generate)
val out = layout.buildDirectory.file("bundle.zip")
outputs.file(out)
doLast { /* read inputs.files, write bundle */ }
}go deeper
Know inputs.files(producerTask) makes Gradle run the producer first.
Explain that outputs.files is a built-by FileCollection, so passing it (or the TaskProvider) as an input infers the edge; .files loses it.
Contrast collection-level vs property-level inference and choose the precise one; explain builtBy for arbitrary collections.
Guide when aggregators should consume whole output sets vs specific artifacts to keep graphs tight and cacheable.
## FileCollections carry build dependencies A Gradle `FileCollection` is more than a set of files — it can also know **which tasks produce those files**. This is the 'built-by' relationship. `task.outputs.files` is exactly such a collection: it is implicitly *built by* its owning task. ## Inferring without a named property Property-to-property wiring (`consumer.input.set(producer.flatMap { it.out })`) needs you to know the specific output property. Sometimes you just want *everything the producer emits*. Then use the output **collection**: ```kotlin tasks.register("package") { // all of generate's outputs become inputs; dependency inferred inputs.files(generate) // shorthand for generate.outputs.files doLast { /* zip up the files */ } } ``` Gradle sees that the input FileCollection is built by `generate` and adds the edge. ## What 'built by' means You can even attach build dependencies to an arbitrary collection with `builtBy`: ```kotlin val files = files("a.txt", "b.txt").builtBy(generate) ``` but for task outputs it's automatic — `outputs.files` is pre-tagged with the owning task. ## Passing a TaskProvider directly `files(taskProvider)` and `inputs.files(taskProvider)` accept a `TaskProvider` and resolve to that task's outputs, carrying the dependency. This is the most concise inferring form when you don't care which specific output property is involved. ## When to prefer property wiring instead Collection-level inference grabs *all* outputs; property wiring is more precise and also passes the exact typed value (a single `RegularFile`/`Directory`) downstream. Use property wiring when a consumer needs one specific artifact; use `outputs.files` when it genuinely consumes the producer's whole output set (e.g. an aggregator/packager). ## The pitfall ```kotlin // BAD: eager resolution loses built-by val f: Set<File> = generate.get().outputs.files.files consumer.inputs.files(f) // no dependency! ``` `.files` (the `Set<File>`) is just paths; it forgot who built them. Keep the `FileCollection` (or the `TaskProvider`) and let Gradle infer.
- What is the difference between inputs.files(producer) and inputs.files(producer.get().outputs.files.files)?The first passes a built-by FileCollection/TaskProvider, so Gradle infers the dependency. The second eagerly extracts a Set<File>, which carries no producer info — no dependency is inferred and you risk reading not-yet-produced files.
- When would you still use property wiring instead of outputs.files?When the consumer needs one specific typed artifact (a single RegularFile or Directory) rather than the producer's whole output set; property wiring is more precise and passes the exact value.
saying these in an interview costs you the question
- Resolving outputs to a Set<File> and expecting the dependency to survive.
- Believing you must reference a named @OutputFile to get inference.
- Using builtBy as a substitute for proper provider wiring when a specific value is needed.