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/10/08 17:39:31 UTC

(grails-micronaut) 01/01: build: align grails-micronaut with released Grails 8.0.0

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

jamesfredley pushed a commit to branch build/align-grails-8.0.0
in repository https://gitbox.apache.org/repos/asf/grails-micronaut.git

commit 8b330d0151d26d8af30ae541213d74b62fa729eb
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Thu Oct 8 13:38:28 2026 -0400

    build: align grails-micronaut with released Grails 8.0.0
    
    Keep this repository at 8.0.0-SNAPSHOT and consume Grails 8.0.0 through grailsVersion.
    Pin the Gradle, Groovy, grails-publish, and asset-pipeline versions that 8.0.0 shipped,
    and stamp projectVersion onto the compiled plugin descriptor.
---
 .sdkmanrc                                          |   2 +-
 README.md                                          |   2 +-
 RELEASE.md                                         |   3 +
 build-logic/gradle/wrapper/gradle-wrapper.jar      | Bin 47505 -> 47623 bytes
 .../gradle/wrapper/gradle-wrapper.properties       |   2 +-
 build-logic/gradlew.bat                            |  52 ++++++++++---
 .../apache/grails/buildsrc/CompilePlugin.groovy    |  76 ++++++++++++++++++-
 .../grails/buildsrc/SharedPropertyPlugin.groovy    |   4 +-
 .../grails/buildsrc/CompilePluginSpec.groovy       |  84 +++++++++++++++++++++
 dependencies.gradle                                |   4 +-
 gradle.properties                                  |   6 +-
 gradle/wrapper/gradle-wrapper.jar                  | Bin 47505 -> 47623 bytes
 gradle/wrapper/gradle-wrapper.properties           |   2 +-
 gradlew.bat                                        |  52 ++++++++++---
 grails-bom/hibernate5-micronaut/build.gradle       |   8 +-
 grails-bom/hibernate7-micronaut/build.gradle       |   8 +-
 grails-bom/micronaut/build.gradle                  |   4 +-
 settings.gradle                                    |   4 +-
 18 files changed, 270 insertions(+), 43 deletions(-)

diff --git a/.sdkmanrc b/.sdkmanrc
index 6e3315ad47..952c983ee5 100644
--- a/.sdkmanrc
+++ b/.sdkmanrc
@@ -1,4 +1,4 @@
 # Micronaut 5 targets JVM 25 bytecode, so this repository builds and publishes on JDK 25.
 java=25.0.3-librca
 # Keep the Gradle version synced with gradle.properties and the wrapper.
-gradle=9.7.1
+gradle=9.8.0
diff --git a/README.md b/README.md
index daf89b185b..490fe4561e 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ limitations under the License.
 
 This repository contains the Grails Micronaut plugin, its Micronaut-specific BOMs, and focused integration examples.
 
-The build consumes published `org.apache.grails` artifacts from the Grails 8.0.x repositories. Only the four artifacts owned by this repository are built locally:
+The build consumes the released Grails 8.0.0 artifacts (`grailsVersion` in `gradle.properties`). This repository's own version stays `8.0.0-SNAPSHOT` until its release tag. Only the four artifacts owned by this repository are built locally:
 
 - `org.apache.grails:grails-micronaut`
 - `org.apache.grails:grails-micronaut-bom`
diff --git a/RELEASE.md b/RELEASE.md
index 02cddcd81d..c2be69f89b 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -68,6 +68,9 @@ Milestones and RCs go through the same process as a final release.
 
 - The tag points at the exact commit to release. No SNAPSHOT dependencies; Apache
   [release policy](https://www.apache.org/legal/release-policy.html) forbids them.
+- `projectVersion` is this repository's version. The release workflow overrides it with
+  `-PprojectVersion`. `grailsVersion` is the released Grails line (8.0.0) imported by the
+  BOMs and used by the Grails Gradle plugins. Do not point `grailsVersion` at a snapshot.
 - JDK is pinned in [`.sdkmanrc`](.sdkmanrc) (`25.0.3-librca`). CI and
   [`etc/bin/Dockerfile`](etc/bin/Dockerfile) must use the same Liberica patch.
 - Secrets and variables:
diff --git a/build-logic/gradle/wrapper/gradle-wrapper.jar b/build-logic/gradle/wrapper/gradle-wrapper.jar
index eddabd2eef..5097068a8d 100644
Binary files a/build-logic/gradle/wrapper/gradle-wrapper.jar and b/build-logic/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/build-logic/gradle/wrapper/gradle-wrapper.properties b/build-logic/gradle/wrapper/gradle-wrapper.properties
index c931ec80aa..fd6054824b 100644
--- a/build-logic/gradle/wrapper/gradle-wrapper.properties
+++ b/build-logic/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
 # Keep gradle version synced with .sdkmanrc, gradle.properties (gradleToolingApiVersion), and all other gradle-wrapper.properties files.
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
 networkTimeout=10000
 retries=0
 retryBackOffMs=500
diff --git a/build-logic/gradlew.bat b/build-logic/gradlew.bat
index a51ec4f588..3185a43f78 100755
--- a/build-logic/gradlew.bat
+++ b/build-logic/gradlew.bat
@@ -26,6 +26,33 @@
 @rem Set local scope for the variables, and ensure extensions are enabled
 setlocal EnableExtensions
 
+@rem Catch executions from older scripts and ensure they exit cleanly.
+@rem This can be removed once we can be reasonably confident that few people
+@rem will be migrating directly to this new wrapper.
+goto afterSafetyNet
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+goto exitWithErrorLevel
+:afterSafetyNet
+
 set DIRNAME=%~dp0
 if "%DIRNAME%"=="" set DIRNAME=.
 @rem This is normally unused
@@ -45,13 +72,14 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+1>&2 echo.
+1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+1>&2 echo.
+1>&2 echo Please set the JAVA_HOME variable in your environment to match the
+1>&2 echo location of your Java installation.
 
 "%COMSPEC%" /c exit 1
+goto exitWithErrorLevel
 
 :findJavaFromJavaHome
 set JAVA_HOME=%JAVA_HOME:"=%
@@ -59,13 +87,14 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 if exist "%JAVA_EXE%" goto execute
 
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+1>&2 echo.
+1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+1>&2 echo.
+1>&2 echo Please set the JAVA_HOME variable in your environment to match the
+1>&2 echo location of your Java installation.
 
 "%COMSPEC%" /c exit 1
+goto exitWithErrorLevel
 
 :execute
 @rem Setup the command line
@@ -75,8 +104,9 @@ echo location of your Java installation. 1>&2
 @rem Execute gradlew
 @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
 @rem which allows us to clear the local environment before executing the java command
-endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
+endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
 
+@rem This label must not be changed. We rely on old scripts being able to jump to this point.
 :exitWithErrorLevel
 @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
 "%COMSPEC%" /c exit %ERRORLEVEL%
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 4eb47d7ded..7b818434a3 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
@@ -26,6 +26,7 @@ import groovy.transform.CompileStatic
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.Task
 import org.gradle.api.file.DuplicatesStrategy
 import org.gradle.api.plugins.JavaPluginExtension
 import org.gradle.api.tasks.PathSensitivity
@@ -87,7 +88,8 @@ class CompilePlugin implements Plugin<Project> {
 
             jar.manifest.attributes(
                     'Implementation-Title': 'Apache Grails',
-                    'Implementation-Version': lookupPropertyByType(project, 'grailsVersion', String),
+                    'Implementation-Version': lookupProperty(project, 'projectVersion',
+                            lookupPropertyByType(project, 'grailsVersion', String)),
                     'Implementation-Vendor': 'grails.apache.org'
             )
             // Explicitly fail since duplicates indicate a double configuration that needs fixed
@@ -145,6 +147,24 @@ class CompilePlugin implements Plugin<Project> {
                 // when both are present.
                 it.groovyOptions.configurationScript =
                         GradleUtils.findRootGrailsCoreDir(project).file('gradle/groovy-compile-configscript.groovy').asFile
+            }
+                // Stamp this repository's published version onto compiled classes after the build
+                // script has assigned version. Without the stamp, the Grails transform falls back to
+                // grails-core's Implementation-Version, so a snapshot build of this plugin would
+                // advertise the released framework version. The script is generated by a task so
+                // `clean compileGroovy` can recreate it after `clean` deletes `build/`.
+            project.afterEvaluate {
+                // Applications and plugins built with the Grails Gradle plugin already stamp
+                // project.version. Replacing their merged compiler script would drop that plugin's
+                // own configuration. This repository's published plugin does not apply it.
+                if (project.plugins.hasPlugin('org.apache.grails.gradle.grails-plugin')
+                        || project.plugins.hasPlugin('org.apache.grails.gradle.grails-web')
+                        || project.plugins.hasPlugin('org.apache.grails.gradle.grails-gsp')) {
+                    return
+                }
+                project.tasks.withType(GroovyCompile).each { GroovyCompile compile ->
+                    configurePublishedVersionScript(project, compile)
+                }
             }
             project.tasks.named('compileGroovy', GroovyCompile).configure { GroovyCompile task ->
                 // Resource-only changes do not ordinarily invalidate compilation. This file changes
@@ -155,6 +175,60 @@ class CompilePlugin implements Plugin<Project> {
         }
     }
 
+    /**
+     * Writes the Groovy compiler configuration for {@code compile}, including the shared
+     * reproducibility script and this project's published version.
+     *
+     * <p>The published version is captured in {@code afterEvaluate}, after the build script assigns
+     * {@code version}. The script file is produced by a task, not written during configuration, so
+     * {@code clean compileGroovy} in one invocation regenerates it after {@code clean} deletes
+     * {@code build/}.</p>
+     */
+    private static void configurePublishedVersionScript(Project project, GroovyCompile compile) {
+        File baseScript = GradleUtils.findRootGrailsCoreDir(project)
+                .file('gradle/groovy-compile-configscript.groovy').asFile
+        File output = project.layout.buildDirectory.file("groovy-compiler/${compile.name}-config.groovy").get().asFile
+        // Captured here, inside afterEvaluate, so version is the build script's value and the
+        // writer action does not touch the project. A configuration-time write would not survive
+        // `clean`, and compileGroovy would then fail because nothing regenerates its input.
+        String version = project.version?.toString() ?: ''
+        String name = project.name
+        String writerName = "grailsCompilerConfig${compile.name.capitalize()}"
+        // mustRunAfter(clean): `clean compileGroovy` schedules both, and command-line order is not a
+        // dependency. Without this, clean can delete the script after the writer creates it.
+        Task writer = project.tasks.register(writerName) { Task task ->
+            task.mustRunAfter('clean')
+            task.inputs.file(baseScript).withPathSensitivity(PathSensitivity.NONE).withPropertyName('reproducibleCompilerScript')
+            task.inputs.property('publishedVersion', version)
+            task.inputs.property('publishedName', name)
+            task.outputs.file(output)
+            task.doLast {
+                writePublishedVersionScript(baseScript, output, version, name)
+            }
+        }.get()
+        compile.dependsOn(writer)
+        compile.groovyOptions.configurationScript = output
+    }
+
+    private static void writePublishedVersionScript(File baseScript, File output, String version, String name) {
+        output.parentFile.mkdirs()
+        String base = baseScript.exists() ? baseScript.getText(StandardCharsets.UTF_8.name()) : ''
+        String script = """${base}
+
+withConfig(configuration) {
+    inline(phase: 'CONVERSION') { source, context, classNode ->
+        classNode.putNodeMetaData('projectVersion', '${escapeGroovyLiteral(version)}')
+        classNode.putNodeMetaData('projectName', '${escapeGroovyLiteral(name)}')
+    }
+}
+"""
+        output.setText(script, StandardCharsets.UTF_8.name())
+    }
+
+    private static String escapeGroovyLiteral(String value) {
+        value.replace('\\', '\\\\').replace('\'', '\\\'')
+    }
+
     static void registerAutoConfigurationImportsInput(Project project, GroovyCompile task) {
         if (task.extensions.extraProperties.has(AUTO_CONFIGURATION_IMPORTS_INPUT_REGISTERED)) {
             return
diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SharedPropertyPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SharedPropertyPlugin.groovy
index ba2aed9064..8ced25cb37 100644
--- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SharedPropertyPlugin.groovy
+++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SharedPropertyPlugin.groovy
@@ -81,7 +81,9 @@ class SharedPropertyPlugin implements Plugin<Project> {
                     }
                 }
 
-                if (rootProperties.containsKey('projectVersion')) {
+                // An explicit grailsVersion pins the released Grails line. Fall back to
+                // projectVersion only when this build is still the Grails repository itself.
+                if (rootProperties.containsKey('projectVersion') && !ext.has('grailsVersion')) {
                     ext.set('grailsVersion', rootProperties.getProperty('projectVersion'))
                 }
             }
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
index 4ca4004cb9..239240120e 100644
--- 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
@@ -116,6 +116,90 @@ class CompilePluginSpec extends Specification {
         result.output.contains('TEST_INDY=true')
     }
 
+    def "compiler configuration stamps the published project version"() {
+        given:
+        testProjectDir.resolve('build.gradle').toFile().text = """
+            plugins {
+                id 'groovy'
+                id 'org.apache.grails.buildsrc.compile'
+            }
+
+            version = '8.0.0-SNAPSHOT'
+
+            ext {
+                javaVersion = 21
+                grailsVersion = '8.0.0'
+                formattedBuildDate = '2026-01-01'
+            }
+
+            repositories {
+                mavenCentral()
+            }
+        """
+
+        when:
+        def result = GradleRunner.create()
+                .withProjectDir(testProjectDir.toFile())
+                .withArguments(['grailsCompilerConfigCompileGroovy', '--stacktrace'])
+                .withPluginClasspath()
+                .build()
+
+        then:
+        result.task(':grailsCompilerConfigCompileGroovy').outcome == TaskOutcome.SUCCESS
+        def script = testProjectDir.resolve('build/groovy-compiler/compileGroovy-config.groovy').toFile().text
+        script.contains("putNodeMetaData('projectVersion', '8.0.0-SNAPSHOT')")
+        script.contains("putNodeMetaData('projectName',")
+    }
+
+    def "clean compileGroovy regenerates the compiler script in one invocation"() {
+        given:
+        def sourceDir = testProjectDir.resolve('src/main/groovy').toFile()
+        sourceDir.mkdirs()
+        new File(sourceDir, 'Hello.groovy').text = 'class Hello { String greet() { "ok" } }\n'
+        testProjectDir.resolve('build.gradle').toFile().text = """
+            plugins {
+                id 'groovy'
+                id 'org.apache.grails.buildsrc.compile'
+            }
+
+            version = '8.0.0-SNAPSHOT'
+
+            ext {
+                javaVersion = 21
+                grailsVersion = '8.0.0'
+                formattedBuildDate = '2026-01-01'
+            }
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                implementation localGroovy()
+            }
+        """
+        // Give clean real work. An empty build directory leaves clean UP_TO_DATE, which never
+        // proves the compiler script is recreated after deletion.
+        def staleBuild = testProjectDir.resolve('build').toFile()
+        staleBuild.mkdirs()
+        new File(staleBuild, 'delete-me.txt').text = 'stale'
+
+        when:
+        def result = GradleRunner.create()
+                .withProjectDir(testProjectDir.toFile())
+                .withArguments(['clean', 'compileGroovy', '--stacktrace'])
+                .withPluginClasspath()
+                .build()
+
+        then:
+        result.task(':clean').outcome == TaskOutcome.SUCCESS
+        result.task(':grailsCompilerConfigCompileGroovy').outcome == TaskOutcome.SUCCESS
+        result.task(':compileGroovy').outcome == TaskOutcome.SUCCESS
+        testProjectDir.resolve('build/groovy-compiler/compileGroovy-config.groovy').toFile().text
+                .contains("putNodeMetaData('projectVersion', '8.0.0-SNAPSHOT')")
+        testProjectDir.resolve('build/classes/groovy/main/Hello.class').toFile().exists()
+    }
+
     private def runPrintIndy(String... extraArgs) {
         GradleRunner.create()
                 .withProjectDir(testProjectDir.toFile())
diff --git a/dependencies.gradle b/dependencies.gradle
index e1f322a5c0..c2c4be4631 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -19,7 +19,7 @@ ext {
     gradleBomDependencyVersions = [
             'asm.version'                   : '9.10.1',
             'gradle-spock.version'          : '2.4-groovy-4.0',
-            'grails-publish-plugin.version' : '1.0.0-M2',
+            'grails-publish-plugin.version' : '1.0.0-RC2',
             'spring-boot.version'           : '4.1.1',
     ]
 
@@ -29,7 +29,7 @@ ext {
 
     micronautBomVersions = [
             'asm.version'     : '9.10.1',
-            'groovy.version'  : '5.1.0',
+            'groovy.version'  : '5.1.3',
             'protobuf.version': '4.30.2',
             'spock.version'   : '2.4-groovy-5.0',
     ]
diff --git a/gradle.properties b/gradle.properties
index 9c35d606f0..696e0eff51 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -14,11 +14,15 @@
 # limitations under the License.
 
 projectVersion=8.0.0-SNAPSHOT
+# Released Grails line consumed by the Gradle plugins and imported BOMs.
+# Kept separate from projectVersion so snapshot builds resolve Grails 8.0.0
+# rather than a Grails snapshot. -PprojectVersion overrides only this repository.
+grailsVersion=8.0.0
 javaVersion=25
 githubBranch=8.0.x
 githubSlug=apache/grails-micronaut
 
-gradleToolingApiVersion=9.7.1
+gradleToolingApiVersion=9.8.0
 micronautPlatformVersion=5.0.3
 ersatzVersion=4.0.1
 
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index eddabd2eef..5097068a8d 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index c931ec80aa..fd6054824b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
 # Keep gradle version synced with .sdkmanrc, gradle.properties (gradleToolingApiVersion), and all other gradle-wrapper.properties files.
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
 networkTimeout=10000
 retries=0
 retryBackOffMs=500
diff --git a/gradlew.bat b/gradlew.bat
index a51ec4f588..3185a43f78 100755
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -26,6 +26,33 @@
 @rem Set local scope for the variables, and ensure extensions are enabled
 setlocal EnableExtensions
 
+@rem Catch executions from older scripts and ensure they exit cleanly.
+@rem This can be removed once we can be reasonably confident that few people
+@rem will be migrating directly to this new wrapper.
+goto afterSafetyNet
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+goto exitWithErrorLevel
+:afterSafetyNet
+
 set DIRNAME=%~dp0
 if "%DIRNAME%"=="" set DIRNAME=.
 @rem This is normally unused
@@ -45,13 +72,14 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+1>&2 echo.
+1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+1>&2 echo.
+1>&2 echo Please set the JAVA_HOME variable in your environment to match the
+1>&2 echo location of your Java installation.
 
 "%COMSPEC%" /c exit 1
+goto exitWithErrorLevel
 
 :findJavaFromJavaHome
 set JAVA_HOME=%JAVA_HOME:"=%
@@ -59,13 +87,14 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 if exist "%JAVA_EXE%" goto execute
 
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+1>&2 echo.
+1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+1>&2 echo.
+1>&2 echo Please set the JAVA_HOME variable in your environment to match the
+1>&2 echo location of your Java installation.
 
 "%COMSPEC%" /c exit 1
+goto exitWithErrorLevel
 
 :execute
 @rem Setup the command line
@@ -75,8 +104,9 @@ echo location of your Java installation. 1>&2
 @rem Execute gradlew
 @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
 @rem which allows us to clear the local environment before executing the java command
-endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
+endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
 
+@rem This label must not be changed. We rely on old scripts being able to jump to this point.
 :exitWithErrorLevel
 @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
 "%COMSPEC%" /c exit %ERRORLEVEL%
diff --git a/grails-bom/hibernate5-micronaut/build.gradle b/grails-bom/hibernate5-micronaut/build.gradle
index c47c598543..d59c676324 100644
--- a/grails-bom/hibernate5-micronaut/build.gradle
+++ b/grails-bom/hibernate5-micronaut/build.gradle
@@ -39,12 +39,12 @@ Set<String> overriddenCoordinates = micronautBomDependencies.values().collect {
     "${parts[0]}:${parts[1]}" as String
 } as Set<String>
 
-flattenPublishedPlatformConstraints("org.apache.grails:grails-base-bom:$projectVersion", overriddenCoordinates)
-flattenPublishedPlatformConstraints("org.apache.grails:grails-hibernate5-bom:$projectVersion", overriddenCoordinates)
+flattenPublishedPlatformConstraints("org.apache.grails:grails-base-bom:$grailsVersion", overriddenCoordinates)
+flattenPublishedPlatformConstraints("org.apache.grails:grails-hibernate5-bom:$grailsVersion", overriddenCoordinates)
 
 dependencies {
     api platform(project(':grails-micronaut-bom'))
-    api(platform("org.apache.grails:grails-hibernate5-bom:$projectVersion")) {
+    api(platform("org.apache.grails:grails-hibernate5-bom:$grailsVersion")) {
         overriddenCoordinates.each { String coordinate ->
             List<String> parts = coordinate.split(':') as List<String>
             exclude group: parts[0], module: parts[1]
@@ -60,7 +60,7 @@ dependencies {
                 }
             }
         }
-        api "org.apache.grails:grails-data-hibernate5:$projectVersion"
+        api "org.apache.grails:grails-data-hibernate5:$grailsVersion"
     }
 }
 
diff --git a/grails-bom/hibernate7-micronaut/build.gradle b/grails-bom/hibernate7-micronaut/build.gradle
index c517dbd0f6..9a06ea2d99 100644
--- a/grails-bom/hibernate7-micronaut/build.gradle
+++ b/grails-bom/hibernate7-micronaut/build.gradle
@@ -39,12 +39,12 @@ Set<String> overriddenCoordinates = micronautBomDependencies.values().collect {
     "${parts[0]}:${parts[1]}" as String
 } as Set<String>
 
-flattenPublishedPlatformConstraints("org.apache.grails:grails-base-bom:$projectVersion", overriddenCoordinates)
-flattenPublishedPlatformConstraints("org.apache.grails:grails-hibernate7-bom:$projectVersion", overriddenCoordinates)
+flattenPublishedPlatformConstraints("org.apache.grails:grails-base-bom:$grailsVersion", overriddenCoordinates)
+flattenPublishedPlatformConstraints("org.apache.grails:grails-hibernate7-bom:$grailsVersion", overriddenCoordinates)
 
 dependencies {
     api platform(project(':grails-micronaut-bom'))
-    api(platform("org.apache.grails:grails-hibernate7-bom:$projectVersion")) {
+    api(platform("org.apache.grails:grails-hibernate7-bom:$grailsVersion")) {
         overriddenCoordinates.each { String coordinate ->
             List<String> parts = coordinate.split(':') as List<String>
             exclude group: parts[0], module: parts[1]
@@ -60,7 +60,7 @@ dependencies {
                 }
             }
         }
-        api "org.apache.grails:grails-data-hibernate7:$projectVersion"
+        api "org.apache.grails:grails-data-hibernate7:$grailsVersion"
     }
 }
 
diff --git a/grails-bom/micronaut/build.gradle b/grails-bom/micronaut/build.gradle
index 066757abbe..718568bacf 100644
--- a/grails-bom/micronaut/build.gradle
+++ b/grails-bom/micronaut/build.gradle
@@ -39,10 +39,10 @@ Set<String> overriddenCoordinates = micronautBomDependencies.values().collect {
     "${parts[0]}:${parts[1]}" as String
 } as Set<String>
 
-flattenPublishedPlatformConstraints("org.apache.grails:grails-base-bom:$projectVersion", overriddenCoordinates)
+flattenPublishedPlatformConstraints("org.apache.grails:grails-base-bom:$grailsVersion", overriddenCoordinates)
 
 dependencies {
-    api(platform("org.apache.grails:grails-base-bom:$projectVersion")) {
+    api(platform("org.apache.grails:grails-base-bom:$grailsVersion")) {
         overriddenCoordinates.each { String coordinate ->
             List<String> parts = coordinate.split(':') as List<String>
             exclude group: parts[0], module: parts[1]
diff --git a/settings.gradle b/settings.gradle
index ed7d76c206..3b1fbe03ea 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -27,11 +27,11 @@ pluginManagement {
         configurePluginRepositories(delegate)
     }
     plugins {
-        def grailsVersion = providers.gradleProperty('projectVersion').get()
+        def grailsVersion = providers.gradleProperty('grailsVersion').get()
         id 'org.apache.grails.gradle.grails-gsp' version grailsVersion
         id 'org.apache.grails.gradle.grails-plugin' version grailsVersion
         id 'org.apache.grails.gradle.grails-web' version grailsVersion
-        id 'cloud.wondrify.asset-pipeline' version '5.2.0-M3'
+        id 'cloud.wondrify.asset-pipeline' version '5.2.0'
         id 'org.nosphere.apache.rat' version '0.8.1'
     }
 }