build: disable Groovy invokedynamic for the Grails 8 compile - #16178
build: disable Groovy invokedynamic for the Grails 8 compile#16178jamesfredley wants to merge 3 commits into
Conversation
Groovy 5 defaults indy on. Only modules that apply the Grails Gradle plugin inherited grails.indy=false, so published framework artifacts were mixed. Centralize indy=false in CompilePlugin and apply gradle/groovy-indy.gradle from the grails-core, grails-gradle, and grails-forge builds. CI can still opt in with -PgrailsIndy=true. See #15293 Assisted-by: Sisyphus:grok-4.6
There was a problem hiding this comment.
Pull request overview
Centralizes Groovy invokedynamic (“indy”) defaults for the Grails 8 build so all framework/related modules compile with indy disabled by default (with an opt-in via -PgrailsIndy=true), avoiding mixed bytecode across the monorepo and addressing the performance regression discussed in #15293.
Changes:
- Add a shared Gradle script to set
GroovyCompile.groovyOptions.optimizationOptions.indyconsistently across the independent builds (grails-core, grails-gradle, grails-forge). - Update
org.apache.grails.buildsrc.compile(CompilePlugin) to default indy tofalse, with agrailsIndyproperty override. - Add a TestKit spec asserting the default/override behavior for Groovy compile tasks.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
build.gradle |
Applies the shared gradle/groovy-indy.gradle script to all subprojects in grails-core. |
grails-gradle/build.gradle |
Applies the shared indy-default script to all grails-gradle subprojects. |
grails-forge/build.gradle |
Applies the shared indy-default script to all grails-forge subprojects. |
gradle/groovy-indy.gradle |
New shared script that sets indy default (with -PgrailsIndy opt-in). |
gradle/grails-extension-gradle-config.gradle |
Clarifies in comments that non-plugin modules inherit the same default via CompilePlugin. |
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy |
Sets GroovyCompile optimization option indy from grailsIndy property (default false). |
build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy |
New TestKit spec validating indy default and property override. |
build-logic/plugins/build.gradle |
Sets local indy default for build-logic/plugins (can’t apply CompilePlugin to itself). |
build-logic/docs-core/build.gradle |
Sets local indy default for docs-core (doesn’t apply CompilePlugin). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #16178 +/- ##
==================================================
- Coverage 53.6425% 53.5306% -0.1119%
- Complexity 19783 19786 +3
==================================================
Files 2086 2086
Lines 99630 99687 +57
Branches 17594 17628 +34
==================================================
- Hits 53444 53363 -81
- Misses 38542 38551 +9
- Partials 7644 7773 +129
🚀 New features to boost your workflow:
|
With indy off, log.debug inside GormStaticApi.count()'s session callback was dispatched as Domain.debug(...). Capture the @slf4j logger in a local first. Also trim -PgrailsIndy the same way as CompilePlugin and apply the shared groovy-indy script from build-logic. Assisted-by: Sisyphus:grok-4.6
Class.newInstance(Map) is not selected under @CompileStatic when invokedynamic is disabled. Use InvokerHelper.invokeConstructorOf with an explicit Object[] so nested Map-constructor types still bind. Also avoid `null as boolean` in the Map-constructor test fixture, which Groovy 5 throws on without indy after unbindable properties are filtered from constructor arguments. Assisted-by: Sisyphus:grok-4.6
✅ All tests passed ✅🏷️ Commit: b224f33 Learn more about TestLens at testlens.app/docs. |
Summary
If Grails 9 on Groovy 6, this will flip the other direction: #16165
Groovy 5's compiler default is invokedynamic on. In this repo, only modules that apply the Grails Gradle plugin inherited
grails.indy = false. Everything else (grails-gradle,grails-forge,build-logic, and published framework modules that only applyorg.apache.grails.buildsrc.compile) compiled with indy on. That left mixed bytecode and the #15293 performance regression in artifacts that never went through the Grails plugin.This PR turns indy off for the entire Grails 8 compile, from one shared default, and applies it in all three independent builds.
How it is centralized
CompilePlugin(org.apache.grails.buildsrc.compile) now setsoptimizationOptions.indy = false. That is the historical GroovyCompile convention plugin used by framework,grails-gradle, andgrails-forgemodules.gradle/groovy-indy.gradleis applied fromsubprojectsin:build.gradle)grails-gradle/build.gradle)grails-forge/build.gradle)build-logiccannot applyCompilePluginto itself (it compiles that plugin).pluginsanddocs-coreset the same default locally.-PgrailsIndy=true(same property asgrails-extension-gradle-config.gradle).GrailsExtension.indystill defaults tofalse, andgrails { indy = true }/ the GraalVM native convention still win inafterEvaluate.Grails 9 / Groovy 6 can flip this default the other way when indy is ready.
Verification
CompilePluginSpecassertscompileGroovy/compileTestGroovyare indy=false by default and true with-PgrailsIndy=true:build-logic:testand:grails-docs-core:testpasshelpconfigures cleanly in grails-core, grails-gradle, and grails-forgejavapongrails-encoderCodecMetaClassSupportshows classicCallSiteArray, not invokedynamic dispatchFixes #15293 (build-side: stop compiling the framework with Groovy's indy default)
See also #15431 (warning noise when indy is disabled; not changed here).