19 August 2014

One of the nice features of Gradle is the task execution caching, which prevent tasks whose inputs/outputs have not changed since the last successful execution from executing in order to speed up build times. Sometimes, however this causes issues when integrating your Gradle build into other systems. I ran into one such issue when building a Gradle application from Jenkins. The Jenkins build job is configured to publish the results of the test task (the test reports output by Gradle). It did not occur to me that if I did not invoke the clean task and let Gradle only re-generate the test reports if any of the compiled code had changed (other file changes to the project would trigger the CI build) that Jenkins would fail the build due to stale test reports. However, that is exactly what happened:

BUILD SUCCESSFUL

Total time: 15.323 secs
Build step 'Invoke Gradle script' changed build result to SUCCESS
Test reports were found but none of them are new. Did tests run?
For example, /var/jenkins/jobs/test-job/workspace/build/test-results/TEST-com.example.TestSpec.xml is 12 hours 15 minutes old

Build step 'Publish JUnit test result report' changed build result to FAILURE
Finished: FAILURE

By not cleaning out the previously compiled code, the caching mechanism for the test task kicked in and left the previous test results in place, as it properly detected that nothing had changed and therefore is no need to re-run the tests. However, Jenkins apparently bases its ability to publish the results of the tests based on a timestamp and not just the presence of the tests results in the directory specified in the job’s configuration. One easy fix is to always run the clean task, but that eliminates a lot of Gradle's performance enhancements around only running task incrementally, based on their inputs/outputs. Another option is to add the --rerun-tasks option, which effectively does the same thing as the clean task, except it doesn’t remove any artifacts — it just simply forces Gradle to run each task regardless of the caching status. Neither of these options really get us what we want. One other alternative is to make use of the upToDateWhen configuration closure on a tasks declared outputs to control caching on a per-task basis:

    // Always execute the tests -- needed for Jenkins to be happy
    test.outputs.upToDateWhen { false }

The example above directs Gradle to always run the test task regardless of what the task cache tells Gradle about it. This gives us fine grained control over which tasks that we want to take advantage of caching and which ones we want to always run. By adding the one line above to my project’s build.gradle script, I was able to make Jenkins happy with always providing up-to-date unit tests results, regardless of which files (source or otherwise) have changed in my project. This also means that Gradle will take advantage of task caching for other tasks in the project (such as compilation, packaging, etc), which helps to reduce build time.

comments powered by Disqus