codeconsole opened a new pull request, #16170: URL: https://github.com/apache/grails-core/pull/16170
Groovy compiles dynamic dispatch either as `invokedynamic` or as call-site caching bytecode. Which is faster depends on the application, but the choice was previously fixed at compile time and could not reach the framework and plugin jars an application already depends on. This publishes every Groovy module and Grails plugin twice: a default artifact using Groovy's own `invokedynamic` default, and a `noindy` classifier artifact compiled without it. An application sets `grails.indy` and the choice propagates across the whole dependency graph. ### Why not the classifier alone A Maven classifier artifact shares the POM of the main artifact, so asking for one by classifier pulls the *default* flavour of everything it depends on — one noindy jar in a graph of indy jars. The noindy artifact is instead published as a **secondary variant** of `apiElements`/`runtimeElements`, which inherits their dependencies, so a single request applies transitively. The classifier remains how the artifact is packaged, not how it is selected. ### Only the noindy variants declare the attribute The default variants deliberately leave `org.apache.grails.indy` unset. A consumer that never requests it — any plain Gradle project not applying a Grails plugin — sees one candidate and resolves the default artifact exactly as before. Declaring it on both variants makes every such build fail with `There are several available matching variants`, which an earlier revision of this branch did. A dependency publishing a single artifact (a plugin built before this, or one opting out) stays resolvable under either setting, since the two flavours interoperate on the same classpath. ### Default now follows Groovy `grails.indy` defaulted to `false`; it now defaults to `true`. Native compilation is the reason: call-site caching bytecode links call sites at runtime and cannot be compiled ahead of time, so every artifact on a native application's classpath must be the `invokedynamic` flavour. `indy = false` is an opt-out that forfeits native compilation. 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`. `grails.indy` no longer governs how a plugin compiles its own sources, since a plugin builds both flavours and the resolving application chooses. ### Tests 8 functional tests in `GrailsIndyVariantsSpec` covering: both flavours compiled and byte-different, variants published on both element configurations, application selection, fallback for single-artifact dependencies, the new default, and that a plain Gradle consumer is unaffected. Verified on a real framework module: `AllowedMethodsHelper.class` carries 2 `invokedynamic` instructions in the default jar and 0 in the noindy jar. `grails-cache`, where both producer paths can reach the same project, builds one `noindyJar` without collision. ### Reviewer notes - Full `./gradlew build` has not been run — 2 of ~137 modules were built locally. - The cost is real: Groovy compilation roughly doubles, as does the artifact count per release (signing, SBOM, vulnerability scan). - Given the native constraint, an alternative worth weighing is dropping dual publication entirely and making `indy` an application-only option so every published artifact is `invokedynamic`. A companion PR follows with that approach. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
