Speed Up Gradle Builds On Travis CI
Gradle can be slow to build on CI out the box. In this article, the author runs through some tips on speeding everything up.
Join the DZone community and get the full member experience.
Join For FreeDefault TravisCI Configuration for Gradle
By default, when Travis detects Gradle as a build system, it configures these two phases by default:
install: gradle assemble
build: gradle check
So as part of your build, Gradle is executed twice by default.
The assemble
and check
phases has only few initial phases in common. There isn’t much tasks redundancy if we run assemble
and check
separately.
There is also Gradle UP-TO-DATE feature. It means Gradle is smart enough to mark task as UP-TO-DATE after first execution. Redundant execution of this task can be than skipped if its input doesn’t change. Of course it works across various separate build executions. Let’s take a look at what happen when we build mentioned example project with this example configuration. Link to the build is here.
$ ./gradlew assemble
:compileJava
:processResources
:classes
:findMainClass
:jar
:bootRepackage
:assemble
BUILD SUCCESSFUL
Total time: 21.0 secs
$ ./gradlew check
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
2016-02-06 11:26:06.883 INFO 2455 --- [ Thread-6] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@295c442d: startup date [Sat Feb 06 11:25:47 UTC 2016]; root of context hierarchy
:check
BUILD SUCCESSFUL
Total time: 55.216 secs
Notice that the full elapsed time was 1 min 49 seconds.
As you can see, the execution of the check task didn’t have to run tasks compileJava
, processResources
, and classes
again, because they were executed previously as part of the assemble
task.
So TravisCI's default configuration may be reasonable for Gradle.
Merge Install and Script Phase
But even if Gradle makes a good effort to reduce overhead, the overhead is there. As Gradle Java Plugin tasks visualization showed earlier in this post, we can cover assemble
and check
tasks with a build task. So why should we run two Gradle processes in a single build when a single process execution is enough?
As previously mentioned, two default tasks may be fine for Continuous Integration workflow, but modern developers and teams don't stop there. Every company that takes software development seriously wants to incorporate Continuous Delivery or Continuous Deployment. So we want to assemble, check, build, and deploy all our assets in one build pipeline. So why would we want to run two Gradle processes?
Let’s do this:
install: echo "skip 'gradle assemble' step"
script: gradle build --continue
In the Travis Install phase we override the default assemble
task and run a full Gradle build
as one process. Let take a look at the timings of such a build for the same project. Build link is here.
$ echo "skip './gradlew assemble' step"
skip './gradlew assemble' step
$ ./gradlew build --continue
:compileJava
:processResources
:classes
:findMainClass
:jar
:bootRepackage
:assemble
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
2016-01-31 20:19:37.202 INFO 2353 --- [ Thread-6] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@46441da4: startup date [Sun Jan 31 20:19:24 UTC 2016]; root of context hierarchy
:check
:build
BUILD SUCCESSFUL
Total time: 36.926 secs
Notice that Full elapsed time was 59 seconds.
So even if Gradle tries to optimize tasks as much as possible, there is noticeable overhead running two separate Gradle processed instead of one.
Cache Gradle and NodeJS Dependencies
A second hint to speed up TravisCI build for Gradle can be found in the TravisCI docs themselves. It involves Gradle dependencies caching.
As I mentioned at the beginning, the project I work on also involves Gulp/NodeJS builds. Therefore, it makes sense to also put NPM dependencies into a TravisCI cache. Gradle integration with Gulp can be done via Gradle Gulp plugin. This plugin locates NPM global dependencies in$HOME/.gradle/nodejs
and local NPM dependencies in node_modules
folder.
So the Gradle caching configuration for our build looks like this:
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
- $HOME/.gradle/nodejs/
- node_modules
Don’t ask me why we need to remove lock in before_cache
section. That is taken from mentioned TravisCI docs. Rest of the configuration caches all Gradle and NPM dependencies between builds.
Here is link to build without caching: 1 min 56 seconds.
Here is link to build with caching: 59 seconds. This example build doesn’t contain Gradle Gulp plugin, so saving from NPM dependencies caching are not included.
Conclusion
These two simple tricks cut off around 4 minutes from the Dotsub project build I mentioned. Pretty nice when it’s free. If you want to mess with this example, you can look at this Github repo.
Published at DZone with permission of Lubos Krnac, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments