build: disable Groovy invokedynamic for the Grails 8 compile - #16178
Conversation
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 54.8357% 54.7232% -0.1125%
- Complexity 20523 20532 +9
==================================================
Files 2104 2104
Lines 101102 101149 +47
Branches 17932 17966 +34
==================================================
- Hits 55440 55352 -88
- Misses 37771 37774 +3
- Partials 7891 8023 +132
🚀 New features to boost your workflow:
|
jdaugherty
left a comment
There was a problem hiding this comment.
- we should be using composition and not using the subprojects { } block in gradle files
- we should be exclusively applying this to the CompilePlugin since it's supposed to be applied to any project that's being published.
- for gradle plugins that are not published, why do we need to even toggle indy? We should use what Gradle's default is.
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
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
Honor review: drop groovy-indy.gradle and subprojects applies. Unpublished build-logic uses Gradle's Groovy default.
b224f33 to
505542f
Compare
|
Addressed @jdaugherty review: indy-off is only in CompilePlugin (published artifacts). Removed groovy-indy.gradle and the subprojects applies. Unpublished build-logic/docs-core and plugins use Gradle's Groovy default. Rebased onto latest 8.0.x. |
b16e242 to
758ce89
Compare
758ce89 to
6fce5e1
Compare
Take #16178's CompilePlugin -PgrailsIndy toggle and flip the default to true so Grails 9 / Groovy 6 framework modules match app-level grails { indy } (already default-on). CI can still pass -PgrailsIndy=false. Assisted-by: Sisyphus:grok-4.6
🚨 TestLens detected 1 failed test 🚨Here is what you can do:
Test SummaryCI / Functional Tests (Java 25, indy=false) > :grails-test-examples-scaffolding:integrationTest
🏷️ Commit: 6fce5e1 Test FailuresUserControllerSpec > User list (:grails-test-examples-scaffolding:integrationTest in CI / Functional Tests (Java 25, indy=false))Rerun ControlsNote Checks are currently running using the configuration below. Select tests to mute in this pull request: 🔲 UserControllerSpec > User list Reuse successful test results: 🔲 ♻️ Only rerun the tests that failed or were muted before Click the checkbox to trigger a rerun: 🔲 Rerun jobs 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).