How do you expose a ConfigurableFileCollection as a managed @InputFiles property on a custom task instead of an eager FileCollection?
answer
- abstract val : ConfigurableFileCollection
- @get:InputFiles + @PathSensitive
- Gradle injects the instance
- callers use from()/setFrom()
- lazy → up-to-date + config cache
basics
~10 sDeclare an abstract getter returning ConfigurableFileCollection, annotated with @InputFiles. Gradle manages the instance for you, so you never create or assign it — callers just use from() to set sources.
solid answer
~40 sOn a custom task, declare the input as an **abstract** read-only getter whose type is `ConfigurableFileCollection`, annotated with `@InputFiles` (and usually a path-sensitivity annotation). Because the getter is abstract, Gradle's managed-property machinery instantiates and injects the collection — you never call `objects.fileCollection()` or assign it yourself. Callers configure it with `task.inputs.from(...)` via the property's `from`/`setFrom`. This is superior to holding an eager `FileCollection`: the value stays lazy, integrates with up-to-date checking and the build/configuration cache, and avoids resolving paths at configuration time. Combine `@InputFiles` with `@PathSensitive(PathSensitivity.RELATIVE)` (or NAME_ONLY) so relocating the project doesn't bust the cache, and use `@SkipWhenEmpty` if the task should skip when there are no inputs.
code
kotlin · 11 linesabstract class BundleTask : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val sources: ConfigurableFileCollection
@get:OutputFile
abstract val archive: RegularFileProperty
@TaskAction
fun run() = sources.files.forEach { /* bundle it */ }
}go deeper
Know the pattern: abstract ConfigurableFileCollection getter annotated @InputFiles, Gradle creates it.
Explain why abstract/managed beats an eager field and pair @InputFiles with @PathSensitive and @SkipWhenEmpty.
Discuss cache-key implications, @Classpath vs @InputFiles, and how laziness preserves configuration-cache compatibility.
Set authoring standards: all task inputs as managed lazy types with explicit normalization, so the org's custom tasks are reliably cacheable.
## Managed properties Gradle can **manage** a task/extension property for you if you declare it as an `abstract` getter of a supported lazy type. For files, that type is `ConfigurableFileCollection`. You don't write a field, a constructor, or a factory call — Gradle generates the implementation and injects an instance backed by the `ObjectFactory`. ```kotlin abstract class BundleTask : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val sources: ConfigurableFileCollection @get:OutputFile abstract val archive: RegularFileProperty @TaskAction fun run() { sources.files.forEach { /* ... */ } } } ``` Usage: ```kotlin tasks.register<BundleTask>("bundle") { sources.from("src/main/resources") sources.from(layout.buildDirectory.dir("generated")) archive.set(layout.buildDirectory.file("out/bundle.zip")) } ``` ## Why not an eager FileCollection field? If you wrote `val sources: FileCollection = project.files(...)` in a constructor, you'd: - resolve sources too early (breaking the configuration cache and forcing eager evaluation), - couple the task to `Project`, - lose the clean lazy-wiring story. A managed `ConfigurableFileCollection` defers everything and lets Gradle track it for **incremental builds**. ## Annotations that pair with @InputFiles - **`@PathSensitive(PathSensitivity.RELATIVE | NAME_ONLY | NONE)`** — controls which part of file paths feeds into the up-to-date / cache key. RELATIVE is the common, relocatable choice. - **`@SkipWhenEmpty`** — skip the task when the collection has no files. - **`@IgnoreEmptyDirectories`**, **`@InputDirectory`** for the single-dir case. - For classpaths specifically, prefer **`@Classpath`** / **`@CompileClasspath`** which apply ordering/normalization semantics. ## Querying inside the action Inside `@TaskAction`, call `sources.files` (a `Set<File>`), iterate, or use `.asFileTree`. Resolution happens then, at execution time — exactly where you want it. ## Key takeaway Abstract getter + `ConfigurableFileCollection` + `@InputFiles` = a lazy, cache-aware, Gradle-managed input that needs no manual instantiation.
- Why does the getter have to be abstract for Gradle to manage it?Abstract signals to Gradle's bytecode-generation that it should supply the implementation and inject an ObjectFactory-backed instance. A concrete getter would mean you take responsibility for creating and returning the value yourself.
- What does @PathSensitive(RELATIVE) buy you over the default?It makes up-to-date checks and the build cache key depend only on relative paths and content, so moving the project to a different absolute directory (or another machine) still hits the cache instead of re-running the task.
- When would you choose @Classpath over @InputFiles for a file collection?When the files represent a classpath: @Classpath applies classpath normalization (ignores timestamps/order-insensitive jar internals appropriately) so irrelevant changes don't bust the cache, which plain @InputFiles wouldn't handle as well.
saying these in an interview costs you the question
- Creating the collection with objects.fileCollection() in the task constructor instead of leaving the getter abstract.
- Using @Input instead of @InputFiles for a file collection.
- Forgetting path sensitivity, so absolute-path changes needlessly invalidate the cache.