Making your Gradle tasks incremental

The easiest way to reduce build times is to not do anything unnecessary.

Gradle has support for making tasks incremental. Tasks can define their own inputs and outputs. Gradle will determine whether they have changed or not. In case they have, Gradle will execute your task otherwise it won’t and you’ll see up-to-date in the command line interface.

Example task

Let’s consider this task to let ktlint run on your project:

def outputDir = "${project.buildDir}/reports/ktlint/"
def outputFile = "${outputDir}ktlint-checkstyle-report.xml"
project.task("ktlint", type: JavaExec) {
group = "verification"
description = "Runs ktlint."
main = "com.github.shyiko.ktlint.Main"
classpath = project.configurations.ktlint
args = [
"--reporter=plain",
"--reporter=checkstyle,output=${outputFile}",
"src/**/*.kt"
]
}

If we run it once, it’ll do all of the work. Running the task a second time will do the same work — again. It’s unnecessary. We haven’t changed any of the Kotlin files and hence we know that we don’t need to redo the work.

Making it incremental

In order to make this task incremental we’ll have to define inputs and outputs.

def outputDir = "${project.buildDir}/reports/ktlint/"
def inputFiles = project.fileTree(dir: "src", include: "**/*.kt")
def outputFile = "${outputDir}ktlint-checkstyle-report.xml"
project.task("ktlint", type: JavaExec) {
inputs.files(inputFiles)
outputs.dir(outputDir)
// Here comes the previous configuration.
}

The Gradle task gives us inputs and outputs APIs.

  • For inputs we can simply give it a file tree structure of all Kotlin files in the source folder
  • Outputs is even easier as we’ll take the output directory where the report will be stored

If we run this task now, it will be executed.

Next time though, Gradle is doing it’s magic with our defined inputs and outputs and tells us that nothing has changed and the task is up-to-date.

It’s important to note that you’re responsible for defining the inputs and outputs correctly. If you have Kotlin files outside of the src folder and you change those, the task won’t be executed and is still up-to-date.

Head over to the documentation of TaskInputs and TaskOutputs to see what other API calls they support out of the box.

Conclusion

I hope this shines some light into how to avoid doing unnecessary work and making your Gradle tasks incremental.

Obviously, this approach is only a first step as all files will be scanned by ktlint even if only one file has changed. IncrementalTaskInputs can help in this case though.

Additionally, this could be optimized even further by leveraging the Worker API which allows to split up some work into discrete units of work and then to execute that work concurrently and asynchronously.

Happy Gradling!