Extract shared BOM build conventions#15827
Conversation
Centralize duplicated BOM publication, constraint extraction, and POM customization logic in a build-logic convention plugin. Apply it across the BOM modules while keeping generated POM and constraints output byte-identical to the current 8.0.x baseline. Assisted-by: Hephaestus:openai/gpt-5.5 codex-review
There was a problem hiding this comment.
Pull request overview
This pull request extracts repeated Grails BOM build logic into a shared build-logic Gradle plugin, then applies it across the various BOM subprojects to reduce duplication and centralize publication / POM / constraints behavior.
Changes:
- Add
org.apache.grails.buildsrc.bom-conventionsplugin to encapsulate common BOM conventions (publication hooks, constraints extraction wiring, snapshot validation, POM customization). - Update Grails BOM subprojects (default/base/hibernate*/micronaut*) to use the shared plugin and remove duplicated build script logic.
- Refactor
dependencies.gradleto generate Groovy module dependency maps via a shared helper and remove now-unreachable override branches.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| grails-bom/micronaut/build.gradle | Switch Micronaut BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| grails-bom/hibernate7/build.gradle | Switch Hibernate 7 BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| grails-bom/hibernate7-micronaut/build.gradle | Switch Hibernate 7 + Micronaut BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| grails-bom/hibernate5/build.gradle | Switch Hibernate 5 BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| grails-bom/hibernate5-micronaut/build.gradle | Switch Hibernate 5 + Micronaut BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| grails-bom/default/build.gradle | Switch default BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| grails-bom/base/build.gradle | Switch base BOM to shared bom-conventions plugin; remove duplicated publication/constraints logic. |
| dependencies.gradle | Extract Groovy module dependency map generation into a helper and simplify override branching. |
| build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/BomConventionsPlugin.groovy | New shared BOM conventions plugin implementing extracted common logic. |
| build-logic/plugins/build.gradle | Register the new plugin and add required module dependency for BOM task classes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #15827 +/- ##
==================================================
+ Coverage 49.4833% 49.5526% +0.0692%
- Complexity 16883 16927 +44
==================================================
Files 1998 1999 +1
Lines 93672 93753 +81
Branches 16400 16419 +19
==================================================
+ Hits 46352 46457 +105
+ Misses 40169 40133 -36
- Partials 7151 7163 +12 🚀 New features to boost your workflow:
|
Use the configured Gradle-build project group mappings when registering POM property replacements, and add focused TestKit coverage for the shared BOM conventions plugin. Assisted-by: Hephaestus:openai/gpt-5.5 codex-review
jdaugherty
left a comment
There was a problem hiding this comment.
I've made several comments inline, but my major issue is we should never use .matching {} - this gradle code is non-lazy.
| task.dependsOn('generateMetadataFileForMavenPublication', 'generatePomFileForMavenPublication') | ||
| } | ||
|
|
||
| project.tasks.named('check') { Task task -> |
There was a problem hiding this comment.
This adds new lifecycle behavior that was not present in any of the extracted BOM scripts: check now runs extractConstraints, which also runs both publication generators and resolves the full BOM graph. grails-doc:generateBomDocumentation already invokes extractConstraints explicitly. It also makes a previously configuration-cache-compatible BOM check fail: ./gradlew :grails-base-bom:check --configuration-cache --rerun-tasks reports unsupported serialization of ConfigurationContainer, DependencyHandler, and Project. Could we remove this check dependency and keep constraint extraction explicit?
| } | ||
|
|
||
| private static void configureCoordinates(Project project) { | ||
| project.version = project.findProperty('projectVersion') |
There was a problem hiding this comment.
The extracted scripts used version = projectVersion, which fails with MissingPropertyException when projectVersion is not defined. findProperty returns null here, leaving project.version as unspecified; projectCoordinateProperties() and the POM customization then register coordinates like org.apache.grails:grails-gradle-plugins:unspecified, and version property replacement silently stops matching with no error. Suggest failing fast, e.g. project.property('projectVersion') or an explicit check.
| if (project.extensions.extraProperties.get('isReleaseBuild') && | ||
| project.extensions.extraProperties.get('isPublishedExternal')) { | ||
| project.tasks.matching { Task task -> | ||
| task.name in ['generateMetadataFileForMavenPublication', 'generatePomFileForMavenPublication'] |
There was a problem hiding this comment.
Behavior change from the extracted scripts: they used afterEvaluate { tasks.named(...).configure { ... } }, which throws UnknownTaskException if the publication tasks are missing. tasks.matching { }.configureEach { } silently no-ops if these tasks are never created (e.g. the publish plugin renames its publication), so on a real release the snapshot validation could silently not run. Since this guard only matters when isReleaseBuild && isPublishedExternal, consider asserting the tasks exist in that case rather than matching by name.
| dep.version[0].value = extractedConstraint.versionPropertyReference | ||
| pomProperties.put(extractedConstraint.versionPropertyName, inlineVersion) | ||
| } | ||
| } else if (!inlineVersion) { |
There was a problem hiding this comment.
else if (!inlineVersion) is always true at this point — carried over from the original scripts, but since this code was rewritten anyway, a plain else would be clearer.
| import org.apache.grails.gradle.tasks.bom.PropertyNameCalculator | ||
|
|
||
| @CompileStatic | ||
| class BomConventionsPlugin implements Plugin<Project> { |
There was a problem hiding this comment.
I would rename this to GrailsBomPlugin
| project.extensions.configure(JavaPlatformExtension) { JavaPlatformExtension extension -> | ||
| extension.allowDependencies() | ||
| } | ||
| project.pluginManager.apply('org.apache.grails.buildsrc.publish') |
There was a problem hiding this comment.
We have not used aggregate plugins to date. These should be left on the individual projects (publish & sbom)
| } | ||
|
|
||
| private static void applyDependencyDefinitions(Project project) { | ||
| project.apply([from: project.rootProject.layout.projectDirectory.file('dependencies.gradle')]) |
There was a problem hiding this comment.
The difference between this and before is it was applied in the buildscript so it was available in all contexts. I think this is ok, but it's important to note the difference.
| // These files are split to facilitate separation of build vs application dependencies. These are the application dependencies. | ||
| static Map<String, String> groovyModuleDependencies(String groovyVersion) { | ||
| [ | ||
| '', '-ant', '-astbuilder', '-cli-commons', '-cli-picocli', '-console', '-contracts', '-datetime', |
There was a problem hiding this comment.
This is harder to read and should be reverted. the standard of this file is to list the dependencies. these extractions make it harder to maintain
Address review feedback against .matching {} and keep BOM publication
conventions on configureEach/named-style configuration.
Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]
✅ All tests passed ✅🏷️ Commit: 7749b58 Learn more about TestLens at testlens.app. |
Summary
This PR reduces duplicated Gradle BOM build logic by moving the shared publication, POM customization, constraint extraction, and release snapshot validation conventions into
org.apache.grails.buildsrc.bom-conventions.The BOM build scripts now keep only BOM-specific inputs such as descriptions, dependency declarations, and variant-specific dependency sets. The shared plugin owns the common task wiring and POM mutation behavior used by the seven Grails BOM projects.
Changes
BomConventionsPlugininbuild-logic/pluginsand register it asorg.apache.grails.buildsrc.bom-conventions.dependencies.gradlewhile leaving one literal coordinate list for Dependabot visibility.gradleBuildProjectsgroup mapping when registering Gradle-build artifacts for POM property replacement.Verification
JAVA_HOME=/tmp/opencode/jdk21 ./gradlew :build-logic-root:build-logic:compileGroovy :build-logic-root:build-logic:testJAVA_HOME=/tmp/opencode/jdk21 ./gradlew :build-logic-root:build-logic:test --tests org.apache.grails.buildsrc.BomConventionsPluginSpec-PincludeMicronautProjects.extractConstraintsfor all seven BOMs with-PincludeMicronautProjects.origin/8.0.xbaseline: no differences.8.0.0-SNAPSHOTversions.Notes
.groovy/.gradlebecause no Groovy/Gradle LSP is configured.origin/8.0.x(GraphQL static compile errors and MongoDB timeout flakes), reproduced in a clean baseline worktree.