Chapter 57. Incremental Tasks

Incremental Tasks are an incubating feature.

Since the introduction of the implementation described above (early in the Gradle 1.6 release cycle), discussions within the Gradle community have produced superior ideas for exposing the information about changes to task implementors to what is described below. As such, the API for this feature will almost certainly change in upcoming releases. However, please do experiment with the current implementation and share your experiences with the Gradle community.

The feature incubation process, which is part of the Gradle feature lifecycle (see Appendix C, The Feature Lifecycle), exists for this purpose of ensuring high quality final implementation through incorporation of early user feedback.

With Gradle, it's very simple to implement a task that gets skipped when all of it's inputs and outputs are up to date (see Section 15.9, “Skipping tasks that are up-to-date”). However, there are times when only a few input files have changed since the last execution, and you'd like to avoid reprocessing all of the unchanged inputs. This can be particularly useful for a transformer task, that converts input files to output files on a 1:1 basis.

If you'd like to optimise your build so that only out-of-date inputs are processed, you can do so with an incremental task.

57.1. Implementing an incremental task

For a task to process inputs incrementally, that task must contain an incremental task action. This is a task action method that contains a single IncrementalTaskInputs parameter, which indicates to Gradle that the action will process the changed inputs only.

The incremental task action may supply an IncrementalTaskInputs.outOfDate() action for processing any input file that is out-of-date, and a IncrementalTaskInputs.removed() action that executes for any input file that has been removed since the previous execution.

Example 57.1. Defining an incremental task action

build.gradle

class IncrementalReverseTask extends DefaultTask {
    @InputDirectory
    def File inputDir

    @OutputDirectory
    def File outputDir

    @TaskAction
    void execute(IncrementalTaskInputs inputs) {
        println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date"
        inputs.outOfDate({ change ->
            println "out of date: ${change.file.name}"
            def targetFile = project.file("$outputDir/${change.file.name}")
            targetFile.text = change.file.text.reverse()
        } as Action)

        inputs.removed({ change ->
            println "removed: ${change.file.name}"
            def targetFile = project.file("$outputDir/${change.file.name}")
            if (targetFile.exists()) {
                targetFile.delete()
            }
        } as Action)
    }
}

Note: The code for this example can be found at samples/userguide/tasks/incrementalTask which is in both the binary and source distributions of Gradle.


For a simple transformer task like this, the task action simply needs to generate output files for any out-of-date inputs, and delete output files for any removed inputs.

A task may only contain a single incremental task action.

57.2. Which inputs are considered out of date?

When Gradle has history of a previous task execution, and the only changes to the task execution context since that execution are to input files, then Gradle is able to determine which input files need to be reprocessed by the task. In this case, the IncrementalTaskInputs.outOfDate() action will be executed any input file that was added or modified, and the IncrementalTaskInputs.removed() action will be executed for any removed input file.

However, there are many cases where Gradle is unable to determine which input files need to be reprocessed. Examples include:

  • There is no history available from a previous execution.
  • An upToDateWhen criteria added to the task returns false.
  • An input property has changed since the previous execution.
  • One or more output files have changed since the previous execution.

In any of these cases, Gradle will consider all of the input files to be outOfDate. The IncrementalTaskInputs.outOfDate() action will be executed for every input file, and the IncrementalTaskInputs.removed() action will not be executed at all.

You can check if Gradle was able to determine the incremental changes to input files with IncrementalTaskInputs.isIncremental().

57.3. An incremental task in action

Given the incremental task implementation above, we can explore the various change scenarios by example. First, consider the an IncrementalReverseTask executed against a set of inputs for the first time. In this case, all inputs will be considered "out of date":

Example 57.2. Running the incremental task for the first time

build.gradle

task incrementalReverse(type: IncrementalReverseTask) {
    inputDir = file('inputs')
    outputDir = file("$buildDir/outputs")
}

Build layout

incrementalTask/
  build.gradle
  inputs/
    1.txt
    2.txt
    3.txt

Output of gradle -q incrementalReverse

> gradle -q incrementalReverse
ALL inputs considered out of date
out of date: 1.txt
out of date: 2.txt
out of date: 3.txt

Naturally when the task is executed again with no changes, then task itself is up to date and no files are reported to the task action:

Example 57.3. Running the incremental task with unchanged inputs

Output of gradle -q incrementalReverse

> gradle -q incrementalReverse

When an input file is modified in some way or a new input file is added, then re-executing the task results in those files being reported to IncrementalTaskInputs.outOfDate():

Example 57.4. Running the incremental task with updated input files

build.gradle

task updatedInputs() << {
    file('inputs/1.txt').text = "Changed content for existing file 1."
    file('inputs/4.txt').text = "Content for new file 4."
}

Output of gradle -q updatedInputs incrementalReverse

> gradle -q updatedInputs incrementalReverse
CHANGED inputs considered out of date
out of date: 1.txt
out of date: 4.txt

When an existing input file is removed, then re-executing the task results that file being reported to IncrementalTaskInputs.removed():

Example 57.5. Running the incremental task with an input file removed

build.gradle

task removedInput() << {
    file('inputs/3.txt').delete()
}

Output of gradle -q removedInput incrementalReverse

> gradle -q removedInput incrementalReverse
CHANGED inputs considered out of date
removed: 3.txt

When an output file is deleted (or modified), then Gradle is unable to determine which input files are out of date. In this case, all input files are reported to the IncrementalTaskInputs.outOfDate() action, and no input files are reported to the IncrementalTaskInputs.removed() action:

Example 57.6. Running the incremental task with an output file removed

build.gradle

task removedOutput() << {
    file("$buildDir/outputs/1.txt").delete()
}

Output of gradle -q removedOutput incrementalReverse

> gradle -q removedOutput incrementalReverse
ALL inputs considered out of date
out of date: 1.txt
out of date: 2.txt
out of date: 3.txt