Infrastructure worth keeping — and kept patched.

Skip rebuilding buildSrc and included builds in Gradle

Gradle can restore the compiled result of buildSrc and of an included build from the cache, so in our runs a change in one part rebuilt only that part, except that a new public method in the included build also rebuilt the app. At FosterStack we prove everything, so here is how we proved it to ourselves: six real runs showing what was restored and what was rebuilt.

The short answer

What we ran

Three parts: a buildSrc folder with one Java class, an included build called lib with one Java class, and an app project that uses lib. Every run used a fresh copy of the project, a new empty Gradle home folder, and the local build cache switched off, so the only build state the runs shared was the cache server (they also used the same Gradle install and the same Java). Runs 3 to 6 each made one edit on a fresh copy of the original code; the edits were not stacked. All runs used gradle :app:build --console=plain --no-daemon on the same Mac, with Java 27 (Homebrew). All six builds ended with BUILD SUCCESSFUL.

RunbuildSrclib (included build)app
1. First build, as CI would do itranranran
2. Fresh copy, same codeFROM-CACHEFROM-CACHEFROM-CACHE
3. Edit buildSrcranFROM-CACHEFROM-CACHE
4. Edit inside a method in libFROM-CACHEranFROM-CACHE
5. Add a public method to libFROM-CACHEranran
6. Edit only appFROM-CACHEFROM-CACHEran

The columns show the compileJava task of each part. These are three lines from run 2, with the lines between them left out:

> Task :buildSrc:compileJava FROM-CACHE
...
> Task :lib:compileJava FROM-CACHE
...
> Task :app:compileJava FROM-CACHE

What this means for your build

We tried one small class in each part. We did not test a buildSrc that holds plugins, convention plugins, or other languages, and we did not test an included build that is itself a large multi-module build.

The files we used

Root settings.gradle.kts. The address is for a test on one machine over plain HTTP; use your server's address and HTTPS across a network.

rootProject.name = "composite"
includeBuild("lib")
include(":app")

buildCache {
    local { isEnabled = false }
    remote<HttpBuildCache> {
        url = uri("http://127.0.0.1:18220/")
        isPush = true
        isAllowInsecureProtocol = true
    }
}

The root gradle.properties has one line, org.gradle.caching=true. The root build.gradle.kts has one line that prints a value from the buildSrc class. lib/settings.gradle.kts is rootProject.name = "lib". buildSrc/build.gradle.kts is plugins { java }. lib/build.gradle.kts is plugins { `java-library` } with group = "demo", and app/build.gradle.kts adds implementation("demo:lib").

If something does not come from the cache

Work through why the hit rate is low. To see what Gradle stores, see what Gradle stores in the build cache.

Run on Gradle 9.8.0 with FosterStack Cache 0.2.1, macOS (arm64), Java 27 (Homebrew).