Potential solution to Maven POM generation issue#4199
Potential solution to Maven POM generation issue#4199janhoy wants to merge 1 commit intoapache:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates Solr’s Gradle Maven publishing defaults to post-process generated POMs and inject explicit <version> elements for dependencies where Gradle omits them (notably BOM-managed dependencies like Jackson), preventing “missing version” validation failures for Maven consumers.
Changes:
- Collect resolved dependency versions from Gradle configurations used for publishing.
- Inject missing
<version>nodes into generated POM<dependencies>entries duringpom.withXmlprocessing.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| config.resolvedConfiguration.resolvedArtifacts.each { artifact -> | ||
| def id = artifact.moduleVersion.id | ||
| def key = "${id.group}:${id.name}" | ||
| if (!resolvedVersions.containsKey(key)) { | ||
| resolvedVersions[key] = id.version |
There was a problem hiding this comment.
config.resolvedConfiguration.resolvedArtifacts forces artifact resolution/download just to discover versions and relies on Gradle’s deprecated resolvedConfiguration API. Consider using config.incoming.resolutionResult (or config.incoming.dependencies + ResolutionResult) to read selected module versions without artifact downloads and with better forward-compatibility.
| config.resolvedConfiguration.resolvedArtifacts.each { artifact -> | |
| def id = artifact.moduleVersion.id | |
| def key = "${id.group}:${id.name}" | |
| if (!resolvedVersions.containsKey(key)) { | |
| resolvedVersions[key] = id.version | |
| def resolutionResult = config.incoming.resolutionResult | |
| resolutionResult.allComponents.each { component -> | |
| def id = component.id | |
| if (id instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier) { | |
| def key = "${id.group}:${id.module}" | |
| if (!resolvedVersions.containsKey(key)) { | |
| resolvedVersions[key] = id.version | |
| } |
| root.dependencies?.dependency?.each { dep -> | ||
| def versionNodes = dep.version | ||
| if (!versionNodes || !versionNodes.text()) { | ||
| def key = "${dep.groupId.text()}:${dep.artifactId.text()}" | ||
| def resolvedVersion = resolvedVersions[key] | ||
| if (resolvedVersion) { | ||
| dep.appendNode('version', resolvedVersion) | ||
| } | ||
| } |
There was a problem hiding this comment.
If a dependency is missing <version> and resolvedVersions doesn’t contain a match, the POM will remain invalid but publishing will still succeed. It would be safer to fail the build (or at least log a warning/error) when a missing version can’t be resolved, so broken POMs aren’t published silently.
dsmiley
left a comment
There was a problem hiding this comment.
I would like to see a reference to somewhere showing this is the industry practice to the solution. Like multiple conversation threads ~recent on a platform like Gradle Forums or Stackoverflow.
I don't like all the imperative code in our Gradle build, and this adds more of that. Yeah I write my own fair share of it... but... declarative is better.
|
I wonder what smoketest / validation might verify the result? |
|
Agree this may fix a symptom, not the cause. Is this related to our discussion lately where the «platform» module is not aware of BOMs and thus we needed the workaround in gradle to help it use the BOM versions? Perhaps the proper fix is to publish a parent/platform POM for Solr to have a location for all our BOMs? Dunno. |
As a downstream Maven consumer (DSpace, upgrading to Solr 10 for Spring Boot 4 compatibility), here's what we observed: The impact is more severe than it may appear: Maven doesn't just skip the versionless Jackson dependencies. When Maven's model validator marks the POM as invalid, it drops all transitive dependencies from the affected module (~50+ for solr-core), not just the ones missing . Our workaround was to explicitly declare all solr-core transitive dependencies in our own POM, which is brittle and maintenance-heavy. For a smoketest: a minimal Maven pom.xml that depends on the published solr-core artifact, followed by mvn dependency:tree -Dverbose, would catch this immediately. If Maven emits "The POM for org.apache.solr:solr-core:jar:10.x.x is invalid, transitive dependencies (if any) will not be available", the published POM is broken. This could be a post-publish CI step. |
Isn't it strage that Maven Central accepted our maven artifacts without warnings (or did it?). This open JIRA https://issues.apache.org/jira/browse/SOLR-18069 is in the same alley. The suggestion is to add a step to smoke tester that downloads maven artifacts and tries to use them in a mini project with maven, which will resolve and catch many kids of potential issues I guess. |
Ref https://lists.apache.org/thread/w0tqdcjyo0wlbf7xjtc0ll9tjhwor8n2
I asked Claude to analyze and fix. This was the result.
Problem
Maven consumers of
solr-api,solr-solrj, andsolr-core10.x get invalid POM errors:Jackson dependencies in
gradle/libs.versions.tomlhave no pinned version — they rely on theJackson BOM for version resolution at Gradle build time. Gradle's
maven-publishplugin faithfullyomits
<version>from the generated POM for such entries, since from Gradle's perspective the BOMimport in
<dependencyManagement>should cover it.However, Maven's model validator rejects POMs where
<dependencies>entries lack<version>elements, even when a BOM import is present — particularly
solr-core, which has no Jackson BOMentry in its
<dependencyManagement>at all (its internal platform entry is stripped during POMgeneration).
Fix
Extend the existing
pom.withXmlblock ingradle/maven/defaults-maven.gradleto post-processgenerated POMs: after the internal platform removal, collect all Gradle-resolved artifact versions
from
compileClasspath/runtimeClasspath, then inject explicit<version>elements for any<dependency>entry that is missing one.This uses the actual BOM-resolved versions (e.g.
jackson-annotations:2.20,jackson-core:2.20.1,jackson-databind:2.20.1) and applies centrally to all published moduleswithout requiring version pinning in
libs.versions.tomlor changes to individualbuild.gradlefiles.