mirror of https://github.com/sbt/sbt.git
[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:
parent
81d379f12d
commit
b362a0ba91
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'")
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
> checkPom
|
||||
Loading…
Reference in New Issue