Gradle incremental tasks and builds

One of the things that makes a build efficient is when it can run incrementally. i.e. If you have already run one build, and you change things and run another, the second build should only have to rerun some tasks – where the inputs to that task have changed. Gradle has great support for this. I recently came across an example while migrating a large build from Maven to Gradle. In this build, we have three steps that do the following:
  1. Generate JAXB java classes from XSDs
  2. Precompile these classes plus a small number of other classes
  3. Run a custom annotation processor which will add json annotations to the generated code

The custom annotation task is defined in a separate git repository. It needed a custom classpath. I don’t believe you can change the classpath for normal tasks, but if you use a JavaExec task to run a task in a new JVM, you can obviously configure the classpath as you wish. Hence this is the setup I used. It looked like this:

tasks.register('jacksonAnnotationTask', JavaExec) {

    classpath = sourceSets.main.compileClasspath
    classpath += files("$buildDir/classes/java/generatedSource")
    classpath += configurations.jacksonAnnotationTaskClasspath

    mainClass = 'com.ice.integration.gradle.JacksonAnnotationTask'

    args = ["$buildDir/generated/jaxb/java/claimsApiModelClasses", "com"]
}

These steps all happen before the main compile. When I did a compile, then repeated it, I was disappointed to see that the custom annotation task was rerun. What was going on?

How do you see what is going on with a Gradle build? The easiest thing to do is rerun with the -d debug flag. Once I did this, the problem was obvious – the task was rewriting the generated source files in place – therefore the inputs to the task had changed, therefore the task had to be rerun. Once I understood this, the route to fix it is clear – the task should output the updated files in a new location. I updated the task code to do this, adding a third parameter to specify the output directory. Then I updated the JavaExec config to specify the input and output, like this:

tasks.register('jacksonAnnotationTask', JavaExec) {
    // we must declare inputs and outputs
    // otherwise Gradle will just rerun this task every time
    String outputDirectory = "$buildDir/generated/jaxb/java/claimsApiModelClassesWithJsonAnnotations"
    inputs.files(sourceSets.generatedSource.java)
    outputs.dir(outputDirectory)

    classpath = sourceSets.main.compileClasspath
    classpath += files("$buildDir/classes/java/generatedSource")
    classpath += configurations.jacksonAnnotationTaskClasspath

    mainClass = 'com.ice.integration.gradle.JacksonAnnotationTask'

    args = ["$buildDir/generated/jaxb/java/claimsApiModelClasses", "com", outputDirectory]
}

Once I made this change, rerunning the compile task told me that all tasks were up to date, nothing to be rerun! Fantastic!

For more information on incremental builds, see:
https://docs.gradle.org/current/userguide/incremental_build.html

For other blog posts on Gradle, see:
Dependencies and configurations in Gradle
Gradle release plugin
Using test fixtures in Gradle and Maven
Code coverage with Gradle and Jacoco

This entry was posted in Gradle, Java and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

HTML tags are not allowed.

517,978 Spambots Blocked by Simple Comments