Skip to content

Enable the Gradle configuration cache and configuration on demand - #234

Merged
rubensworks merged 2 commits into
master-26-ltsfrom
claude/gradle-config-on-demand-6hbhyg
Aug 22, 2026
Merged

Enable the Gradle configuration cache and configuration on demand#234
rubensworks merged 2 commits into
master-26-ltsfrom
claude/gradle-config-on-demand-6hbhyg

Conversation

@rubensworks

@rubensworks rubensworks commented Aug 20, 2026

Copy link
Copy Markdown
Member

Serialises the configuration phase and replays it, so its cost is paid once instead of on every invocation, and only configures the projects a given invocation actually needs.

This started as an investigation into configuration on demand alone. On its own that is the weaker lever — it only skips projects not needed by the requested tasks, and every command in ci.yml and AGENTS.md uses unqualified task names (build, test, runGameTestServer, publish, …) that match a task in all four subprojects. Measured on master-1.21-lts, ./gradlew build was 20.4s baseline vs 21.5s with --configure-on-demand — no gain. It only helps qualified invocations like :loader-fabric:build (−28%).

JustEnoughItems uses the configuration cache instead, which is what helps ./gradlew build itself. Both are enabled here; they compose cleanly.

Changes

Three fixes were needed to make the build configuration-cache compatible:

  • Bump Spotless 6.25.0 → 7.2.1. Spotless 6.x fails every configuration cache run without a daemon with Spotless JVM-local cache is stale (diffplug/spotless#987).
  • Replace indentWithSpaces() with leadingTabsToSpaces(), its Spotless 7 equivalent. Same behaviour (4 spaces).
  • Look up git tags in getChangelog() via providers.exec instead of Groovy's String.execute(). Starting a process during configuration is unsupported by the configuration cache, and was the only remaining violation in the build.

Then enables org.gradle.configuration-cache, org.gradle.configureondemand and org.gradle.daemon.

Numbers

./gradlew build, warm caches, 4 vCPU:

Scenario Before After Δ
up-to-date, --no-daemon 13.8s 7.7s −44%
up-to-date, daemon 3.3s 2.1s −36%
after a loader-common edit, --no-daemon 37.1s 37.1s no change
after that edit, daemon 20.1s 17.3s −14%
runGameTestServer 32.5s 25.6s −20%

Enabling the daemon on its own accounts for most of it: a rebuild after editing a loader-common source drops from ~37s to ~20s, and an up-to-date build from 13.8s to 3.3s.

Configuration on demand only affects runs that miss the configuration cache — on a hit, no project is configured at all. On a miss:

Invocation COD Time Projects configured
build on 5.6s / 5.0s 4
build off 5.0s / 5.2s 4
:loader-neoforge:build on 1.8s / 1.8s 2
:loader-neoforge:build off 4.7s / 4.6s 4

So it is neutral for build and worth −62% for a single-loader build.

Scope of the benefit

CI is largely unaffected. The configuration cache lives in the project's .gradle directory, which gradle/actions/setup-gradle does not save or restore — it only handles caches and notifications under Gradle User Home. So CI stores a fresh entry each run rather than reusing one. The daemon setting is likewise inert in CI, since the workflow passes --no-daemon explicitly. The benefit here is to local iteration.

Configuration cache problems fail the build rather than being downgraded to warnings, so that a future update to Loom, ModDevGradle or ForgeGradle that reintroduces one is visible instead of silently degrading. The build reports zero problems today.

Verification

All run on this branch with the final settings and no extra command-line flags:

  • Clean ./gradlew build from empty build/ dirs — succeeds, produces all 12 jars.
  • ./gradlew build --no-daemon (CI's command) — succeeds.
  • ./gradlew test runGameTestServer jacocoTestReport (CI's test command) — succeeds, All 2 required tests passed.
  • ./gradlew :loader-neoforge:build — succeeds, configures 2 projects instead of 4.
  • Configuration cache store and reuse both verified, including across daemon and --no-daemon runs.
  • The changelog string produced by getChangelog() is byte-identical before and after the providers.exec change, checked side by side on both code paths.

Serialises the configuration phase and replays it, so its cost is paid
once instead of on every invocation. Three changes were needed to make
the build configuration-cache compatible:

- Bump Spotless 6.25.0 -> 7.2.1. Spotless 6.x fails every configuration
  cache run without a daemon with "Spotless JVM-local cache is stale"
  (diffplug/spotless#987).
- Replace the deprecated indentWithSpaces() with leadingTabsToSpaces(),
  its Spotless 7 equivalent. Same behaviour (4 spaces).
- Look up git tags in getChangelog() via providers.exec instead of
  Groovy's String.execute(). Starting a process during configuration is
  unsupported by the configuration cache, and was the only remaining
  violation in the build. The resulting changelog string is unchanged.

Also enables the Gradle daemon, which is the larger win locally: a
rebuild after editing a loader-common source drops from ~37s to ~20s.
CI is unaffected, as its workflow passes --no-daemon explicitly.

Measured on this branch, ./gradlew build with warm caches:

  up-to-date, --no-daemon      13.8s -> 7.7s   (-44%)
  up-to-date, daemon            3.3s -> 2.1s   (-36%)
  after a loader-common edit   37.1s -> 37.1s  (no change)
  after that edit, daemon      20.1s -> 17.3s  (-14%)
  runGameTestServer            32.5s -> 25.6s  (-20%)

The configuration cache lives in the project's .gradle directory, which
setup-gradle does not save or restore, so CI stores a fresh entry each
run rather than reusing one. The benefit is to local iteration.

Problems are reported as warnings rather than failing the build, so that
a future plugin update reintroducing one degrades gracefully. The build
currently reports zero problems in strict mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVogTgqmFMYafVAsnQMs7k
@coveralls

coveralls commented Aug 20, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 31.032%. remained the same — claude/gradle-config-on-demand-6hbhyg into master-26-lts

Configuration on demand only configures the projects needed by the
requested tasks. For unqualified tasks like `build` that is still all
four subprojects, so it changes nothing there, but it composes cleanly
with the configuration cache and pays off for qualified invocations:

  configuration cache miss, ./gradlew build
    on   5.6s / 5.0s   (4 projects configured)
    off  5.0s / 5.2s   (4 projects configured)

  configuration cache miss, ./gradlew :loader-neoforge:build
    on   1.8s / 1.8s   (2 projects configured)
    off  4.7s / 4.6s   (4 projects configured)

On a configuration cache hit no project is configured at all, so this
only affects runs that miss the cache.

Also drops the configuration-cache.problems=warn setting, so that
problems fail the build rather than being reported and ignored. The
build reports none today, and a plugin update that introduces one
should be visible rather than silently degrading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVogTgqmFMYafVAsnQMs7k
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@rubensworks rubensworks changed the title Enable the Gradle configuration cache Enable the Gradle configuration cache and configuration on demand Aug 20, 2026
@sonarqubecloud

Copy link
Copy Markdown

@rubensworks
rubensworks merged commit a75f891 into master-26-lts Aug 22, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants