[2.x] fix: Emit <type> in POM for non-jar explicit artifacts (#8975)

PomGenerator never emitted <type> for dependencies with explicit
artifacts, so a WAR dependency would appear in the POM without
<type>war</type>. Maven then treats it as a JAR dependency, resolving
the wrong artifact.

Uses the primary (non-classifier) artifact to determine the type,
so .withSources()/.withJavadoc() classifier artifacts don't produce
spurious <type>doc</type> or <type>src</type> elements.

Fixes #1979

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BrianHotopp 2026-03-24 23:51:01 -04:00 committed by GitHub
parent 81d379f12d
commit b362a0ba91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 5 deletions

View File

@ -221,7 +221,7 @@ private[sbt] object PomGenerator:
{versionNode}
{scopeElem(scope)}
{optionalElem(optional)}
{classifierElem(dep)}
{typeAndClassifierElems(dep)}
{exclusions(dep)}
</dependency>
result
@ -265,10 +265,19 @@ private[sbt] object PomGenerator:
private def optionalElem(opt: Boolean): NodeSeq =
if opt then <optional>true</optional> else NodeSeq.Empty
private def classifierElem(dep: ModuleID): NodeSeq =
dep.explicitArtifacts.headOption.flatMap(_.classifier) match
case Some(c) => <classifier>{c}</classifier>
case None => NodeSeq.Empty
private def typeAndClassifierElems(dep: ModuleID): NodeSeq =
dep.explicitArtifacts.headOption match
case None => NodeSeq.Empty
case Some(art) =>
val classifier = art.classifier
val baseType = Option(art.`type`).filter(_ != Artifact.DefaultType)
val tpe = (classifier, baseType) match
case (Some(c), Some(t)) if Artifact.classifierType(c) == t => None
case _ => baseType
val typeNode = tpe.map(t => <type>{t}</type>).getOrElse(NodeSeq.Empty)
val classifierNode =
classifier.map(c => <classifier>{c}</classifier>).getOrElse(NodeSeq.Empty)
typeNode ++ classifierNode
private def exclusions(dep: ModuleID): NodeSeq =
if dep.exclusions.isEmpty then NodeSeq.Empty

View File

@ -0,0 +1,38 @@
lazy val checkPom = taskKey[Unit]("check pom emits <type> for non-jar explicit artifacts")
lazy val root = (project in file("."))
.settings(
scalaVersion := "2.13.16",
autoScalaLibrary := false,
libraryDependencies += "org.eclipse.jetty" % "jetty-webapp" % "11.0.15" artifacts (Artifact("jetty-webapp", "war", "war")),
libraryDependencies += "com.typesafe" % "config" % "1.4.3",
// classified artifact with non-default type: both <type> and <classifier> must appear
libraryDependencies += ("com.example" % "classified-war" % "1.0")
.artifacts(Artifact("classified-war", "war", "war").withClassifier(Some("client"))),
checkPom := {
val converter = fileConverter.value
val pomFile = makePom.value
val pom = xml.XML.loadFile(converter.toPath(pomFile).toFile)
val deps = pom \ "dependencies" \ "dependency"
// WAR dependency should be present with <type>war</type>
val warDep = deps.find(d => (d \ "artifactId").text == "jetty-webapp")
assert(warDep.isDefined, s"jetty-webapp dependency missing from POM.\nDeps: ${deps.map(d => (d \ "artifactId").text)}")
val warType = (warDep.get \ "type").text
assert(warType == "war", s"Expected <type>war</type> for jetty-webapp, got: '$warType'")
// JAR dependency should NOT have <type> (jar is the Maven default)
val jarDep = deps.find(d => (d \ "artifactId").text == "config")
assert(jarDep.isDefined, "config dependency missing from POM")
val jarType = (jarDep.get \ "type").text
assert(jarType == "", s"Expected no <type> for config (jar is default), got: '$jarType'")
// Classified WAR: must have both <type>war</type> and <classifier>client</classifier>
val cwDep = deps.find(d => (d \ "artifactId").text == "classified-war")
assert(cwDep.isDefined, s"classified-war dependency missing from POM.\nDeps: ${deps.map(d => (d \ "artifactId").text)}")
val cwType = (cwDep.get \ "type").text
assert(cwType == "war", s"Expected <type>war</type> for classified-war, got: '$cwType'")
val cwClassifier = (cwDep.get \ "classifier").text
assert(cwClassifier == "client", s"Expected <classifier>client</classifier> for classified-war, got: '$cwClassifier'")
},
)

View File

@ -0,0 +1 @@
> checkPom