How does a ConfigurableFileCollection carry task dependencies, and when do you need builtBy()?
answer
- FileCollection is Buildable
- task output / provider → dep inferred
- raw path → no producer known
- builtBy(task) = manual escape hatch
- prefer provider wiring over builtBy
basics
~20 sA ConfigurableFileCollection tracks the tasks that produce its files. If you add a task output or a Provider that knows its producer, dependencies are inferred automatically. builtBy() is for the case where you add raw files whose producing task Gradle can't infer.
solid answer
~40 sA `ConfigurableFileCollection` is *buildable*: it can carry implicit task dependencies so that whoever consumes it (e.g. as a task input) automatically runs the tasks that generate those files first. When you populate it from a **task output property**, a `Provider` derived from one (like `layout.buildDirectory.file(...)` wired through a task), or another buildable `FileCollection`, Gradle infers the producer automatically — no extra wiring. You only need **`builtBy(Object...)`** when you add *plain* files or paths that Gradle cannot trace back to a producing task — for example a hard-coded `File` in `build/` that some task writes. `builtBy(task)` then declares that this collection's contents are produced by `task`, so consumers get the correct ordering. Prefer wiring real provider chains over `builtBy`, which is essentially a manual escape hatch.
code
kotlin · 8 linesval gen = tasks.register<GenerateTask>("gen")
val inputs = project.objects.fileCollection()
// inferred dependency on 'gen':
inputs.from(gen.flatMap { it.output })
// raw path needs explicit builtBy:
inputs.from("build/generated/legacy.txt").builtBy("gen")go deeper
Awareness that file collections can trigger producing tasks; builtBy exists for manual cases.
Explain automatic inference from task outputs/providers vs needing builtBy for raw paths.
Contrast provider wiring with builtBy, explain why inference is more robust, and trace why a producer might fail to run.
Establish a no-builtBy-by-default policy: outputs flow through provider chains so the task graph stays correct under refactoring.
## Buildable collections Many Gradle types are **buildable**: they implement `Buildable` / expose a `TaskDependency` so the dependency graph can ask "which tasks must run to produce you?". A `ConfigurableFileCollection` is one of them. ## Automatic inference If you populate the collection from something that *knows its producer*, the dependency is inferred for free: - a task's output `RegularFileProperty` / `DirectoryProperty`, - a `Provider` that was derived from a task output (e.g. `someTask.flatMap { it.outputFile }`), - another buildable `FileCollection` (e.g. a `Configuration`), - `tasks.named("gen").map { it.outputs.files }`. ```kotlin val gen = tasks.register<GenerateTask>("gen") val inputs = objects.fileCollection() inputs.from(gen.flatMap { it.output }) // dependency on 'gen' inferred ``` When `inputs` is later used as another task's `@InputFiles`, Gradle runs `gen` first — automatically. ## When inference fails: builtBy() If you add a **raw** path with no traceable producer, Gradle sees files but not the task that makes them: ```kotlin inputs.from("build/generated/out.txt") // who creates this? Gradle can't tell ``` In that case, declare it explicitly: ```kotlin inputs.from("build/generated/out.txt").builtBy("gen") // or: inputs.builtBy(genTaskProvider) ``` `builtBy(Object...)` accepts task names, `Task` objects, `TaskProvider`s, or anything `tasks` can resolve. Now consumers of `inputs` will run `gen` first. ## Prefer wiring over builtBy `builtBy` is a manual override and is easy to get wrong (typo a task name, forget to update it). The idiomatic approach is to **wire provider chains**: expose the producing task's output as a `Property`/`Provider` and `from(...)` that provider, letting inference do the work. Reserve `builtBy` for legacy code, third-party outputs, or files genuinely outside the provider graph. ## Querying dependencies You can introspect with `collection.buildDependencies` (a `TaskDependency`), which is what the executor uses internally to compute ordering.
- Why is wiring a Provider preferred over calling builtBy with a task name?A Provider carries both the file location and its producer, so the dependency is type-checked and stays correct under refactors. builtBy with a string task name is unchecked, easy to typo, and silently rots if the task is renamed.
- If a consumer task's @InputFiles points at a collection built from a task output but the producer never runs, what likely went wrong?The collection was probably populated from a resolved value (e.g. someTask.get().output or a raw File) rather than a lazy Provider, so the producer link was lost. Re-wire with a Provider/flatMap so the dependency is inferred.
saying these in an interview costs you the question
- Claiming you always need builtBy — most cases infer dependencies automatically.
- Using builtBy as the default instead of wiring provider chains.
- Saying a FileCollection cannot carry task dependencies at all.