Skip to content

build: disable Groovy invokedynamic for the Grails 8 compile - #16178

Open
jamesfredley wants to merge 3 commits into
8.0.xfrom
fix/disable-groovy-indy-build
Open

build: disable Groovy invokedynamic for the Grails 8 compile#16178
jamesfredley wants to merge 3 commits into
8.0.xfrom
fix/disable-groovy-indy-build

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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 apply org.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 sets optimizationOptions.indy = false. That is the historical GroovyCompile convention plugin used by framework, grails-gradle, and grails-forge modules.
  • gradle/groovy-indy.gradle is applied from subprojects in:
    • grails-core (build.gradle)
    • grails-gradle (grails-gradle/build.gradle)
    • grails-forge (grails-forge/build.gradle)
  • build-logic cannot apply CompilePlugin to itself (it compiles that plugin). plugins and docs-core set the same default locally.
  • CI can still opt in with -PgrailsIndy=true (same property as grails-extension-gradle-config.gradle).
  • Applications are unchanged: GrailsExtension.indy still defaults to false, and grails { indy = true } / the GraalVM native convention still win in afterEvaluate.

Grails 9 / Groovy 6 can flip this default the other way when indy is ready.

Verification

  • CompilePluginSpec asserts compileGroovy / compileTestGroovy are indy=false by default and true with -PgrailsIndy=true
  • :build-logic:test and :grails-docs-core:test pass
  • help configures cleanly in grails-core, grails-gradle, and grails-forge
  • javap on grails-encoder CodecMetaClassSupport shows classic CallSiteArray, not invokedynamic dispatch

Fixes #15293 (build-side: stop compiling the framework with Groovy's indy default)

See also #15431 (warning noise when indy is disabled; not changed here).

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
Copilot AI lite review requested due to automatic review settings August 19, 2026 23:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.indy consistently across the independent builds (grails-core, grails-gradle, grails-forge).
  • Update org.apache.grails.buildsrc.compile (CompilePlugin) to default indy to false, with a grailsIndy property 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.

Comment thread build-logic/docs-core/build.gradle Outdated
Comment thread gradle/groovy-indy.gradle
Comment thread build-logic/plugins/build.gradle Outdated
Comment thread gradle/grails-extension-gradle-config.gradle Outdated
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.5306%. Comparing base (d0c68dc) to head (b224f33).
⚠️ Report is 7 commits behind head on 8.0.x.

Files with missing lines Patch % Lines
.../grails/web/databinding/GrailsWebDataBinder.groovy 0.0000% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                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     
Files with missing lines Coverage Δ
.../groovy/grails/databinding/SimpleDataBinder.groovy 74.3210% <100.0000%> (-0.1840%) ⬇️
...ovy/org/grails/datastore/gorm/GormStaticApi.groovy 76.1290% <100.0000%> (-0.2464%) ⬇️
.../grails/web/databinding/GrailsWebDataBinder.groovy 30.1370% <0.0000%> (-0.2232%) ⬇️

... and 64 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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
@testlens-app

testlens-app Bot commented Aug 20, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: b224f33
▶️ Tests: 66256 executed
⚪️ Checks: 80/80 completed


Learn more about TestLens at testlens.app/docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Groovy - invoke dynamic performance problems

2 participants