From 74c07c64a78447274be47804a6f5f5019c571dab Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 20 Aug 2012 15:55:50 -0400 Subject: [PATCH] Merge multiple dependency definitions for the same ID. Fixes #468, #285, #419, #480. This is only a workaround. Multiple dependency definitions should be avoided in general. --- ivy/CustomPomParser.scala | 11 +- ivy/Ivy.scala | 97 +++++++++---- ivy/IvyActions.scala | 2 +- ivy/IvyInterface.scala | 1 + ivy/MakePom.scala | 58 ++++++-- ivy/MergeDescriptors.scala | 132 ++++++++++++++++++ .../make-pom-type/build.sbt | 25 ++++ .../dependency-management/make-pom-type/test | 1 + .../multiple-classifiers/build.sbt | 29 ++++ .../multiple-classifiers/test | 3 + .../pom-scope/project/PomTest.scala | 16 +-- .../dependency-management/t468/build.sbt | 22 +++ .../sbt-test/dependency-management/t468/test | 2 + 13 files changed, 347 insertions(+), 52 deletions(-) create mode 100644 ivy/MergeDescriptors.scala create mode 100644 sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/make-pom-type/test create mode 100644 sbt/src/sbt-test/dependency-management/multiple-classifiers/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/multiple-classifiers/test create mode 100644 sbt/src/sbt-test/dependency-management/t468/build.sbt create mode 100644 sbt/src/sbt-test/dependency-management/t468/test diff --git a/ivy/CustomPomParser.scala b/ivy/CustomPomParser.scala index aced1d48a..668aa5cff 100644 --- a/ivy/CustomPomParser.scala +++ b/ivy/CustomPomParser.scala @@ -64,8 +64,11 @@ object CustomPomParser // Fixes up the detected extension in some cases missed by Ivy. val convertArtifacts = artifactExtIncorrect(md) + // Merges artifact sections for duplicate dependency definitions + val mergeDuplicates = IvySbt.hasDuplicateDependencies(md.getDependencies) + val unqualify = (filtered - ExtraAttributesKey) map { case (k,v) => ("e:" + k, v) } - if(unqualify.isEmpty && extraDepAttributes.isEmpty && !convertArtifacts) + if(unqualify.isEmpty && extraDepAttributes.isEmpty && !convertArtifacts && !mergeDuplicates) md else addExtra(unqualify, extraDepAttributes, parser, md) @@ -74,7 +77,6 @@ object CustomPomParser md.getConfigurations.exists(conf => md.getArtifacts(conf.getName).exists(art => JarPackagings(art.getExt))) private[this] def shouldBeUnqualified(m: Map[String, String]): Map[String, String] = m.filter { case (SbtVersionKey | ScalaVersionKey | ExtraAttributesKey,_) => true; case _ => false } - private[this] def condAddExtra(properties: Map[String, String], id: ModuleRevisionId): ModuleRevisionId = if(properties.isEmpty) id else addExtra(properties, id) @@ -169,7 +171,10 @@ object CustomPomParser for( (key,value) <- md.getExtraInfo.asInstanceOf[java.util.Map[String,String]].asScala ) dmd.addExtraInfo(key, value) for( (key, value) <- md.getExtraAttributesNamespaces.asInstanceOf[java.util.Map[String,String]].asScala ) dmd.addExtraAttributeNamespace(key, value) IvySbt.addExtraNamespace(dmd) - for( dd <- md.getDependencies ) dmd.addDependency(addExtra(dd, dependencyExtra)) + + val withExtra = md.getDependencies map { dd => addExtra(dd, dependencyExtra) } + val unique = IvySbt.mergeDuplicateDefinitions(withExtra) + unique foreach dmd.addDependency for( ed <- md.getInheritedDescriptors) dmd.addInheritedDescriptor( new DefaultExtendsDescriptor( mrid, resolvedMrid, ed.getLocation, ed.getExtendsTypes) ) for( conf <- md.getConfigurations) { diff --git a/ivy/Ivy.scala b/ivy/Ivy.scala index 2866c8484..93ab5e94b 100644 --- a/ivy/Ivy.scala +++ b/ivy/Ivy.scala @@ -146,7 +146,8 @@ final class IvySbt(val configuration: IvyConfiguration) val parser = IvySbt.parseIvyXML(ivy.getSettings, IvySbt.wrapped(module, ivyXML), moduleID, defaultConf.name, validate) IvySbt.addMainArtifact(moduleID) IvySbt.addOverrides(moduleID, overrides, ivy.getSettings.getMatcher(PatternMatcher.EXACT)) - IvySbt.addDependencies(moduleID, IvySbt.overrideDirect(dependencies, overrides), parser) + val transformedDeps = IvySbt.overrideDirect(dependencies, overrides) + IvySbt.addDependencies(moduleID, transformedDeps, parser) (moduleID, parser.getDefaultConf) } private def newConfiguredModuleID(module: ModuleID, moduleInfo: ModuleInfo, configurations: Iterable[Configuration]) = @@ -423,36 +424,73 @@ private object IvySbt } /** This method is used to add inline dependencies to the provided module. */ - def addDependencies(moduleID: DefaultModuleDescriptor, dependencies: Iterable[ModuleID], parser: CustomXmlParser.CustomParser) + def addDependencies(moduleID: DefaultModuleDescriptor, dependencies: Seq[ModuleID], parser: CustomXmlParser.CustomParser) { - for(dependency <- dependencies) - { - val dependencyDescriptor = new DefaultDependencyDescriptor(moduleID, toID(dependency), dependency.isForce, dependency.isChanging, dependency.isTransitive) - dependency.configurations match - { - case None => // The configuration for this dependency was not explicitly specified, so use the default - parser.parseDepsConfs(parser.getDefaultConf, dependencyDescriptor) - case Some(confs) => // The configuration mapping (looks like: test->default) was specified for this dependency - parser.parseDepsConfs(confs, dependencyDescriptor) - } - for(artifact <- dependency.explicitArtifacts) - { - import artifact.{name, classifier, `type`, extension, url} - val extraMap = extra(artifact) - val ivyArtifact = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, name, `type`, extension, url.getOrElse(null), extraMap) - for(conf <- dependencyDescriptor.getModuleConfigurations) - dependencyDescriptor.addDependencyArtifact(conf, ivyArtifact) - } - for(excls <- dependency.exclusions) - { - for(conf <- dependencyDescriptor.getModuleConfigurations) - { - dependencyDescriptor.addExcludeRule(conf, IvyScala.excludeRule(excls.organization, excls.name, excls.configurations, excls.artifact)) - } - } - moduleID.addDependency(dependencyDescriptor) - } + val converted = dependencies map { dependency => convertDependency(moduleID, dependency, parser) } + val unique = if(hasDuplicateDependencies(converted)) mergeDuplicateDefinitions(converted) else converted + unique foreach moduleID.addDependency } + /** Determines if there are multiple dependency definitions for the same dependency ID. */ + def hasDuplicateDependencies(dependencies: Seq[DependencyDescriptor]): Boolean = + { + val ids = dependencies.map(_.getDependencyRevisionId) + ids.toSet.size != ids.size + } + + /** Combines the artifacts, includes, and excludes of duplicate dependency definitions. + * This is somewhat fragile and is only intended to workaround Ivy (or sbt's use of Ivy) not handling this case properly. + * In particular, Ivy will create multiple dependency entries when converting a pom with a dependency on a classified artifact and a non-classified artifact: + * https://github.com/harrah/xsbt/issues/468 + * It will also allow users to declare dependencies on classified modules in different configurations: + * https://groups.google.com/d/topic/simple-build-tool/H2MdAARz6e0/discussion + * as well as basic multi-classifier handling: #285, #419, #480. + * Multiple dependency definitions should otherwise be avoided as much as possible. + */ + def mergeDuplicateDefinitions(dependencies: Seq[DependencyDescriptor]): Seq[DependencyDescriptor] = + { + val deps = new java.util.LinkedHashMap[ModuleRevisionId, DependencyDescriptor] + for( dd <- dependencies ) + { + val id = dd.getDependencyRevisionId + val updated = deps get id match { + case null => dd + case v => ivyint.MergeDescriptors(v, dd) + } + deps.put(id, updated) + } + import collection.JavaConverters._ + deps.values.asScala.toSeq + } + + /** Transforms an sbt ModuleID into an Ivy DefaultDependencyDescriptor.*/ + def convertDependency(moduleID: DefaultModuleDescriptor, dependency: ModuleID, parser: CustomXmlParser.CustomParser): DefaultDependencyDescriptor = + { + val dependencyDescriptor = new DefaultDependencyDescriptor(moduleID, toID(dependency), dependency.isForce, dependency.isChanging, dependency.isTransitive) + dependency.configurations match + { + case None => // The configuration for this dependency was not explicitly specified, so use the default + parser.parseDepsConfs(parser.getDefaultConf, dependencyDescriptor) + case Some(confs) => // The configuration mapping (looks like: test->default) was specified for this dependency + parser.parseDepsConfs(confs, dependencyDescriptor) + } + for(artifact <- dependency.explicitArtifacts) + { + import artifact.{name, classifier, `type`, extension, url} + val extraMap = extra(artifact) + val ivyArtifact = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, name, `type`, extension, url.getOrElse(null), extraMap) + for(conf <- dependencyDescriptor.getModuleConfigurations) + dependencyDescriptor.addDependencyArtifact(conf, ivyArtifact) + } + for(excls <- dependency.exclusions) + { + for(conf <- dependencyDescriptor.getModuleConfigurations) + { + dependencyDescriptor.addExcludeRule(conf, IvyScala.excludeRule(excls.organization, excls.name, excls.configurations, excls.artifact)) + } + } + dependencyDescriptor + } + def addOverrides(moduleID: DefaultModuleDescriptor, overrides: Set[ModuleID], matcher: PatternMatcher): Unit = overrides foreach addOverride(moduleID, matcher) def addOverride(moduleID: DefaultModuleDescriptor, matcher: PatternMatcher)(overrideDef: ModuleID): Unit = @@ -500,6 +538,7 @@ private object IvySbt } } + /** This code converts the given ModuleDescriptor to a DefaultModuleDescriptor by casting or generating an error. * Ivy 2.0.0 always produces a DefaultModuleDescriptor. */ private def toDefaultModuleDescriptor(md: ModuleDescriptor) = diff --git a/ivy/IvyActions.scala b/ivy/IvyActions.scala index 902b84d95..a2d040a97 100644 --- a/ivy/IvyActions.scala +++ b/ivy/IvyActions.scala @@ -20,7 +20,7 @@ final class PublishConfiguration(val ivyFile: Option[File], val resolverName: St final class UpdateConfiguration(val retrieve: Option[RetrieveConfiguration], val missingOk: Boolean, val logging: UpdateLogging.Value) final class RetrieveConfiguration(val retrieveDirectory: File, val outputPattern: String) -final case class MakePomConfiguration(file: File, moduleInfo: ModuleInfo, configurations: Option[Seq[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean, includeTypes: Set[String] = Set(Artifact.DefaultType)) +final case class MakePomConfiguration(file: File, moduleInfo: ModuleInfo, configurations: Option[Seq[Configuration]] = None, extra: NodeSeq = NodeSeq.Empty, process: XNode => XNode = n => n, filterRepositories: MavenRepository => Boolean = _ => true, allRepositories: Boolean, includeTypes: Set[String] = Set(Artifact.DefaultType, Artifact.PomType)) // exclude is a map on a restricted ModuleID final case class GetClassifiersConfiguration(module: GetClassifiersModule, exclude: Map[ModuleID, Set[String]], configuration: UpdateConfiguration, ivyScala: Option[IvyScala]) final case class GetClassifiersModule(id: ModuleID, modules: Seq[ModuleID], configurations: Seq[Configuration], classifiers: Seq[String]) diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index 9a1708059..0f37aa241 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -37,6 +37,7 @@ final case class ModuleID(organization: String, name: String, revision: String, def extra(attributes: (String,String)*) = copy(extraAttributes = this.extraAttributes ++ ModuleID.checkE(attributes)) def sources() = artifacts(Artifact.sources(name)) def javadoc() = artifacts(Artifact.javadoc(name)) + def pomOnly() = artifacts(Artifact.pom(name)) def withSources() = jarIfEmpty.sources() def withJavadoc() = jarIfEmpty.javadoc() private def jarIfEmpty = if(explicitArtifacts.isEmpty) jar() else this diff --git a/ivy/MakePom.scala b/ivy/MakePom.scala index 31a83a705..5dc39f069 100644 --- a/ivy/MakePom.scala +++ b/ivy/MakePom.scala @@ -10,13 +10,12 @@ package sbt import java.io.File // Node needs to be renamed to XNode because the task subproject contains a Node type that will shadow // scala.xml.Node when generating aggregated API documentation -import scala.xml.{Node => XNode, NodeSeq, PrettyPrinter} +import scala.xml.{Elem, Node => XNode, NodeSeq, PrettyPrinter} import Configurations.Optional import org.apache.ivy.{core, plugins, Ivy} import core.settings.IvySettings -import core.module.descriptor -import descriptor.{DependencyDescriptor, License, ModuleDescriptor, ExcludeRule} +import core.module.descriptor.{DependencyArtifactDescriptor, DependencyDescriptor, License, ModuleDescriptor, ExcludeRule} import plugins.resolver.{ChainResolver, DependencyResolver, IBiblioResolver} class MakePom(val log: Logger) @@ -146,38 +145,75 @@ class MakePom(val log: Logger) def makeDependency(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = + { + val artifacts = dependency.getAllDependencyArtifacts + val includeArtifacts = artifacts.filter(d => includeTypes(d.getType)) + if(artifacts.isEmpty) { + val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations) + makeDependencyElem(dependency, scope, optional, None, None) + } + else if(includeArtifacts.isEmpty) + NodeSeq.Empty + else + NodeSeq.fromSeq(artifacts.map( a => makeDependencyElem(dependency, a) )) + } + + def makeDependencyElem(dependency: DependencyDescriptor, artifact: DependencyArtifactDescriptor): Elem = + { + val artifactConfigs = artifact.getConfigurations + val configs = if(artifactConfigs.isEmpty) dependency.getModuleConfigurations else artifactConfigs + val (scope, optional) = getScopeAndOptional(configs) + makeDependencyElem(dependency, scope, optional, artifactClassifier(artifact), artifactType(artifact)) + } + def makeDependencyElem(dependency: DependencyDescriptor, scope: Option[String], optional: Boolean, classifier: Option[String], tpe: Option[String]): Elem = { val mrid = dependency.getDependencyRevisionId {mrid.getOrganisation} {mrid.getName} {mrid.getRevision} - { scopeAndOptional(dependency) } - { classifier(dependency, includeTypes) } + { scopeElem(scope) } + { optionalElem(optional) } + { classifierElem(classifier) } + { typeElem(tpe) } { exclusions(dependency) } } + @deprecated("No longer used and will be removed.", "0.12.1") def classifier(dependency: DependencyDescriptor, includeTypes: Set[String]): NodeSeq = { val jarDep = dependency.getAllDependencyArtifacts.filter(d => includeTypes(d.getType)).headOption jarDep match { - case Some(a) => { - val cl = a.getExtraAttribute("classifier") - if (cl != null) {cl} else NodeSeq.Empty - } - case _ => NodeSeq.Empty + case Some(a) => classifierElem(artifactClassifier(a)) + case None => NodeSeq.Empty } } + def artifactType(artifact: DependencyArtifactDescriptor): Option[String] = + Option(artifact.getType).flatMap { tpe => if(tpe == "jar") None else Some(tpe) } + def typeElem(tpe: Option[String]): NodeSeq = + tpe match { + case Some(t) => {t} + case None => NodeSeq.Empty + } + + def artifactClassifier(artifact: DependencyArtifactDescriptor): Option[String] = + Option(artifact.getExtraAttribute("classifier")) + def classifierElem(classifier: Option[String]): NodeSeq = + classifier match { + case Some(c) => {c} + case None => NodeSeq.Empty + } + @deprecated("No longer used and will be removed.", "0.12.1") def scopeAndOptional(dependency: DependencyDescriptor): NodeSeq = { val (scope, opt) = getScopeAndOptional(dependency.getModuleConfigurations) scopeElem(scope) ++ optionalElem(opt) } def scopeElem(scope: Option[String]): NodeSeq = scope match { - case Some(s) => {s} case None => NodeSeq.Empty + case Some(s) => {s} } def optionalElem(opt: Boolean) = if(opt) true else NodeSeq.Empty def moduleDescriptor(module: ModuleDescriptor) = module.getModuleRevisionId diff --git a/ivy/MergeDescriptors.scala b/ivy/MergeDescriptors.scala new file mode 100644 index 000000000..f46f90501 --- /dev/null +++ b/ivy/MergeDescriptors.scala @@ -0,0 +1,132 @@ +package sbt +package ivyint + +import java.io.File +import java.net.URI +import java.util.{Collection, Collections => CS} +import CS.singleton + +import org.apache.ivy.{core, plugins, util, Ivy} +import core.module.descriptor.{DependencyArtifactDescriptor, DefaultDependencyArtifactDescriptor} +import core.module.descriptor.{DefaultDependencyDescriptor => DDD, DependencyDescriptor} +import core.module.id.{ArtifactId,ModuleId, ModuleRevisionId} +import plugins.namespace.Namespace +import util.extendable.ExtendableItem + +private[sbt] object MergeDescriptors +{ + def apply(a: DependencyDescriptor, b: DependencyDescriptor): DependencyDescriptor = + { + assert(a.isForce == b.isForce) + assert(a.isChanging == b.isChanging) + assert(a.isTransitive == b.isTransitive) + assert(a.getParentRevisionId == b.getParentRevisionId) + val amrid = a.getDependencyRevisionId + val bmrid = b.getDependencyRevisionId + assert(amrid == bmrid) + val adyn = a.getDynamicConstraintDependencyRevisionId + val bdyn = b.getDynamicConstraintDependencyRevisionId + assert(adyn == bdyn) + assert(a.getNamespace == b.getNamespace) + + new MergedDescriptors(a,b) + } +} + +// 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 +{ + def getDependencyId = a.getDependencyId + def isForce = a.isForce + def isChanging = a.isChanging + def isTransitive = a.isTransitive + def getNamespace = a.getNamespace + def getParentRevisionId = a.getParentRevisionId + def getDependencyRevisionId = a.getDependencyRevisionId + def getDynamicConstraintDependencyRevisionId = a.getDynamicConstraintDependencyRevisionId + + def getModuleConfigurations = concat(a.getModuleConfigurations, b.getModuleConfigurations) + + def getDependencyConfigurations(moduleConfiguration: String, requestedConfiguration: String) = + concat(a.getDependencyConfigurations(moduleConfiguration, requestedConfiguration), b.getDependencyConfigurations(moduleConfiguration)) + + def getDependencyConfigurations(moduleConfiguration: String) = + concat(a.getDependencyConfigurations(moduleConfiguration), b.getDependencyConfigurations(moduleConfiguration)) + + def getDependencyConfigurations(moduleConfigurations: Array[String]) = + concat(a.getDependencyConfigurations(moduleConfigurations), b.getDependencyConfigurations(moduleConfigurations)) + + def getAllDependencyArtifacts = concatArtifacts(a, a.getAllDependencyArtifacts, b, b.getAllDependencyArtifacts) + + def getDependencyArtifacts(moduleConfigurations: String) = + concatArtifacts(a, a.getDependencyArtifacts(moduleConfigurations), b, b.getDependencyArtifacts(moduleConfigurations)) + + def getDependencyArtifacts(moduleConfigurations: Array[String]) = + concatArtifacts(a, a.getDependencyArtifacts(moduleConfigurations), b, b.getDependencyArtifacts(moduleConfigurations)) + + def getAllIncludeRules = concat(a.getAllIncludeRules, b.getAllIncludeRules) + + def getIncludeRules(moduleConfigurations: String) = + concat(a.getIncludeRules(moduleConfigurations), b.getIncludeRules(moduleConfigurations)) + + def getIncludeRules(moduleConfigurations: Array[String]) = + concat(a.getIncludeRules(moduleConfigurations), b.getIncludeRules(moduleConfigurations)) + + private[this] def concatArtifacts(a: DependencyDescriptor, as: Array[DependencyArtifactDescriptor], b: DependencyDescriptor, bs: Array[DependencyArtifactDescriptor]) = + { + if(as.isEmpty) + if(bs.isEmpty) as + else defaultArtifact(a) +: explicitConfigurations(b, bs) + else if(bs.isEmpty) explicitConfigurations(a, as) :+ defaultArtifact(b) + else concat(explicitConfigurations(a, as), explicitConfigurations(b, bs)) + } + private[this] def explicitConfigurations(base: DependencyDescriptor, arts: Array[DependencyArtifactDescriptor]): Array[DependencyArtifactDescriptor] = + 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 + } + private[this] def defaultArtifact(a: DependencyDescriptor): DependencyArtifactDescriptor = + { + val dd = new DefaultDependencyArtifactDescriptor(a, a.getDependencyRevisionId.getName, "jar", "jar", null, null) + addConfigurations(dd, a.getModuleConfigurations) + dd + } + private[this] def copyWithConfigurations(dd: DependencyArtifactDescriptor, confs: Seq[String]): DependencyArtifactDescriptor = + { + val dextra = dd.getQualifiedExtraAttributes + val newd = new DefaultDependencyArtifactDescriptor(dd.getDependencyDescriptor, dd.getName, dd.getType, dd.getExt, dd.getUrl, dextra) + addConfigurations(newd, confs) + newd + } + private[this] def addConfigurations(dd: DefaultDependencyArtifactDescriptor, confs: Seq[String]): Unit = + confs foreach dd.addConfiguration + + private[this] def concat[T: ClassManifest](a: Array[T], b: Array[T]): Array[T] = (a ++ b).distinct.toArray + + def getAllExcludeRules = concat(a.getAllExcludeRules, b.getAllExcludeRules) + + def getExcludeRules(moduleConfigurations: String) = concat(a.getExcludeRules(moduleConfigurations), b.getExcludeRules(moduleConfigurations)) + + def getExcludeRules(moduleConfigurations: Array[String]) = concat(a.getExcludeRules(moduleConfigurations), b.getExcludeRules(moduleConfigurations)) + + def doesExclude(moduleConfigurations: Array[String], artifactId: ArtifactId) = a.doesExclude(moduleConfigurations, artifactId) || b.doesExclude(moduleConfigurations, artifactId) + + def canExclude = a.canExclude || b.canExclude + + def asSystem = this + + def clone(revision: ModuleRevisionId) = new MergedDescriptors(a.clone(revision), b.clone(revision)) + + def getAttribute(name: String): String = a.getAttribute(name) + def getAttributes = a.getAttributes + def getExtraAttribute(name: String) = a.getExtraAttribute(name) + def getExtraAttributes = a.getExtraAttributes + def getQualifiedExtraAttributes = a.getQualifiedExtraAttributes + def getSourceModule = a.getSourceModule +} diff --git a/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt b/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt new file mode 100644 index 000000000..67412eeff --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/make-pom-type/build.sbt @@ -0,0 +1,25 @@ +scalaVersion := "2.9.2" + +libraryDependencies += "org.mongodb" %% "casbah" % "2.4.1" pomOnly() + +autoScalaLibrary := false + + +TaskKey[Unit]("check-pom") <<= makePom map { file => + val pom = xml.XML.loadFile(file) + val actual = pom \\ "dependencies" + val expectedDep = + + org.mongodb + casbah_2.9.2 + 2.4.1 + pom + + val expected = + {expectedDep} + + val pp = new xml.PrettyPrinter(Int.MaxValue, 0) + val expectedString = pp.format(expected) + val actualString = pp.formatNodes(actual) + assert(expectedString == actualString, "Expected dependencies section:\n" + expectedString + "\n\nActual:\n" + actualString) +} diff --git a/sbt/src/sbt-test/dependency-management/make-pom-type/test b/sbt/src/sbt-test/dependency-management/make-pom-type/test new file mode 100644 index 000000000..c8edbfbcd --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/make-pom-type/test @@ -0,0 +1 @@ +> check-pom \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/multiple-classifiers/build.sbt b/sbt/src/sbt-test/dependency-management/multiple-classifiers/build.sbt new file mode 100644 index 000000000..2e5c53184 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/multiple-classifiers/build.sbt @@ -0,0 +1,29 @@ +libraryDependencies ++= Seq("natives-windows", "natives-linux", "natives-osx") map ( c => + "org.lwjgl.lwjgl" % "lwjgl-platform" % "2.8.2" classifier c +) + +autoScalaLibrary := false + +TaskKey[Unit]("check") <<= dependencyClasspath in Compile map { cp => + assert(cp.size == 3, "Expected 3 jars, got: " + cp.files.mkString("(", ", ", ")")) +} + +TaskKey[Unit]("check-pom") <<= makePom map { file => + val pom = xml.XML.loadFile(file) + val actual = pom \\ "dependencies" + def depSection(classifier: String) = + + org.lwjgl.lwjgl + lwjgl-platform + 2.8.2 + {classifier} + + val sections = depSection("natives-windows") ++ depSection("natives-linux") ++ depSection("natives-osx") + val expected = + {sections} + + val pp = new xml.PrettyPrinter(Int.MaxValue, 0) + val expectedString = pp.format(expected) + val actualString = pp.formatNodes(actual) + assert(expectedString == actualString, "Expected dependencies section:\n" + expectedString + "\n\nActual:\n" + actualString) +} diff --git a/sbt/src/sbt-test/dependency-management/multiple-classifiers/test b/sbt/src/sbt-test/dependency-management/multiple-classifiers/test new file mode 100644 index 000000000..b4d923e87 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/multiple-classifiers/test @@ -0,0 +1,3 @@ +> check + +> check-pom diff --git a/sbt/src/sbt-test/dependency-management/pom-scope/project/PomTest.scala b/sbt/src/sbt-test/dependency-management/pom-scope/project/PomTest.scala index 1f1e12bf0..7c937dd03 100644 --- a/sbt/src/sbt-test/dependency-management/pom-scope/project/PomTest.scala +++ b/sbt/src/sbt-test/dependency-management/pom-scope/project/PomTest.scala @@ -13,7 +13,8 @@ object PomTest extends Build "d" % "d" % "1.0" % "test", "e" % "e" % "1.0" % "custom", "f" % "f" % "1.0" % "custom,optional,runtime", - "g" % "g" % "1.0" % "custom,runtime" classifier "foo" + "g" % "g" % "1.0" % "custom,runtime" classifier "foo", + "h" % "h" % "1.0" % "custom,optional,runtime" classifier "foo" ) ) @@ -27,21 +28,20 @@ object PomTest extends Build ("d", Some("test"), false, None), ("e", Some("custom"), false, None), ("f", Some("runtime"), true, None), - ("g", Some("runtime"), false, Some("foo")) + ("g", Some("runtime"), false, Some("foo")), + ("h", Some("runtime"), true, Some("foo")) ) val loaded = xml.XML.loadFile(pom) val deps = loaded \\ "dependency" expected foreach { case (id, scope, opt, classifier) => val dep = deps.find(d => (d \ "artifactId").text == id).getOrElse( error("Dependency '" + id + "' not written to pom:\n" + loaded)) - val actualOpt = java.lang.Boolean.parseBoolean( (dep \\ "optional").text ) - println("Actual: " + actualOpt + ", opt: " + opt) - assert(opt == actualOpt, "Invalid 'optional' section '" + (dep \\ "optional") + "', expected optional=" + opt) + assert(opt == actualOpt, "Invalid 'optional' section '" + (dep \\ "optional") + "' for " + id + ", expected optional=" + opt) val actualScope = (dep \\ "scope") match { case Seq() => None; case x => Some(x.text) } - val acutalClassifier = (dep \\ "classifier") match { case Seq() => None; case x => Some(x.text) } - assert(actualScope == scope, "Invalid 'scope' section '" + (dep \\ "scope") + "', expected scope=" + scope) - assert(acutalClassifier == classifier, "Invalid 'classifier' section '" + (dep \\ "classifier") + "', expected classifier=" + classifier) + val actualClassifier = (dep \\ "classifier") match { case Seq() => None; case x => Some(x.text) } + assert(actualScope == scope, "Invalid 'scope' section '" + (dep \\ "scope") + "' for " + id + ", expected scope=" + scope) + assert(actualClassifier == classifier, "Invalid 'classifier' section '" + (dep \\ "classifier") + "' for " + id + ", expected classifier=" + classifier) } } } diff --git a/sbt/src/sbt-test/dependency-management/t468/build.sbt b/sbt/src/sbt-test/dependency-management/t468/build.sbt new file mode 100644 index 000000000..a47ffb223 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/t468/build.sbt @@ -0,0 +1,22 @@ +autoScalaLibrary := false + +ivyPaths <<= (baseDirectory, target)( (dir, t) => new IvyPaths(dir, Some(t / "ivy-cache"))) + +libraryDependencies ++= Seq( + "org.sat4j" % "org.sat4j.pb" % "2.3.1", + "org.sat4j" % "org.sat4j.core" % "2.3.1" +) + +TaskKey[Unit]("check-update") <<= update map { report => + val mods = report.configuration(Compile.name).get.allModules.map(_.name).toSet + val expected = Set("org.sat4j.pb", "org.sat4j.core") + if(mods != expected) + error("Expected modules " + expected + ", got: " + mods) +} + +TaskKey[Unit]("check-classpath") <<= dependencyClasspath in Compile map { cp => + val jars = cp.files.map(_.getName).toSet + val expected = Set("org.sat4j.pb-2.3.1.jar", "org.sat4j.core-2.3.1.jar") + if(jars != expected) + error("Expected jars " + expected + ", got: " + jars) +} \ No newline at end of file diff --git a/sbt/src/sbt-test/dependency-management/t468/test b/sbt/src/sbt-test/dependency-management/t468/test new file mode 100644 index 000000000..0006bc0f5 --- /dev/null +++ b/sbt/src/sbt-test/dependency-management/t468/test @@ -0,0 +1,2 @@ +> check-update +> check-classpath