Chapter 61. Only Build the Parts That Change and Reuse the Rest

Jenn Strater

As Java programmers, we spend a lot of time waiting for builds to run, often because we don’t run them efficiently. We can make small improvements by changing our behavior. For example, we could only run a submodule instead of the entire project, and not run clean before every build. To make a bigger difference, we should take advantage of the build caching offered by our build tools, namely Gradle, Maven, and Bazel.

Build caching is the reuse of results from a previous run to minimize the number of build steps (e.g., Gradle tasks, Maven goals, Bazel actions) executed during the current run. Any build step that is idempotent, meaning that it produces the same output for a given set of inputs, can be cached.

The output of Java compilation, for example, is the tree of class files generated by the Java compiler, and the inputs are factors that impact the produced class files, such as the source code itself, Java version, operating system, and any compiler flags. Given the same run conditions and source code, the Java compilation step produces the same class files every time. So instead of running the compilation step, the build tool can look in the cache for any previous runs with the same inputs and reuse the output.

Build caching isn’t limited to compilation. Build tools define standard inputs and outputs for other common build steps, like static analysis and documentation generation, and also allow us to configure the inputs and outputs for any cacheable build step.

This type of caching is especially useful for multimodule builds. In a project with 4 modules, each of which has 5 build steps, a clean build must execute 20 steps. Most of the time, though, we are only modifying the source code in one module. If no other projects depend on that module, then that means we only need to execute the steps downstream from source code generation; in this example, only 4: the outputs of the other 16 steps can be pulled from the cache, saving time and resources.

Gradle’s incremental build, which we see as UP-TO-DATE in the build output, implements build caching at the project level. A local cache, like the one built into Gradle and available as an extension to Maven, works even when changing workspaces, Git branches, and command-line options.

The collaborative effect of remote build caching available in Gradle, Maven, and Bazel adds additional benefits. One of the common use cases for remote caching is the first build after pulling from a remote version control repository. After we pull from the remote, we have to build the project on our machine to take advantage of those changes. But since we have never built those changes on our machine, they aren’t in our local cache yet. However, the continuous integration system has already built those changes and uploaded the results to the shared remote cache so we get a cache hit from the remote cache, saving the time required to execute those build steps locally.

By using build caching in our Java builds, we can share the results across our local builds, the agents of the CI server, and the entire team, resulting in faster builds for everyone and fewer resources computing the same operations over and over again.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset