Skip to content

feat: keep published artifacts on invokedynamic, let applications choose - #16171

Open
codeconsole wants to merge 1 commit into
apache:8.0.xfrom
codeconsole:feat/indy-app-only
Open

feat: keep published artifacts on invokedynamic, let applications choose#16171
codeconsole wants to merge 1 commit into
apache:8.0.xfrom
codeconsole:feat/indy-app-only

Conversation

@codeconsole

Copy link
Copy Markdown
Contributor

Alternative to #16170. Same problem, opposite conclusion: instead of publishing every artifact twice, publish each one once — always as invokedynamic.

Why

Call-site caching links call sites at runtime, which ahead-of-time native compilation cannot do. A native image needs every class on the classpath to be the invokedynamic flavour, so one dependency compiled the other way rules out a native image for every application that depends on it.

That makes the flavour of a published artifact not really its own build's decision. Today a plugin author can set grails { indy = false } and silently strip native compatibility from every application using that plugin.

What changes

A project applying org.apache.grails.gradle.grails-plugin always compiles its published classes with invokedynamic and ignores grails.indy. Plugin authors need to change nothing; a plugin build that sets indy can drop it.

grails.indy still applies to applications, which sit at the end of the classpath and may choose for their own sources. It now defaults to true, following Groovy's own default, rather than false. An application setting it to false opts itself out of native compilation but still runs correctly against the invokedynamic artifacts of the framework and its plugins — the two flavours interoperate on the same classpath.

This also resolves an existing incoherence: the framework currently ships mixed bytecode (grails-controllers indy-on, grails-gsp indy-off) purely from which modules happened to adopt grails-plugin. Every published artifact is now invokedynamic.

Compared to #16170

#16170 (dual artifacts) this PR
Published artifacts 2 per module 1 per module
Diff 2 new plugin classes + test projects 73 insertions, 10 deletions, 6 files
Groovy compilation ~doubles unchanged
Release surface doubles (signing, SBOM, scanning) unchanged
Native compilation possible unless the app opts out possible unless the app opts out
App can use call-site caching yes, for its own code and its dependencies yes, for its own code

The noindy artifacts that #16170 publishes can never be used in a native build, so they double the release surface to serve only the non-native case — which this PR still supports for an application's own code at no artifact cost.

Verification

4 functional tests in GrailsIndyAppOnlySpec: a plugin that explicitly sets indy = false still emits PLUGIN_BYTECODE=indy=true,callsite=false; no second-flavour tasks exist; an application can still choose call-site caching for itself; an application configuring nothing follows Groovy's default.

On a real module: grails-cache applies grails-plugin and compiled with zero invokedynamic instructions on 8.0.x. On this branch, 17 of its 37 classes carry them.

Suite: 96/99 passing. The 3 GroovyPageToolchainSpec failures are environmental — they need a JDK 21 toolchain that is not installed on the machine I ran them on, and they fail identically on unmodified 8.0.x. codeStyle clean.

Reviewer notes

  • Full ./gradlew build has not been run; one framework module was built and inspected locally.
  • The 29 modules here that apply grails-plugin change from indy-off to indy-on, which is the intended unification but is the change most likely to show in a performance comparison against 7.x.

Callsite caching links call sites at runtime and cannot be compiled ahead
of time, so a native image needs every class on the classpath to be the
invokedynamic flavour. One dependency compiled the other way rules out a
native image for every application that uses it.

A project applying the grails-plugin plugin therefore always compiles its
published classes with invokedynamic and ignores grails.indy, so a plugin
build can no longer strip native compatibility from its consumers. Plugin
authors need to change nothing; a plugin build that sets indy can drop it.

grails.indy still applies to applications, which sit at the end of the
classpath and may choose for their own sources, and now defaults to true
following Groovy's own default rather than false. An application setting
it to false opts itself out of native compilation but still runs correctly
against the invokedynamic artifacts of the framework and its plugins.

The -PgrailsIndy toggle used by CI now applies only to the
application-type projects in this repository.
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 11.11111% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.3204%. Comparing base (deac428) to head (2c9410c).

Files with missing lines Patch % Lines
...rails/gradle/plugin/core/GrailsGradlePlugin.groovy 0.0000% 7 Missing ⚠️
...gradle/plugin/core/GrailsPluginGradlePlugin.groovy 0.0000% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #16171        +/-   ##
==================================================
- Coverage     52.3364%   52.3204%   -0.0160%     
+ Complexity      18545      18519        -26     
==================================================
  Files            2039       2039                
  Lines           97521      97524         +3     
  Branches        17143      17159        +16     
==================================================
- Hits            51039      51025        -14     
- Misses          38994      39034        +40     
+ Partials         7488       7465        -23     
Files with missing lines Coverage Δ
...g/grails/gradle/plugin/core/GrailsExtension.groovy 54.3478% <100.0000%> (ø)
...gradle/plugin/core/GrailsPluginGradlePlugin.groovy 0.0000% <0.0000%> (ø)
...rails/gradle/plugin/core/GrailsGradlePlugin.groovy 0.0000% <0.0000%> (ø)

... and 11 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.

@testlens-app

testlens-app Bot commented Aug 19, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 2c9410c
▶️ Tests: 60028 executed
⚪️ Checks: 77/77 completed


Learn more about TestLens at testlens.app.

@jdaugherty

jdaugherty commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

I would 100% support using a classifier to let end users chose. I do not support having indy be the default for Groovy 5 though. The performance isn't there yet and it's enough that we already decided to remove it. I'm a -1 on making it the default until we can get the performance to be on par (which I think Groovy 6 does)

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.

2 participants