Gradle Incremental Builds

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Incremental Build

An incremental build is a build that avoids running tasks whose inputs and outputs did not change since the previous build, making the execution of such tasks unnecessary. An incremental build does not execute a task if it does not need to. Incremental builds save time, because they avoid doing unnecessary and some times lengthy work.

The central element that enables such behavior is the capacity of tasks to analyze their inputs and outputs relative to the last build, detect that nothing that would require it to run changed and decide that they don't need to run. To be considered as part of an incremental build, a task must have at least one output.

An even more sophisticated behavior is identifying which of the input elements had changed and perform work only for those elements. An incremental build is not required to support this behavior, it only has to avoid to run tasks unnecessarily. However, Gradle supports the behavior described above with incremental tasks.

All builds in Gradle are incremental builds by default, Build caching does not have to be enabled to allow them.

Task Inputs and Outputs

In most cases, a task takes some inputs and generates some outputs. For Java compilation, the inputs are the source files, target JDK version, whether we want debugging information or not, and the outputs are the generated class files. The amount of memory allocated to the compiler is not an input, but an internal task property, as its change does not influence the tasks' outputs.

When it is about to execute a task, Gradle tests externally to the task whether any of the following changed from the last build:

  • any of the task inputs changed
  • any of the task outputs changed or it was removed
  • the task code changed

If none of the above elements changed, Gradle considers the task UP-TO_DATE and skips executing its actions. To be considered as part of an incremental build, a task must have at least one output.

Inputs

The essential characteristic of an input is whether it affects one or more outputs in any way. In the above example, source files, target JDK version, enabling debugging are all task inputs. The amount of memory allocated to the compiler is not an input, but an internal task property.

Simple Values

File Types

Nested Values

Internal Task Properties

Anything that is not a task input, meaning that it does not influence one or more outputs, it is an internal task property.

Outputs

To be considered as part of an incremental build, a task must have at least one output and zero or more inputs.

If a task declares outputs but those are not configured in the configuration phase, the build will fail with a message similar to "No value has been specified for property '...'".

Examples

Incremental Task

Incremental Task

Build Cache

Gradle allows caching task output in build caches, if the tasks producing those outputs are cacheable tasks, hence enabling sharing of outputs between builds. Build caching does not have to be enabled to allow incremental builds.

Builds Cache










To process

Gradle determines whether a task is up to date if the task's inputs and outputs have not changed since the last execution, by comparing their values with those preserved in the build cache. Does the build cache need to be enabled for inputs and outputs to be relevant?

If the input and output snapshots are identical to the previous ones, the task is not executed. An input can be a property, a directory or one or more files. An output is a directory or one or more files. Inputs and outputs are fields of the DefaultTask class. Task inputs and outputs are obtained with Task.getInputs() and Task.getOutputs(), which return TaskInputs and TaskOutputs instances, respectively.

Task inputs and outputs are evaluated during the configuration phase to wire up the task dependencies. That is why they need to be defined in a configuration block. To avoid unexpected behavior, make sure that the value you assign to inputs and outputs is accessible at configuration time. If you need to implement programmatic output evaluation, the method upToDateWhen(Closure) on TaskOutputs comes in handy. In contrast to regular inputs/outputs evaluation, this method is evaluated at execution time. If the closure returns true, the task is considered up to date.

Debug a Java compilation task to see how input/output works.

Annotations

@Input

https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/Input.html

⚠️ Note that in Java, the annotation must be attached to the getter method. Annotations on setters or just the field in Java are ignored.

@OutputFile

https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputFile.html

Up-to-date Check

UP-TO-DATE