Stop leaking build-only dependencies into the application Grails BOM (DRAFT - possible solution)#15652
Draft
jamesfredley wants to merge 1 commit into
Draft
Stop leaking build-only dependencies into the application Grails BOM (DRAFT - possible solution)#15652jamesfredley wants to merge 1 commit into
jamesfredley wants to merge 1 commit into
Conversation
Problem
=======
The Apache Groovy joint validation build has been catching transitive
dependency version drift between Grails BOM constraints and the resolved
runtime classpath. Most recently:
Dependency version validation failed for project 'grails-async-gpars'.
The following dependencies resolved to versions different from the BOM (:grails-bom):
com.github.javaparser:javaparser-core - resolved 3.28.1, expected 3.28.0
A transitive dependency is upgrading these versions.
Root cause: the application-facing BOMs (grails-bom, grails-base-bom,
grails-hibernate5-bom, grails-micronaut-bom) were iterating
gradleBomDependencies into their constraints, which leaked Grails-build-only
artefacts (Gradle plugins, the JavaParser library used by the build's
GroovydocEnhancer) into the published consumer BOM. When Apache Groovy
internally bumped one of these (e.g. GROOVY-11989 javaparser-core 3.28.0 -> 3.28.1
on GROOVY_5_0_X HEAD), the resolved transitive version no longer matched the
Grails BOM's constraint, breaking :validateDependencyVersions across most
modules.
The Grails-internal Gradle plugins, AST tooling, and asciidoctor-gradle
plugin are not used by user-facing Grails artefacts and have no business
appearing as managed-version constraints in the consumer BOM. They are
already managed by grails-gradle-bom, which is exactly the BOM intended
for Grails build classpath consumers.
Fix
===
Introduce a gradleBomBuildOnlyDependencyKeys set in dependencies.gradle
listing the artefacts that exist solely for the Grails build's own
classpath. The four application-facing BOM projects skip these keys when
iterating gradleBomDependencies into their constraints. grails-gradle-bom
(the build-time platform) is unchanged and continues to manage them.
Initial set (all five are unambiguous build-only artefacts):
* asciidoctor-gradle-jvm (Gradle plugin)
* asset-pipeline-gradle (Gradle plugin)
* grails-publish-plugin (Gradle plugin)
* javaparser-core (used by GroovydocEnhancerExtension; comes via
groovy-groovysh transitively for end users)
* spring-boot-gradle (Gradle plugin)
Other gradleBomDependencies entries (jansi, jline, jline2, byte-buddy,
commons-text, directory-watcher, jna, ant, ant-junit, asciidoctorj,
objenesis, spring-boot-cli, spring-boot-loader-tools, jquery) remain in
the application BOM because they are or may be used at runtime by Grails
applications.
Verification
============
./gradlew validateDependencyVersions
-> 184 actionable tasks, BUILD SUCCESSFUL
against javaparser-core.version = 3.28.0 (the value that previously failed
on 8.0.x because Groovy 5.0.6-SNAPSHOT pulled in 3.28.1 transitively).
After the fix the application BOM no longer publishes a javaparser-core
constraint at all, so transitive resolution is free to pick whichever
version Apache Groovy was built against.
Verified that the published BOMs no longer list build-only artefacts:
base/default/hibernate5/micronaut POMs ABSENT: javaparser-core,
asciidoctor-gradle-jvm,
asset-pipeline-gradle,
grails-publish,
spring-boot-gradle-plugin
base/default/hibernate5/micronaut POMs PRESENT: jansi, jline, byte-buddy,
commons-text,
directory-watcher, ant-junit
(legitimate runtime/test deps)
Verified grails-gradle-bom still manages the build-only artefacts so
Grails-internal Gradle plugin development is unaffected.
Backwards compatibility
=======================
End-user impact: any Grails application that was relying on grails-bom to
manage javaparser-core, asciidoctor-gradle-jvm, asset-pipeline-gradle,
grails-publish-plugin, or spring-boot-gradle-plugin will need to either pin
the version directly or import grails-gradle-bom. We surveyed grails-core
itself and only grails-data-graphql/plugin/build.gradle directly references
javaparser-core (line 38: pinned at 3.25.7 explicitly, doesn't use the
BOM-managed version), so this is unlikely to affect downstream consumers
in practice. Gradle plugins are normally applied via the plugins {} block
with a literal version anyway, not via BOM resolution.
Assisted-by: claude-code:claude-opus-4-7
🚨 TestLens detected 3 failed tests 🚨Here is what you can do:
Test Summary
🏷️ Commit: 20913cb Test FailuresUserControllerSpec > User list (:grails-test-examples-scaffolding:integrationTest in CI / Functional Tests (Java 21, indy=true))GroovyChangeLogSpec > outputs a warning message by calling the warn method (:grails-data-hibernate5-dbmigration:test in CI - Groovy Joint Validation Build / build_grails)GroovyChangeLogSpec > updates a database with Groovy Change (:grails-data-hibernate5-dbmigration:test in CI - Groovy Joint Validation Build / build_grails)Muted TestsSelect tests to mute in this pull request:
Reuse successful test results:
Click the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app. |
Contributor
|
As long as shell requires the build classpath and we have plugins also be able to be included on the build path, I don't think we can make this change. I think this is the right solution but the core issue is grails itself mixes these classpaths. |
Contributor
|
We should talk about the feasibility of splitting this, it 100% makes sense to do. Maybe we can discuss solutions for each issue:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stop leaking Grails-build-only artefacts (Gradle plugins, JavaParser-as-build-tool) into the application Grails BOMs. Those entries belong in
grails-gradle-bom(which manages the Grails build classpath); republishing them ingrails-bom/grails-base-bom/grails-hibernate5-bom/grails-micronaut-bomadds no value to consumers and creates a class of CI failures that we keep paying off manually.The recurring problem
The
Validate Dependency Versionsjob has been catching transitive drift between Grails BOM constraints and the resolved runtime classpath whenever Apache Groovy bumps an internal dependency. The most recent instance, on PR #15557 against8.0.x:The mechanism, every time:
dependencies.gradlepins a Grails-build-only artefact (e.g.javaparser-core: 3.28.0) insidegradleBomDependencyVersionsbecause the build itself uses it (here:build-logic'sGroovydocEnhancerExtension).grails-bom/{base,default,hibernate5,micronaut}/build.gradleiterates all ofgradleBomDependenciesintoconstraints {}, so the build-only pin gets republished as a managed-version constraint of the application BOM.javaparser-core3.28.0 → 3.28.1 onGROOVY_5_0_XHEAD - andgroovy-groovysh/groovy-ginqlink against the new version.javaparser-core3.28.1 transitively, which now contradicts the 3.28.0 constraint the Grails BOM is republishing ->:validateDependencyVersionsfails on every module.dependencies.gradleto match (PR Groovy 5.0.x support for Grails 8 + Spring Boot 4 #15557 did813b131607).These artefacts are not used by user-facing Grails artefacts and have no business in the consumer BOM. Gradle plugins like
asciidoctor-gradle-jvmorasset-pipeline-gradleare normally applied via theplugins {}block with a literal version, not via BOM resolution.javaparser-corereaches end users only as a transitive ofgroovy-groovysh(which is the version that should win, per Apache Groovy's release).Proposal
Introduce a
gradleBomBuildOnlyDependencyKeysset independencies.gradle:The four application BOM projects (
grails-bom/{base,default,hibernate5,micronaut}/build.gradle) skip these keys when iteratinggradleBomDependenciesinto theirconstraints {}.grails-gradle-bom(ingrails-gradle/bom/) is unchanged and continues to manage the build classpath, so Grails-plugin developers are unaffected.What stays in the application BOM
I deliberately left these in
gradleBomDependencies-> consumer BOM, because they are or may be used at app runtime / test runtime:ant,ant-junitbyte-buddycommons-textdirectory-watcherjansi,jline,jline2,jnajqueryobjenesisasciidoctorjspring-boot-cli,spring-boot-loader-toolsThree of those last items (
asciidoctorj,spring-boot-cli,spring-boot-loader-tools) are arguably build-only too. I left them for a separate decision so the diff stays minimal and focused.Verification
Verified locally on
origin/8.0.xHEAD (b47917c1fe) without the manualjavaparser-core3.28.0 -> 3.28.1 bump that PR #15557 had to apply:Without this fix, that exact command on the same source tree fails with the
javaparser-core - resolved 3.28.1, expected 3.28.0error.Generated BOM POMs confirm the leak is gone:
Why a draft
A few open questions worth resolving before this lands:
Scope of "build-only":
asciidoctorj,spring-boot-cli,spring-boot-loader-toolsare arguably also build-only - I left them in the application BOM conservatively, but if anyone has clear opinions about whether end-user apps consume them via the BOM today I am happy to expand the set.Backwards compatibility: any Grails 7.x / 8.0.0-M1 app that was relying on
grails-bomto managejavaparser-coreetc. will need to either pin directly or importgrails-gradle-bom. I surveyed grails-core and onlygrails-data-graphql/plugin/build.gradleline 38 directly referencesjavaparser-core(and it pins 3.25.7 explicitly, ignoring the BOM-managed version anyway), so internally this is a no-op. External consumers may want a release note.Whether to also publish
grails-gradle-bomcross-referenced from the application BOM POMs so any consumer that does want the Gradle classpath versions can opt in with one extraplatform(...).Context
Surfaced while burning down workarounds on PR #15557 (Groovy 5 / Spring Boot 4 upgrade). PR #15557 worked around the same javaparser leak by bumping
javaparser-core.versionto3.28.1(commit813b131607); this PR removes the underlying class of failure rather than playing whack-a-mole each time Groovy bumps an internal.