Consuming the output from task that produces a single file


I’m trying to clean up a plugin that I’m writing. Specifically I’m trying to follow Luke Daley’s advice from the mailing list which he also mentions in his Plugin Best Practices presentation (34:30):

If you have one task consuming the outputs of another, you are best off wiring them together directly so the task dependency can be inferred.

While implementing this I ran into some surprising behaviour (for me at least).

If there is anything I can do to help please let me know.

Conceptual Question

How do you consume the output of a task that produces a single file? E.g. how do I write a task that relies on the ‘.war’ file produced by the war task?

Technical questions

Why does the ‘consumeSingleFile’ task not infer the dependency on the ‘producer’ task but ‘consumeCollection’ does?

E.g. the output of this command: ‘gradle tasks --all’ is

Other tasks





Is that expected behaviour?


task producer {
    outputs.file file("somefile.txt")
  task consumeSingleFile(type: ConsumeSingleFile) {
    inputFile = producer.outputs.files[0]
  task consumeCollection(type: ConsumeCollection) {
    inputFiles = producer.outputs.files
  public class ConsumeSingleFile extends DefaultTask {
    @InputFile File inputFile
    void run() { }
  public class ConsumeCollection extends DefaultTask {
    @InputFiles FileCollection inputFiles
    void run() { }

Other surprises

  • There are ‘@InputFile’ and ‘@OutputFile’ annotations but no support for single files in ‘TaskOutputs’ - If, like me, you initially assume ‘TaskOutputs’ has single file support then the ‘file()’, ‘files()’ and ‘getFiles()’ methods (when called groovy style without ‘get’) are a bit confusing. Especially since ‘file()’/‘files()’ return ‘TaskOutputs’. Only after reading the JavaDoc on the interface (opposed to looking at ‘DefaultTaskOutputs’) it became clear to me.

The key point to understand is that the inferred dependency is triggered by an input of type Buildable. TaskOutputs.files() returns a FileCollection which is a Buildable and therefore gradle can interrogate it for the task that it is built by and infer the dependency. TaskOutputs.files()[0], on the other hand, returns a File object which is not Buildable.

@InputFile and @OutputFile are short hand annotations for adding things to the input and output file collections, but tasks always output a file collection, even if there is only one file in the collection. That collection is what allows for the auto-wired dependency.

Thanks Gary, that clears up my technical question on how dependencies are inferred when tasks are wired together.

But can you please help me to understand the best approach to write a custom task that consumes a single input file?

It seems to me that the ‘@InputFile’ annotation is incompatible with the concepts of task wiring and dependency inference?

In mind there are two options:

  1. Annotate the input property with ‘@InputFile File input’
  • Pro: The code is expressive and self descriptive. It shows that the task operates on a single input file.

  • Con: I can’t follow Luke’s best practice advice of wiring tasks together and let the dependencies be inferred 2. Annotate the the input property with ‘@InputFiles FileCollection inputs’ and call ‘getSingleFile()’ on it in the task

  • Pro: This task can now be wired up to consume the output of another task

  • Con: The code is misleading since you see the FileCollection property but it is actually just a container for a single file.

This is also what Luke did in his “Best Practises” presentation where he changed his ‘Markdown’ task from ‘@InputFile File markdown’ to ‘@InputFiles FileCollection markdown’ as soon as he was demonstrating the task wiring. (But I only noticed this after much frustration and then carefully re-watching the presentation)

In general I find it hard to find good and consistent “Best Practice” advice for Plugin development (as maybe illustrated by my previous post).

I’m working on list of “Best Practices” which I want to share but at the moment I’m struggling to validate the list.

1 Like

2 is the right way to go in my estimation. I think the value of the auto-wiring outweighs the ambiguity of using a file collection. In fact, depending on what the downstream task does, it may make sense to have it always operate on a FileCollection even though you expect it to be a single file in most (or all) cases.

I wouldn’t say that @InputFile is incompatible with auto-wiring, because you could potentially hook it to something that is of type Buildable and the auto-wiring should then work. The problem is that there is no way to query a task’s outputs for a single Buildable file - only for a file collection.