skip to content

How do you derive one task's input file from another task's RegularFileProperty output so Gradle infers the task dependency automatically?

level: seniorimportance: should knowfreq 35%

answer

  1. set input = producer output Provider
  2. flatMap over TaskProvider
  3. implicit/inferred task dependency
  4. no dependsOn needed
  5. bare File loses producer link

basics

~10 s

Set the consumer's input property to the producer's output Provider: consumer.input.set(producer.flatMap { it.outputFile }). Because the value is a Provider carrying task info, Gradle wires the dependency automatically — no dependsOn needed.

solid answer

~40 s

When you set a task's `RegularFileProperty`/`DirectoryProperty` input from another task's *output Property*, Gradle records the producing task inside the Provider. Then when the consumer is built, Gradle automatically adds the dependency — you don't write `dependsOn`. The idiom uses `flatMap` over a `TaskProvider`: `consumer.configure { source.set(producerProvider.flatMap { it.outputFile }) }`. Here `producerProvider` is the `TaskProvider<Produce>` from `tasks.register`, and `outputFile` is its `RegularFileProperty`. The resulting `Provider<RegularFile>` carries both the value and the task that produces it ('task dependency inference' or 'implicit task dependency'). This only works if the producer's location came from a tracked output property; a bare `File` carries no task info and would need explicit `dependsOn`. Keep everything lazy: register tasks with `tasks.register`, wire with Providers, and let Gradle compute the order.

code

kotlin · 7 lines
kotlin
val produce = tasks.register<Produce>("produce") {
    outputFile.set(layout.buildDirectory.file("gen/data.txt"))
}
tasks.register<Consume>("consume") {
    source.set(produce.flatMap { it.outputFile })   // wires dependency
    result.set(layout.buildDirectory.file("gen/count.txt"))
}

go deeper

for a junior

Know that setting an input from another task's output Provider links the tasks without dependsOn.

for a middle

Use flatMap over a TaskProvider to wire the file and explain implicit dependency inference.

for a senior

Explain map vs flatMap, why bare Files break inference, and apply the same to DirectoryProperty.

for a principal

Establish provider-wiring conventions so multi-task pipelines self-order and stay cache/incremental-correct without manual dependsOn.

## The goal: implicit task dependencies Gradle can infer that task B must run after task A *purely from data flow*: if B's input Property is set to A's output Property (wrapped in a Provider), Gradle sees the producer embedded in the Provider and orders A before B automatically. This is cleaner and safer than manual `dependsOn`, which only sequences and doesn't carry the file value. ## The producing task ```kotlin abstract class Produce : DefaultTask() { @get:OutputFile abstract val outputFile: RegularFileProperty @TaskAction fun run() = outputFile.get().asFile.writeText("data") } val produce = tasks.register<Produce>("produce") { outputFile.set(layout.buildDirectory.file("gen/data.txt")) } ``` `produce` is a `TaskProvider<Produce>` — itself lazy, so the task isn't created until needed. ## The consuming task — flatMap the producer ```kotlin abstract class Consume : DefaultTask() { @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val source: RegularFileProperty @get:OutputFile abstract val result: RegularFileProperty @TaskAction fun run() { val n = source.get().asFile.readText().length result.get().asFile.writeText(n.toString()) } } tasks.register<Consume>("consume") { // flatMap because outputFile is itself a Provider (RegularFileProperty) source.set(produce.flatMap { it.outputFile }) result.set(layout.buildDirectory.file("gen/count.txt")) } ``` Running `consume` automatically runs `produce` first — **no `dependsOn`**. The `flatMap` is required because `produce.flatMap { ... }` maps over the `TaskProvider` and `it.outputFile` is *itself* a Provider; `map` would give you `Provider<RegularFileProperty>`, while `flatMap` flattens to `Provider<RegularFile>`. ## Why a bare File breaks inference If `produce` had written to `File(buildDir, "data.txt")` and you set `source.set(layout.projectDirectory.file(...))` with no link to the producer, Gradle has no way to know `produce` made it — you'd need explicit `dependsOn`. The whole point of typed output Properties is to carry that producer link. ## Directory variant Same pattern with `DirectoryProperty` + `@OutputDirectory`/`@InputDirectory`, and navigate children with `produce.flatMap { it.outputDir.file("child.txt") }`. ## Rules of thumb - Producer: expose a `RegularFileProperty`/`DirectoryProperty` annotated as output. - Consumer: set its input Property to `producerProvider.flatMap { it.output }`. - Never resolve to `File` between the two — keep it a Provider so the dependency survives.

  • Why flatMap instead of map when wiring produce.outputFile?
    outputFile is itself a Provider (RegularFileProperty). map would yield Provider<RegularFileProperty>; flatMap flattens to Provider<RegularFile>, the value you actually want.
  • Do you still need dependsOn after wiring outputs to inputs this way?
    No. The Provider carries the producing task, so Gradle infers the dependency. dependsOn would be redundant.
  • What happens if the producer wrote to a plain File with no output Property?
    Gradle can't infer the dependency; you'd have to add explicit dependsOn, and you lose lazy relocation/tracking benefits.

saying these in an interview costs you the question

  • Adding dependsOn AND wiring providers (redundant) while believing the dependsOn is what links them.
  • Using map instead of flatMap and being surprised by Provider<RegularFileProperty>.
  • Resolving to .get().asFile at configuration time, which severs the producer link and breaks inference.

context