skip to content

How can consuming a task's outputs (task.outputs.files / outputs as a FileCollection) infer a dependency without naming a specific output property?

level: middleimportance: should knowfreq 35%

answer

  1. task.outputs.files = FileCollection built-by task
  2. inputs.files(producer) infers edge
  3. builtBy attaches deps to any collection
  4. TaskProvider accepted by files()
  5. Set<File> loses built-by

basics

~10 s

A 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 s

Every 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 lines
kotlin
val 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

for a junior

Know inputs.files(producerTask) makes Gradle run the producer first.

for a middle

Explain that outputs.files is a built-by FileCollection, so passing it (or the TaskProvider) as an input infers the edge; .files loses it.

for a senior

Contrast collection-level vs property-level inference and choose the precise one; explain builtBy for arbitrary collections.

for a principal

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.

context