You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/08/19 23:42:17 UTC
(grails-core) 01/01: build: disable Groovy invokedynamic for the Grails 8 compile
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 <ja...@users.noreply.github.com>
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))