Gradle 9.7.0 Upgrade - Generate the Groovy compiler config script in its own task (fixes Gradle 9.7) - #16114
Open
codeconsole wants to merge 3 commits into
Open
Conversation
`configureGroovyCompiler` assigned `groovyOptions.configurationScript` from a
`doFirst` on each GroovyCompile task. Gradle finalizes task properties before
any task action runs, so from Gradle 9.7 — where GroovyCompileOptions became a
lazy property — every Groovy compilation fails:
Execution failed for task ':compileGroovy'.
> The value for task ':compileGroovy' property
'groovyOptions.configurationScriptFile' is final and cannot be changed
any further.
Assigning the property during configuration is not enough on its own: Gradle
then treats the script as an input file that must exist before the compile task
runs, which fails on a clean build because `doFirst` runs after input
validation. So the combined script is now produced by a dedicated task that the
compile task depends on.
That task is marked `doNotTrackState`, which keeps the property that motivated
the original `doFirst`: generating the script needs the resolved compile
classpath, and declaring it as an input would pull the runtimeClasspath into the
task's up-to-date check. The script is cheap to build, so it is simply
regenerated on every build.
Wiring moves to `afterEvaluate` so a `configurationScript` set by the build
script is already in place and gets folded into the combined script rather than
clobbered — the merge the old execution-time read performed. Task names are read
via `TaskCollection.names`, which does not realize the tasks.
Verified against a multi-project Grails 8 application on both Gradle 9.6.1 and
9.7.0: full `bootJar`, and `clean` plus compilation in a single invocation.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #16114 +/- ##
==================================================
- Coverage 52.3431% 52.3350% -0.0081%
Complexity 18296 18296
==================================================
Files 2036 2036
Lines 96347 96381 +34
Branches 16829 16835 +6
==================================================
+ Hits 50431 50441 +10
- Misses 38492 38514 +22
- Partials 7424 7426 +2
🚀 New features to boost your workflow:
|
Moves grails-core's own build onto 9.7.0: `.sdkmanrc`, `gradleToolingApiVersion`, and the wrapper for every Gradle build in the repository — root, build-logic, grails-gradle, grails-forge, end-to-end — plus the grails-shell-cli gradle-sample fixture. The shared wrappers were regenerated with `gradle -p gradle-bootstrap`; the gradle-sample fixture, which bootstrap does not reach, was refreshed from the same output so the launcher scripts and wrapper jar stay byte-identical everywhere. `end-to-end/legacy-g7-command-plugin` deliberately stays on Gradle 8.14.5, the version Grails 7 pins for that fixture. The wrapper task regenerates the properties files from scratch, so the "keep this synced" checklist comments were restored afterwards.
Applications created by the forge and by the profile CLIs now get a 9.7.0 wrapper instead of 9.6.0. For the forge, that means the `gradleWrapperProperties` template plus the three binaries `Gradle.java` copies onto the generated project — `gradlew`, `gradlew.bat`, and `gradle/wrapper/gradle-wrapper.jar`. For the profile CLIs, only the `base` and `profile` skeletons carry wrapper assets; `web`, `rest-api`, and `plugin` inherit them from `base` through `profileRuntimeApi`, and `web-plugin` / `rest-api-plugin` chain through those, so every application type picks the new wrapper up. Generated applications can only move to 9.7 together with the compiler config script fix earlier in this branch — without it every Groovy compilation in a Grails project fails on Gradle 9.7.
✅ All tests passed ✅🏷️ Commit: 17f81fe Learn more about TestLens at testlens.app. |
codeconsole
requested review from
borinquenkid,
jamesfredley,
jdaugherty,
matrei and
sbglasius
and removed request for
jdaugherty and
matrei
August 7, 2026 21:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GrailsGradlePlugin.configureGroovyCompilerassignsgroovyOptions.configurationScriptfrom adoFirston eachGroovyCompiletask. Gradle finalizes task properties before any task action runs, so from Gradle 9.7 — whereGroovyCompileOptionsbecame a lazy property — every Groovy compilation in every Grails project fails:This blocks Grails 8 on Gradle 9.7 entirely — there is no user-side workaround, and no Gradle opt-out flag.
Fix
The combined compiler configuration script is now produced by a dedicated task that the compile task depends on.
Assigning the property during configuration is necessary but not sufficient: Gradle then treats the script as an input file that must exist before the compile task runs, which fails on a clean build because
doFirstruns after input validation. A producing task is what makes the file exist at the right moment.The generator task is marked
doNotTrackState, which preserves the property the originaldoFirstwas protecting — the existing comment noted that generating the script needs the resolved compile classpath, and declaring that as an input would pull theruntimeClasspathinto the task's up-to-date check. The script is cheap to build, so it is regenerated on every build instead.Wiring moves into
afterEvaluateso aconfigurationScriptset by the build script is already in place and gets folded into the combined script rather than clobbered — preserving the merge the old execution-time read performed. Task names are read viaTaskCollection.names, which does not realize the tasks.Testing
:grails-gradle-plugins:testpasses.bootJarsucceeds on both;cleanplus compilation in a single invocation succeeds (the case adoFirstcannot satisfy);grailsGroovyCompilerConfig-<task>.groovystill carries the expectedwithConfig(configuration) { inline(phase: 'CONVERSION') ... }metadata (projectVersion,projectName,isPlugin).Without this change, that same application fails on Gradle 9.7.0 at the first
compileGroovy.