Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ca495ab
feat: replace Spring Dependency Management plugin with Gradle platfor…
jamesfredley Feb 26, 2026
531041b
Address review: add XInclude hardening and extract interpolation dept…
jamesfredley Feb 26, 2026
5e89656
fix: apply grails-bom platform to all declarable configurations
jamesfredley Feb 26, 2026
1a2cea3
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Mar 20, 2026
30a46f6
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Mar 21, 2026
96fea95
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Mar 21, 2026
5607ddb
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Mar 21, 2026
575b77f
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Mar 30, 2026
a4511eb
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Mar 31, 2026
28a6817
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 16, 2026
77932f2
fix: allow Micronaut BOM validator to coexist with plugin-injected pl…
jamesfredley Apr 16, 2026
54fbe93
fix: exclude annotation-processor configurations from grails-bom plat…
jamesfredley Apr 16, 2026
4d1bcdb
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 24, 2026
90c1238
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 26, 2026
a2363ae
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 26, 2026
f0c1934
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 29, 2026
2ca302b
Extract BOM property overrides into a standalone Gradle plugin (PR #1…
jamesfredley Apr 29, 2026
bbf5e53
Fix GrailsDependencyValidatorPlugin to prefer enforcedPlatform BOM
jamesfredley Apr 29, 2026
9204972
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 29, 2026
0178a10
Merge branch '8.0.x' into feat/gradle-managed-version-overrides
jamesfredley Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.DependencyConstraint
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.VersionConstraint
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.attributes.Category

/**
* Validates that transitive dependencies do not replace versions what the
Expand Down Expand Up @@ -159,19 +161,49 @@ class GrailsDependencyValidatorPlugin implements Plugin<Project> {

/**
* Scans the project's configurations to find which BOM project is in use.
*
* <p>When multiple known BOMs are declared on the same project (for example,
* the {@code grails-app} plugin auto-injects {@code platform(grails-bom)} on
* every declarable configuration while a Micronaut project additionally
* declares {@code enforcedPlatform(grails-micronaut-bom)}), this method
* prefers an {@code enforcedPlatform} declaration over a regular
* {@code platform}. The enforced BOM is the one whose constraints actually
* win at resolution time, so it is the correct reference for the
* "expected" versions reported by the validator.</p>
*/
static String detectBomPath(Project project) {
String regularPlatformBomPath = null

for (Configuration config : project.configurations) {
for (Dependency dep : config.dependencies) {
if (BOM_PROJECT_NAMES.contains(dep.name)) {
Project bomProject = project.rootProject.findProject(":${dep.name}" as String)
if (bomProject != null) {
return bomProject.path
}
if (!BOM_PROJECT_NAMES.contains(dep.name)) {
continue
}
Project bomProject = project.rootProject.findProject(":${dep.name}" as String)
if (bomProject == null) {
continue
}
if (isEnforcedPlatformDependency(dep)) {
return bomProject.path
}
if (regularPlatformBomPath == null) {
regularPlatformBomPath = bomProject.path
}
}
}
null

regularPlatformBomPath
}

private static boolean isEnforcedPlatformDependency(Dependency dep) {
if (!(dep instanceof ModuleDependency)) {
return false
}
Object categoryAttr = ((ModuleDependency) dep).attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)
if (categoryAttr == null) {
return false
}
categoryAttr.toString() == Category.ENFORCED_PLATFORM
}

/**
Expand Down
2 changes: 1 addition & 1 deletion grails-bom/base/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ ext {
ExtractedDependencyConstraint extractedConstraint = propertyNameCalculator.calculate(groupId, artifactId, inlineVersion, isBom)
if (extractedConstraint?.versionPropertyReference) {
// use the property reference instead of the hard coded version so that it can be
// overriden by the spring boot dependency management plugin
// overridden by project properties (gradle.properties or ext['property.name'])
dep.version[0].value = extractedConstraint.versionPropertyReference

// Add an entry in the <properties> node with the actual version number
Expand Down
1 change: 0 additions & 1 deletion grails-data-graphql/examples/spring-boot-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ buildscript {
apply plugin: 'groovy'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.apache.grails.buildsrc.dependency-validator'

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,33 @@ dependencies {

Note that version numbers are not present in the majority of the dependencies.

This is thanks to the Spring dependency management plugin which automatically configures `grails-bom` as a Maven BOM via the Grails Gradle Plugin. This defines the default dependency versions for most commonly used dependencies and plugins.
This is thanks to Gradle's platform support which automatically imports `grails-bom` as a managed dependency platform via the Grails Gradle Plugin. This defines the default dependency versions for most commonly used dependencies and plugins.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The versions are only supported by your plugin. Also, the dependency management plugin causes the bom versions to resolve to the spring version and not to the highest version. We should discuss this side effect in the weekly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 2ca302b. The relevant section in grails-doc/src/en/guide/commandLine/gradleBuild/gradleDependencies.adoc now:

  1. Explicitly calls out that the property-override mechanism is a feature of the new org.apache.grails.gradle.bom-property-overrides plugin and links to Support overriding BOM version properties with ext gradle/gradle#9160 so readers know it isn't native Gradle behaviour.

  2. Adds a NOTE block describing the resolution side effect you raised: the plugin participates in Gradle's standard conflict resolution, so when an artifact is requested at multiple versions Gradle's highest-version-wins picks the version, and the property-override pins what wins after that. This contrasts with the legacy Spring DM plugin which forced BOM versions unconditionally and could mask transitive drift. enforcedPlatform(grails-bom) is documented as the escape hatch for stricter behaviour.

  3. Also documents how the new plugin can be applied standalone (non-Grails projects) so the property-override workflow isn't tied to grails-bom.

Happy to refine the side-effect wording further after the weekly discussion - leaving this thread open in case you want to expand on it.


For a Grails App, applying `org.apache.grails.gradle.grails-web` will automatically configure the `grails-bom`. No other steps required.
==== Overriding Managed Versions

For Plugins and Projects which do not use `org.apache.grails.gradle.grails-web`, you can apply the `grails-bom` in one of the following two ways.
To override a managed version, set the corresponding property in `gradle.properties` or `build.gradle`:
[source,groovy]
----
// gradle.properties
slf4j.version=1.7.36

// or build.gradle
ext['slf4j.version'] = '1.7.36'
----

The property override mechanism is a feature of the **Grails BOM Property Overrides Gradle plugin** (`org.apache.grails.gradle.bom-property-overrides`); standalone Gradle does not natively read `<properties>` from BOM POMs (see https://github.com/gradle/gradle/issues/9160[Gradle issue #9160]). The plugin parses the BOM POM, builds a property→artifact mapping, and applies overrides via Gradle's `ResolutionStrategy.eachDependency()`.

[NOTE]
====
The plugin participates in Gradle's standard dependency resolution. When the same artifact is requested at multiple versions across a build (for example, when both `grails-bom` and a transitive dependency declare `slf4j-api`), Gradle's default *highest-version-wins* conflict resolution applies _before_ the property override is consulted. Setting `slf4j.version` therefore pins the version that will be used after conflict resolution rather than overriding the resolved version after the fact. This differs from the legacy Spring Dependency Management plugin, which forced BOM versions to win unconditionally and could mask transitive version drift. If you need stricter behaviour, declare `enforcedPlatform(grails-bom)` on the relevant configuration.
====

==== Applying the BOM in Different Project Types

For a Grails App, applying `org.apache.grails.gradle.grails-web` will automatically configure the `grails-bom` _and_ apply the `bom-property-overrides` plugin. No other steps required.

For Plugins and Projects which do not use `org.apache.grails.gradle.grails-web`, you can apply the `grails-bom` using Gradle Platforms:

build.gradle, using Gradle Platforms:
[source,groovy]
----
dependencies {
Expand All @@ -75,13 +95,35 @@ dependencies {
}
----

build.gradle, using Spring dependency management plugin:
==== Using `bom-property-overrides` Standalone (Non-Grails Projects)

The property-override mechanism is published as a standalone, BOM-agnostic Gradle plugin so it can be reused with any BOM that follows the Maven `<properties>` convention. Apply it directly when you want the same `gradle.properties` / `ext['…']` override workflow without applying any Grails plugin:

[source,groovy]
----
plugins {
id 'java-library'
id 'org.apache.grails.gradle.bom-property-overrides' version '{GrailsVersion}'
}

dependencies {
implementation platform('com.example:my-bom:1.0.0')
}

// gradle.properties or build.gradle
ext['slf4j.version'] = '2.0.13'
----

By default the plugin auto-detects every `platform()` and `enforcedPlatform()` dependency declared on the project's configurations and registers each one for property-override processing. You can disable auto-detection or register additional BOMs explicitly via the `bomPropertyOverrides` extension:

[source,groovy]
----
dependencyManagement {
imports {
mavenBom 'org.apache.grails:grails-bom:{GrailsVersion}'
}
applyMavenExclusions false
bomPropertyOverrides {
// Disable scanning declared platforms (default: true)
autoDetect = false

// Register specific BOMs (e.g. ones referenced indirectly)
bom 'com.example:my-bom:1.0.0'
bom 'com.example:other-bom:2.0.0'
}
----
81 changes: 81 additions & 0 deletions grails-gradle/bom-property-overrides/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.
*/

plugins {
id 'groovy'
id 'java-gradle-plugin'
id 'org.apache.grails.buildsrc.properties'
id 'org.apache.grails.buildsrc.dependency-validator'
id 'org.apache.grails.buildsrc.compile'
id 'org.apache.grails.buildsrc.publish'
id 'org.apache.grails.buildsrc.sbom'
id 'org.apache.grails.gradle.grails-code-style'
}

version = projectVersion
group = 'org.apache.grails'

ext {
pomTitle = 'Grails BOM Property Overrides Gradle Plugin'
pomDescription = 'A standalone Gradle plugin that enables Maven-style property-based version overrides for any Gradle platform() BOM. Reads the BOM POM <properties> block and lets consumers override versions via gradle.properties or ext[\'property.name\']. Reusable independently of Grails.'
pomMavenPublicationName = 'pluginMaven'
}

dependencies {
implementation platform(project(':grails-gradle-bom'))

// compile with the Groovy version provided by Gradle
// see: https://docs.gradle.org/current/userguide/compatibility.html#groovy
compileOnly 'org.apache.groovy:groovy'

// Testing - Gradle TestKit is auto-added by java-gradle-plugin
testImplementation('org.spockframework:spock-core') { transitive = false }
testImplementation 'org.apache.groovy:groovy-test-junit5'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

configurations {
testCompileClasspath.exclude group: 'org.apache.groovy', module: 'groovy'
testRuntimeClasspath.exclude group: 'org.apache.groovy', module: 'groovy'
}

gradlePlugin {
plugins {
bomPropertyOverrides {
displayName = 'Grails BOM Property Overrides Plugin'
description = 'Enables Maven-style property-based version overrides for any Gradle platform() BOM. ' +
'Apply this plugin and override versions via gradle.properties or ext[\'property.name\']. ' +
'Auto-detects declared platform() BOMs by default, or accepts an explicit list via the ' +
'bomPropertyOverrides extension.'
id = 'org.apache.grails.gradle.bom-property-overrides'
implementationClass = 'org.grails.gradle.plugin.bom.BomPropertyOverridesPlugin'
}
}
}

tasks.withType(Copy) {
configure {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
}

apply {
from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
}
Loading
Loading