A Software Architect Blog

Happy Builds

March 25, 2018

Make happier developers by supercharging your build with Gradle caching.

A modern trend in code bases is to create mono-repos which consist of multiple related code bases. These can make developer workflow easier to manage. The downside is build time can grow and this can be compounded by using a mixture of languages in the code base e.g. Java and Javascript.

Code bases grow over time. There are ways to manage this, but at some point, the build will need to be supercharged to regain its speed as it slows down due to its size.

Taking a large Java/Javascript code base with an average build time of 12 minutes we can look at ways to improve its build performance.

A great tool to use to inspect your build is Gradle build scans, it can be added using the build-scan plugin.

plugins {   
    id 'com.gradle.build-scan' 
    version '1.12.1' 
}  
buildScan {   
    licenseAgreementUrl = 'https://gradle.com/terms-of-service'
    licenseAgree = 'yes' 
}

Now you can run your build:

./gradlew build --scan

At the end of the build it will printout a url. This is a link to the build summary from Gradle. This shows information about the build’s performance like the build cache utilisation.

Performance

Now rerun your build with caching enabled.

./gradlew build --build-cache --scan

Gradle does a great job of using the cache for Java modules but some work is required for JavaScript builds.

To support caching, a task has to implement @CacheableTask and have defined inputs and outputs. This means if Gradle has run this task before with the same inputs it can use the cached outputs instead of executing the task again.

@CacheableTask
class CacheableYarnTask extends com.moowork.gradle.node.yarn.YarnTask {
}
task runJSDeploy(type: CacheableYarnTask) {
    inputs.file("package.json")
    inputs.file("yarn.lock")
    inputs.file("webpack.js")
    inputs.dir("src")
    outputs.dir("$buildDir/bundles")
    outputs.cacheIf { true }

    args = ['deploy']
}

Gradle also supports sharing the cached values. The best approach is to allow a build to use its own cache and the cached assets created from builds in your CI environment.

In your settings.gradle add information about a central cache server. Using a shared HTTP build cache backend (such as the one provided by Gradle Enterprise).

ext.isCiServer = "true".equalsIgnoreCase(System.getProperty('ci'))
buildCache {
    local {
        enabled = !isCiServer
    }
    remote(HttpBuildCache) {
        url = "${URL}"
        push = isCiServer
        credentials {
            username = "${USERNAME}"
            password = "${PASSWORD}"
        }
    }
}

For this large Java/Javascript code base the build time average reduced from 12mins to 1–6mins depending on the code change.

Improving build speed has a massive impact on a team’s productivity and morale. With larger code bases comes larger teams and a slow or unstable build has a compound effect on people’s ability to deliver quality software. Slow builds lead to people rushing to commit during green windows and not running test suites locally. This can cause more red builds.

Before these speed improvements, the build was discussed at every developer meeting. Since the improvements it hasn’t been mentioned.


Andy Riley

Follow me on twitter @andyianriley
or see andyianriley @ linkedin.