This is an automated email from the ASF dual-hosted git repository.

jamesfredley pushed a commit to branch fix/disable-groovy-indy-build
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit d9a19f508b92b7b3e6059dbb9b090f48b0ce6d53
Author: James Fredley <[email protected]>
AuthorDate: Wed Aug 19 19:41:25 2026 -0400

    build: disable Groovy invokedynamic for the Grails 8 compile
    
    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
---
 build-logic/docs-core/build.gradle                 |  7 ++
 build-logic/plugins/build.gradle                   |  7 ++
 .../apache/grails/buildsrc/CompilePlugin.groovy    |  7 ++
 .../grails/buildsrc/CompilePluginSpec.groovy       | 93 ++++++++++++++++++++++
 build.gradle                                       |  2 +
 gradle/grails-extension-gradle-config.gradle       |  2 +
 gradle/groovy-indy.gradle                          | 34 ++++++++
 grails-forge/build.gradle                          |  2 +
 grails-gradle/build.gradle                         |  2 +
 9 files changed, 156 insertions(+)

diff --git a/build-logic/docs-core/build.gradle 
b/build-logic/docs-core/build.gradle
index d3459b3331..c52c7515df 100644
--- a/build-logic/docs-core/build.gradle
+++ b/build-logic/docs-core/build.gradle
@@ -71,6 +71,13 @@ sourceSets {
     }
 }
 
+// docs-core does not apply org.apache.grails.buildsrc.compile. Keep the same
+// Grails 8 default (indy off) as CompilePlugin. See #15293.
+tasks.withType(GroovyCompile).configureEach {
+    groovyOptions.optimizationOptions.indy = project.hasProperty('grailsIndy') 
&&
+            Boolean.parseBoolean(project.property('grailsIndy') as String)
+}
+
 def docFilesJar = tasks.register('docFilesJar', Jar)
 docFilesJar.configure {Jar it ->
     it.description = 'Package up files used for generating documentation.'
diff --git a/build-logic/plugins/build.gradle b/build-logic/plugins/build.gradle
index e6d687e1f8..b7c64b86a3 100644
--- a/build-logic/plugins/build.gradle
+++ b/build-logic/plugins/build.gradle
@@ -57,6 +57,13 @@ tasks.named('test') {
     useJUnitPlatform()
 }
 
+// This project compiles CompilePlugin itself, so it cannot apply that plugin.
+// Keep the same Grails 8 default (indy off) as CompilePlugin. See #15293.
+tasks.withType(GroovyCompile).configureEach {
+    groovyOptions.optimizationOptions.indy = project.hasProperty('grailsIndy') 
&&
+            Boolean.parseBoolean(project.property('grailsIndy') as String)
+}
+
 gradlePlugin {
     plugins {
         register('compilePlugin') {
diff --git 
a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
 
b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
index 38c31e0b12..5275928991 100644
--- 
a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
+++ 
b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/CompilePlugin.groovy
@@ -35,6 +35,7 @@ import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.api.tasks.javadoc.Javadoc
 import org.gradle.external.javadoc.StandardJavadocDocletOptions
 
+import static org.apache.grails.buildsrc.GradleUtils.lookupProperty
 import static org.apache.grails.buildsrc.GradleUtils.lookupPropertyByType
 
 @CompileStatic
@@ -107,6 +108,12 @@ class CompilePlugin implements Plugin<Project> {
                 it.groovyOptions.encoding = StandardCharsets.UTF_8.name()
                 // Preserve method parameter names in Groovy/Java classes for 
IDE parameter hints & bean reflection metadata.
                 it.groovyOptions.parameters = true
+                // Grails 8 keeps invokedynamic off. Groovy 5's compiler 
default is indy=true,
+                // which is a large runtime regression for dynamic Groovy (see 
#15293). Modules
+                // that do not apply the Grails Gradle plugin would otherwise 
inherit that
+                // default. Grails 9 / Groovy 6 can flip this. CI can still 
opt in with
+                // -PgrailsIndy=true (same property as 
grails-extension-gradle-config.gradle).
+                it.groovyOptions.optimizationOptions.put('indy', 
lookupProperty(project, 'grailsIndy', false))
                 // encoding needs to be the same since it's different across 
platforms
                 it.options.encoding = StandardCharsets.UTF_8.name()
                 it.options.fork = true
diff --git 
a/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy
 
b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy
new file mode 100644
index 0000000000..04b707a2ae
--- /dev/null
+++ 
b/build-logic/plugins/src/test/groovy/org/apache/grails/buildsrc/CompilePluginSpec.groovy
@@ -0,0 +1,93 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.grails.buildsrc
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import spock.lang.Specification
+import spock.lang.TempDir
+
+import java.nio.file.Path
+
+class CompilePluginSpec extends Specification {
+
+    @TempDir
+    Path testProjectDir
+
+    def setup() {
+        testProjectDir.resolve('settings.gradle').toFile().text = ''
+        testProjectDir.resolve('.asf.yaml').toFile().text = ''
+        def configScript = 
testProjectDir.resolve('gradle/groovy-compile-configscript.groovy').toFile()
+        configScript.parentFile.mkdirs()
+        configScript.text = ''
+        testProjectDir.resolve('build.gradle').toFile().text = """
+            plugins {
+                id 'groovy'
+                id 'org.apache.grails.buildsrc.compile'
+            }
+
+            ext {
+                javaVersion = 21
+                grailsVersion = '8.0.0-SNAPSHOT'
+                formattedBuildDate = '2026-01-01'
+            }
+
+            repositories {
+                mavenCentral()
+            }
+
+            tasks.register('printIndy') {
+                def compileTask = tasks.named('compileGroovy', 
org.gradle.api.tasks.compile.GroovyCompile)
+                def testCompileTask = tasks.named('compileTestGroovy', 
org.gradle.api.tasks.compile.GroovyCompile)
+                doLast {
+                    println 
"MAIN_INDY=\${compileTask.get().groovyOptions.optimizationOptions.indy}"
+                    println 
"TEST_INDY=\${testCompileTask.get().groovyOptions.optimizationOptions.indy}"
+                }
+            }
+        """
+    }
+
+    def "disables invokedynamic on GroovyCompile tasks by default"() {
+        when:
+        def result = runPrintIndy()
+
+        then:
+        result.task(':printIndy').outcome == TaskOutcome.SUCCESS
+        result.output.contains('MAIN_INDY=false')
+        result.output.contains('TEST_INDY=false')
+    }
+
+    def "enables invokedynamic when grailsIndy is true"() {
+        when:
+        def result = runPrintIndy('-PgrailsIndy=true')
+
+        then:
+        result.task(':printIndy').outcome == TaskOutcome.SUCCESS
+        result.output.contains('MAIN_INDY=true')
+        result.output.contains('TEST_INDY=true')
+    }
+
+    private def runPrintIndy(String... extraArgs) {
+        GradleRunner.create()
+                .withProjectDir(testProjectDir.toFile())
+                .withArguments(['printIndy', '--stacktrace'] + (extraArgs as 
List))
+                .withPluginClasspath()
+                .build()
+    }
+}
diff --git a/build.gradle b/build.gradle
index 61f25361f5..b8fa7f63e5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -109,6 +109,8 @@ final class ActiveProcessorCountArgumentProvider implements 
CommandLineArgumentP
 
 subprojects {
 
+    apply from: 
rootProject.layout.projectDirectory.file('gradle/groovy-indy.gradle')
+
     tasks.withType(Test).configureEach { testTask ->
         testTask.jvmArgumentProviders.add(new 
ActiveProcessorCountArgumentProvider(
                 Runtime.runtime.availableProcessors(), 
gradle.startParameter.maxWorkerCount))
diff --git a/gradle/grails-extension-gradle-config.gradle 
b/gradle/grails-extension-gradle-config.gradle
index d491c3f2e5..bb3cc52e28 100644
--- a/gradle/grails-extension-gradle-config.gradle
+++ b/gradle/grails-extension-gradle-config.gradle
@@ -35,6 +35,8 @@ grails {
     // Allow CI to toggle Groovy invokedynamic (indy) via -PgrailsIndy=true
     // This enables testing functional tests with both indy enabled and 
disabled.
     // See: https://github.com/apache/grails-core/issues/15321
+    // Framework modules that do not apply this plugin inherit the same default
+    // from org.apache.grails.buildsrc.compile (CompilePlugin).
     if (project.hasProperty('grailsIndy')) {
         indy = Boolean.parseBoolean(project.property('grailsIndy') as String)
     }
diff --git a/gradle/groovy-indy.gradle b/gradle/groovy-indy.gradle
new file mode 100644
index 0000000000..33fc608be8
--- /dev/null
+++ b/gradle/groovy-indy.gradle
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+// Grails 8 keeps invokedynamic off. Groovy 5's compiler default is indy=true,
+// which is a large runtime regression for dynamic Groovy (see #15293).
+// Applied from each independent build (grails-core, grails-gradle, 
grails-forge)
+// so modules that never apply the Grails Gradle plugin still inherit this.
+// Grails 9 / Groovy 6 can flip the default. CI can still opt in with 
-PgrailsIndy=true.
+boolean grailsIndyEnabled = false
+if (project.hasProperty('grailsIndy')) {
+    grailsIndyEnabled = Boolean.parseBoolean(project.property('grailsIndy') as 
String)
+}
+
+project.pluginManager.withPlugin('groovy') {
+    
project.tasks.withType(org.gradle.api.tasks.compile.GroovyCompile).configureEach
 { compileTask ->
+        compileTask.groovyOptions.optimizationOptions.indy = grailsIndyEnabled
+    }
+}
diff --git a/grails-forge/build.gradle b/grails-forge/build.gradle
index a149ee6e11..81570aaa0b 100644
--- a/grails-forge/build.gradle
+++ b/grails-forge/build.gradle
@@ -83,6 +83,8 @@ allprojects {
 }
 
 subprojects {
+    apply from: 
rootProject.layout.projectDirectory.file('../gradle/groovy-indy.gradle')
+
     configurations.configureEach {
         resolutionStrategy {
             def cacheHours = isCiBuild || isReproducibleBuild ? 0 : 24
diff --git a/grails-gradle/build.gradle b/grails-gradle/build.gradle
index bc42c184e2..43b1ee10b4 100644
--- a/grails-gradle/build.gradle
+++ b/grails-gradle/build.gradle
@@ -72,6 +72,8 @@ final class ActiveProcessorCountArgumentProvider implements 
CommandLineArgumentP
 }
 
 subprojects {
+    apply from: 
rootProject.layout.projectDirectory.file('../gradle/groovy-indy.gradle')
+
     tasks.withType(Test).configureEach { testTask ->
         testTask.jvmArgumentProviders.add(new 
ActiveProcessorCountArgumentProvider(
                 Runtime.runtime.availableProcessors(), 
gradle.startParameter.maxWorkerCount))

Reply via email to