diff --git a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/MergeDescriptors.scala b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/MergeDescriptors.scala index b3c8c378c..1f9cfa152 100644 --- a/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/MergeDescriptors.scala +++ b/librarymanagement/src/main/scala/sbt/internal/librarymanagement/ivyint/MergeDescriptors.scala @@ -31,7 +31,7 @@ private[sbt] object MergeDescriptors { // combines the artifacts, configurations, includes, and excludes for DependencyDescriptors `a` and `b` // that otherwise have equal IDs -private final class MergedDescriptors(a: DependencyDescriptor, b: DependencyDescriptor) extends DependencyDescriptor { +private[sbt] final case class MergedDescriptors(a: DependencyDescriptor, b: DependencyDescriptor) extends DependencyDescriptor { def getDependencyId = a.getDependencyId def isForce = a.isForce def isChanging = a.isChanging @@ -80,11 +80,14 @@ private final class MergedDescriptors(a: DependencyDescriptor, b: DependencyDesc arts map { art => explicitConfigurations(base, art) } private[this] def explicitConfigurations(base: DependencyDescriptor, art: DependencyArtifactDescriptor): DependencyArtifactDescriptor = { - val aConfs = art.getConfigurations - if (aConfs == null || aConfs.isEmpty) - copyWithConfigurations(art, base.getModuleConfigurations) - else - art + val aConfs = Option(art.getConfigurations) map { _.toList } + // In case configuration list is "*", we should still specify the module configuration of the DependencyDescriptor + // otherwise the explicit specified artifacts from one dd can leak over to the other. + // See gh-1500, gh-2002 + aConfs match { + case None | Some(Nil) | Some(List("*")) => copyWithConfigurations(art, base.getModuleConfigurations) + case _ => art + } } private[this] def defaultArtifact(a: DependencyDescriptor): Array[DependencyArtifactDescriptor] = { diff --git a/librarymanagement/src/test/scala/MergeDescriptorSpec.scala b/librarymanagement/src/test/scala/MergeDescriptorSpec.scala new file mode 100644 index 000000000..65749a28d --- /dev/null +++ b/librarymanagement/src/test/scala/MergeDescriptorSpec.scala @@ -0,0 +1,34 @@ +package sbt.internal.librarymanagement + +import org.apache.ivy.core.module.descriptor.{ DependencyArtifactDescriptor } +import sbt.librarymanagement._ +import sbt.internal.librarymanagement.ivyint._ + +class MergeDescriptorSpec extends BaseIvySpecification { + "Merging duplicate dependencies" should "work" in { + cleanIvyCache() + val m = module( + ModuleID("com.example", "foo", "0.1.0").withConfigurations(Some("compile")), + Vector(guavaTest, guavaTestTests), None, UpdateOptions() + ) + m.withModule(log) { + case (ivy, md, _) => + val deps = md.getDependencies + assert(deps.size == 1) + deps.headOption.getOrElse(sys.error("Dependencies not found")) match { + case dd @ MergedDescriptors(dd0, dd1) => + val arts = dd.getAllDependencyArtifacts + val a0: DependencyArtifactDescriptor = arts.toList(0) + val a1: DependencyArtifactDescriptor = arts.toList(1) + val configs0 = a0.getConfigurations.toList + val configs1 = a1.getConfigurations.toList + configs0 shouldEqual List("compile") + configs1 shouldEqual List("test") + } + } + } + def guavaTest = ModuleID("com.google.guava", "guava-tests", "18.0").withConfigurations(Option("compile")) + def guavaTestTests = ModuleID("com.google.guava", "guava-tests", "18.0").withConfigurations(Option("test")).classifier("tests") + def defaultOptions = EvictionWarningOptions.default + +}