Using Gradle build cache with Kotlin


Eric Wendelin
This is a guest blog post from Eric Wendelin
software engineer for Gradle

A build cache allows Gradle to reuse task output from any previous invocation, including those from other machines. Kotlin 1.2.21 allows Kotlin projects to make use of build caching.

The build cache works by storing compiled classes, test outputs, and other build artifacts in a cache, taking into account all task inputs, including input file contents, relevant classpaths, and task configuration.

Build Cache topological diagram

This frequently results in faster builds. The following chart shows aggregated build time with and without the build cache for part of Gradle’s CI:

Build minutes saved with Gradle build cache

In this post, we’ll explain how you can use Gradle’s build cache to avoid unnecessary Kotlin compilation to speed up your builds.

Quick demo with Spek

You can try out build caching with Gradle right now. Just follow these steps:

Clone Spek

The Spek 2.x branch (which is the default) already has all prerequisites for build caching that we’ll describe later.

Build and populate cache

The following command builds Spek and populates the local build cache.

Using the --build-cache flag is one way to tell Gradle to store outputs in a separate task output cache.

Remove/change build outputs

This simulates being on another machine or perhaps making a change and stashing it. The quickest way to demonstrate is use the clean task.

Re-build and resolve from build cache

This time when we re-build, all Kotlin compiled sources are pulled from the build cache.

Voilà! You just used Gradle’s build cache to reuse Kotlin compiled classes instead of recompiling again! The build was about 5x faster!

You can see from this build scan that Kotlin compile tasks were pulled from the build cache; :jar and :processResources tasks were not because it’s faster to generate JARs and copy files locally than pull from a cache. Note that caching :test tasks is also supported.

The Gradle build cache is particularly effective when a CI instance populates a shared build cache, which developers can pull from. Links to more resources for achieving this are listed below.

Enabling the build cache for your projects

I hope you’re excited to try this out on your project — you can follow these steps to enable the build cache.

First, you need to ensure you’re using Gradle 4.3 or above, so the Kotlin Gradle Plugin can opt-in to using new APIs in Gradle. You can upgrade Gradle easily using the Gradle wrapper.

Next, we need to ensure we are compiling with Kotlin version 1.2.20 or above. You might have something like this declared in your buildscript {} block in build.gradle:

Next, we need to tell Gradle to use the build cache. There are 3 ways to do this:

  • Enable for the current build using --build-cache on the command-line.
  • Enable for the project by adding org.gradle.caching=true to $PROJECT_ROOT/gradle.properties.
  • Enable for all builds for the current user by adding org.gradle.caching=true to $GRADLE_HOME/gradle.properties.

NOTE: Android developers still need to do this even if android.enableBuildCache=true is set, because Gradle’s build cache is separate from the Android build cache.

We can optionally take advantage of the build cache from IDEs by delegating run and test actions to Gradle.

Enabling build caching in IntelliJ

If you use IntelliJ to execute Gradle actions, you will need to “Delegate IDE build/run actions to Gradle” in your IDE settings to take advantage of build caching when building and running tests from IntelliJ.

Delegate IDE build/run to Gradle

NOTE: Android Studio does this by default.

Caching kapt tasks

Caching for kapt is currently disabled by default, even with --build-cache, because Gradle does not yet have a way to map inputs and outputs for annotation processors. You can explicitly enable use of the build cache for Kotlin annotation processing tasks by setting useBuildCache to true in kapt configuration.

Further reading

You can learn more about leveraging the Gradle build cache through these resources:

Conclusion

Compiling Kotlin code using kotlin-gradle-plugin version 1.2.20 and above can take advantage of Gradle’s --build-cache feature to speed up your development cycle. Work continues to expand the set of tasks that support build caching.

Onward!

This entry was posted in guestpost, Tools and tagged . Bookmark the permalink.

13 Responses to Using Gradle build cache with Kotlin

  1. Brian Stokes says:

    Does anybody know which version of Android Studio the “Delegate IDE build/run actions to Gradle” setting appears in? I don’t see it in 3.0 or either of the 3.1 and 3.2 previews. I can’t wait to try it out.

    • Sergey Igushkin says:

      This step is not needed with Android Studio, since it runs Android project builds with Gradle by default.

  2. Scott says:

    I don’t see any such “Delegate IDE build/run actions to Gradle” in Android Studio

    • Sergey Igushkin says:

      Android Studio does that by default, all Android project builds are run with Gradle. This step is therefore not needed with Android Studio.

  3. Louis CAD says:

    Why is it disabled by default for kapt? Are there any risks or drawbacks enabling it?

    • Sergey Igushkin says:

      We disabled it for kapt by default because annotation processors are basically arbitrary third party code, which may not necessarily only transform the inputs into the outputs but also read and write files that are not tracked by Gradle or take some system state like date & time into account.

      If you are sure that the annotation processors that you are using only access the sources, classpath (and, possibly, Android resources) and produce only Java/Kotlin sources or class files, and that they are also pure (produce the same outputs given same inputs), then it’s fine to cache them. Many annotation processors fall into this category, sou you can just check how caching works with kapt in your projects.

      Some annotation processors may use or modify additional files or other system state and are not pure. If you use them, it’s up to you to declare all of their inputs and outputs as, respectively, inputs and outputs of the kapt tasks. If you don’t, you may get false positive cache hits with caching enabled for kapt, or some of the outputs might not be stored in the cache.

      If there appears a mechanism in Gradle for tracking annotation processor inputs and outputs, we may enable kapt caching by default for builds that only use annotation processors that correctly declare their inputs/outputs.

  4. Svyatoslav Chatchenko says:

    What kind of issues should we expect from enabling caching for kapt? How should we handle them? Execute gradle clean or gradle cleanBuildCache or something else?

    • Sergey Igushkin says:

      Basically, I can imagine three kinds of issues that can appear:

      1) An annotation processor uses some data that is not tracked by Gradle as inputs of the kapt task. In this case, the cache key for kapt tasks in your project won’t include that untracked data, and you will get false cache hits. For example, if there are some special local configuration files for the annotation processor, you can still get a FROM-CACHE result and stale outputs unpacked after having modifed the configuration files. You can handle this sort if issues by adding the additional data as inputs to the kapt tasks in your Gradle build script. By default, the tracked data includes sources, compilation classpath, compiler options, annotation processors and kapt options (plus, Android layouts, if present). If an annotation processor does not use anything beside that, you won’t face this kind of problem.

      2) An annotation processor produces something that is not tracked as an output of the kapt task. This will lead to those outputs not being packed into the cache and, consequently, not being unpacked on cache hit. It’s relatively easy to detect, since the build will likely fail in this case. Likewise, you can handle this by registering the locations of those special outputs as the kapt task outputs. By default, the tracked outputs are Java and Kotlin generated sources and class files, all in the pre-defined locations.

      3) An annotation processor is not pure, i.e. given the same inputs it may produce different outputs depending on some additional state (e.g. system date and time). Similarly to the first case, you will get false positive cache hits, but if the state that the annotation processor takes into account is as transient as system time, you should not use caching for annotation processing.

      Many annotation processors can be cached just fine, though. You can try to enable caching for kapt in your projects and check how it works.

  5. Nirmesh says:

    Fantastic article.

  6. Tim Kist says:

    Great article and thanks for the demo!

    I think there’s a typo where the build cache is supported in Kotlin from 1.2.20 rather than 1.2.21. The link at the top goes to a release page for 1.2.20, but the text says 1.2.21.

    • Sergey Igushkin says:

      Thanks for the feedback! In fact, the 1.2.21 release is a hotfix release for 1.2.20, and it does not have its own blog post. We recommend 1.2.21 over 1.2.20, so the version in the article is intended.

  7. Tomek says:

    Thanks for the article.
    In my Android codebase I do not see a significant performance improvement when using build cache.
    In what kind of projects should it improve the performance the most? Those who have multiple small modules? That use java only? Or any other?

    Cheers

  8. Vitaly says:

    I’ve added org.gradle.caching=true to gradle.properties and enabled build/run task delegation to Gradle in IDEA. But now when I run the app with Ctrl+Shift+R after making some changes, I still see the same output, as if changes haven’t been made.

Comments are closed.