diff --git a/cli/src/main/scala-2.12/coursier/cli/Helper.scala b/cli/src/main/scala-2.12/coursier/cli/Helper.scala index add604fc0..3f842c66a 100644 --- a/cli/src/main/scala-2.12/coursier/cli/Helper.scala +++ b/cli/src/main/scala-2.12/coursier/cli/Helper.scala @@ -584,17 +584,33 @@ class Helper( } private def getDepArtifactsForClassifier(sources: Boolean, javadoc: Boolean, res0: Resolution): Seq[(Dependency, Artifact)] = { - if (classifier0.nonEmpty || sources || javadoc) { - var classifiers = classifier0 - if (sources) - classifiers = classifiers + "sources" - if (javadoc) - classifiers = classifiers + "javadoc" + val raw: Seq[(Dependency, Artifact)] = if (hasOverrideClassifiers(sources, javadoc)) { //TODO: this function somehow gives duplicated things - res0.dependencyClassifiersArtifacts(classifiers.toVector.sorted) + res0.dependencyClassifiersArtifacts(overrideClassifiers(sources, javadoc).toVector.sorted) } else { res0.dependencyArtifacts(withOptional = true) } + + raw.map({ case (dep, artifact) => + ( + dep.copy( + attributes = dep.attributes.copy(classifier = artifact.classifier)), + artifact + ) + }) + } + + private def overrideClassifiers(sources: Boolean, javadoc:Boolean): Set[String] = { + var classifiers = classifier0 + if (sources) + classifiers = classifiers + "sources" + if (javadoc) + classifiers = classifiers + "javadoc" + classifiers + } + + private def hasOverrideClassifiers(sources: Boolean, javadoc: Boolean): Boolean = { + classifier0.nonEmpty || sources || javadoc } def fetchMap( @@ -708,9 +724,17 @@ class Helper( val artifacts: Seq[(Dependency, Artifact)] = res.dependencyArtifacts val jsonReq = JsonPrintRequirement(artifactToFile, depToArtifacts) - val roots = deps.toVector.map(JsonElem(_, artifacts, Option(jsonReq), res, printExclusions = verbosityLevel >= 1, excluded = false, colors = false)) - val jsonStr = JsonReport(roots, conflictResolutionForRoots)(_.children, _.reconciledVersionStr, _.requestedVersionStr, _.downloadedFiles) - + val roots = deps.toVector.map(JsonElem(_, artifacts, Option(jsonReq), res, printExclusions = verbosityLevel >= 1, excluded = false, colors = false, overrideClassifiers = overrideClassifiers(sources, javadoc))) + val jsonStr = JsonReport( + roots, + conflictResolutionForRoots, + overrideClassifiers(sources, javadoc) + )( + _.children, + _.reconciledVersionStr, + _.requestedVersionStr, + _.downloadedFile + ) val pw = new PrintWriter(new File(jsonOutputFile)) pw.write(jsonStr) pw.close() diff --git a/cli/src/main/scala-2.12/coursier/cli/util/JsonReport.scala b/cli/src/main/scala-2.12/coursier/cli/util/JsonReport.scala index ecde1a281..33dec84ca 100644 --- a/cli/src/main/scala-2.12/coursier/cli/util/JsonReport.scala +++ b/cli/src/main/scala-2.12/coursier/cli/util/JsonReport.scala @@ -12,9 +12,18 @@ import scala.collection.parallel.ParSeq import argonaut._ import Argonaut._ +/** + * Lookup table for files and artifacts to print in the JsonReport. + */ final case class JsonPrintRequirement(fileByArtifact: Map[String, File], depToArtifacts: Map[Dependency, Vector[Artifact]]) -final case class DepNode(coord: String, files: Vector[(String, String)], dependencies: Set[String]) +/** + * Represents a resolved dependency's artifact in the JsonReport. + * @param coord String representation of the artifact's maven coordinate. + * @param file The path to the file for the artifact. + * @param dependencies The dependencies of the artifact. + */ +final case class DepNode(coord: String, file: Option[String], dependencies: Set[String]) final case class ReportNode(conflict_resolution: Map[String, String], dependencies: Vector[DepNode], version: String) @@ -34,7 +43,7 @@ object ReportNode { import argonaut.ArgonautShapeless._ implicit val encodeJson = EncodeJson.of[ReportNode] implicit val decodeJson = DecodeJson.of[ReportNode] - val version = "0.0.1" + val version = "0.1.0" } @@ -42,8 +51,8 @@ object JsonReport { private val printer = PrettyParams.nospace.copy(preserveOrder = true) - def apply[T](roots: IndexedSeq[T], conflictResolutionForRoots: Map[String, String]) - (children: T => Seq[T], reconciledVersionStr: T => String, requestedVersionStr: T => String, getFiles: T => Seq[(String, String)]): String = { + def apply[T](roots: IndexedSeq[T], conflictResolutionForRoots: Map[String, String], overrideClassifiers: Set[String]) + (children: T => Seq[T], reconciledVersionStr: T => String, requestedVersionStr: T => String, getFile: T => Option[String]): String = { val rootDeps: ParSeq[DepNode] = roots.par.map(r => { @@ -64,10 +73,10 @@ object JsonReport { val acc = scala.collection.mutable.Set[String]() flattenDeps(Seq(r), Set(), acc) - DepNode(reconciledVersionStr(r), getFiles(r).toVector, acc.toSet) + DepNode(reconciledVersionStr(r), getFile(r), acc.toSet) }) - val report = ReportNode(conflictResolutionForRoots, rootDeps.toVector, ReportNode.version) + val report = ReportNode(conflictResolutionForRoots, rootDeps.toVector.sortBy(_.coord), ReportNode.version) printer.pretty(report.asJson) } @@ -80,7 +89,9 @@ final case class JsonElem(dep: Dependency, resolution: Resolution, colors: Boolean, printExclusions: Boolean, - excluded: Boolean) { + excluded: Boolean, + overrideClassifiers: Set[String] + ) { val (red, yellow, reset) = if (colors) @@ -89,23 +100,24 @@ final case class JsonElem(dep: Dependency, ("", "", "") // This is used to printing json output - // Seq of (classifier, file path) tuple - lazy val downloadedFiles: Seq[(String, String)] = { - jsonPrintRequirement match { - case Some(req) => + // Option of the file path + lazy val downloadedFile: Option[String] = { + jsonPrintRequirement.flatMap(req => req.depToArtifacts.getOrElse(dep, Seq()) - .map(x => (x.classifier, req.fileByArtifact.get(x.url))) - .filter(_._2.isDefined) - .map(x => (x._1, x._2.get.getPath)) - case None => Seq() - } + .filter(_.classifier == dep.attributes.classifier) + .map(x => req.fileByArtifact.get(x.url)) + .filter(_.isDefined) + .filter(_.nonEmpty) + .map(_.get.getPath) + .headOption + ) } lazy val reconciledVersion: String = resolution.reconciledVersions .getOrElse(dep.module, dep.version) // These are used to printing json output - val reconciledVersionStr = s"${dep.module}:$reconciledVersion" + val reconciledVersionStr = s"${dep.mavenPrefix}:$reconciledVersion" val requestedVersionStr = s"${dep.module}:${dep.version}" lazy val repr = @@ -150,6 +162,12 @@ final case class JsonElem(dep: Dependency, withReconciledVersions = false ).sortBy { trDep => (trDep.module.organization, trDep.module.name, trDep.version) + }.map { d => + if (overrideClassifiers.contains(dep0.attributes.classifier)) { + d.copy(attributes = d.attributes.copy(classifier = dep0.attributes.classifier)) + } else { + d + } } def excluded = resolution @@ -164,17 +182,18 @@ final case class JsonElem(dep: Dependency, .filterNot(dependencies.map(_.moduleVersion).toSet).map { case (mod, ver) => JsonElem( - Dependency(mod, ver, "", Set.empty, Attributes("", ""), false, false), + Dependency(mod, ver, "", Set.empty, Attributes("", ""), optional = false, transitive = false), artifacts, jsonPrintRequirement, resolution, colors, printExclusions, - excluded = true + excluded = true, + overrideClassifiers = overrideClassifiers ) } - dependencies.map(JsonElem(_, artifacts, jsonPrintRequirement, resolution, colors, printExclusions, excluded = false)) ++ + dependencies.map(JsonElem(_, artifacts, jsonPrintRequirement, resolution, colors, printExclusions, excluded = false, overrideClassifiers = overrideClassifiers)) ++ (if (printExclusions) excluded else Nil) } @@ -183,5 +202,5 @@ final case class JsonElem(dep: Dependency, * children's children, causing performance issue. Hash collision should be rare, but when that happens, the * default equality check should take of the recursive aspect of `children`. */ - override def hashCode(): Int = Objects.hash(dep, requestedVersionStr, reconciledVersion, downloadedFiles) + override def hashCode(): Int = Objects.hash(dep, requestedVersionStr, reconciledVersion, downloadedFile) } diff --git a/cli/src/test/scala-2.12/coursier/cli/BUILD b/cli/src/test/scala-2.12/coursier/cli/BUILD index 7eb254259..8f6241bd8 100644 --- a/cli/src/test/scala-2.12/coursier/cli/BUILD +++ b/cli/src/test/scala-2.12/coursier/cli/BUILD @@ -5,7 +5,7 @@ junit_tests( "3rdparty/jvm:scalatest", "cli/src/main/scala-2.12:cli", ], - sources = ["CliFetchIntegrationTest.scala", "CliUnitTest.scala"], + sources = rglobs("*.scala", exclude = ["CliTestLib.scala", "CliBootstrapIntegrationTest.scala"]), ) scala_library( diff --git a/cli/src/test/scala-2.12/coursier/cli/CliFetchIntegrationTest.scala b/cli/src/test/scala-2.12/coursier/cli/CliFetchIntegrationTest.scala index 1eac9d23d..aa09480a6 100644 --- a/cli/src/test/scala-2.12/coursier/cli/CliFetchIntegrationTest.scala +++ b/cli/src/test/scala-2.12/coursier/cli/CliFetchIntegrationTest.scala @@ -1,7 +1,7 @@ package coursier.cli import java.io._ -import java.util.zip.ZipInputStream + import java.net.URLEncoder.encode import argonaut.Argonaut._ import coursier.cli.util.{DepNode, ReportNode} @@ -27,6 +27,8 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { } } + private val fileNameLength: DepNode => Int = _.file.getOrElse("").length + "Normal fetch" should "get all files" in { val fetchOpt = FetchOptions(common = CommonOptions()) @@ -92,7 +94,7 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { // org.apache.commons:commons-compress:1.4.1 should not contain deps underneath it. val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.4.1") - assert(compressNode.isDefined) + assert(node.dependencies.exists(_.coord == "org.apache.commons:commons-compress:1.4.1")) assert(compressNode.get.dependencies.isEmpty) } } @@ -200,10 +202,10 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val node: ReportNode = getReportFromJson(jsonFile) - val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.5") + val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:jar:tests:1.5") + assert(compressNode.isDefined) - assert(compressNode.get.files.head._1 == "tests") - assert(compressNode.get.files.head._2.contains("commons-compress-1.5-tests.jar")) + compressNode.get.file.map(f => assert(f.contains("commons-compress-1.5-tests.jar"))).orElse(fail("Not Defined")) assert(compressNode.get.dependencies.contains("org.tukaani:xz:1.2")) } } @@ -237,14 +239,15 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val node: ReportNode = getReportFromJson(jsonFile) val compressNodes: Seq[DepNode] = node.dependencies - .filter(_.coord == "org.apache.commons:commons-compress:1.5") - .sortBy(_.files.head._1.length) // sort by first classifier length - assert(compressNodes.length == 2) - assert(compressNodes.head.files.head._1 == "") - assert(compressNodes.head.files.head._2.contains("commons-compress-1.5.jar")) + .filter(_.coord.startsWith("org.apache.commons:commons-compress")) + .sortBy(_.coord.length) // sort by coord length - assert(compressNodes.last.files.head._1 == "tests") - assert(compressNodes.last.files.head._2.contains("commons-compress-1.5-tests.jar")) + assert(compressNodes.length == 2) + assert(compressNodes.head.coord == "org.apache.commons:commons-compress:1.5") + compressNodes.head.file.map( f => assert(f.contains("commons-compress-1.5.jar"))).orElse(fail("Not Defined")) + + assert(compressNodes.last.coord == "org.apache.commons:commons-compress:jar:tests:1.5") + compressNodes.last.file.map( f => assert(f.contains("commons-compress-1.5-tests.jar"))).orElse(fail("Not Defined")) } } } @@ -266,8 +269,7 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val node: ReportNode = getReportFromJson(jsonFile) val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.5") assert(compressNode.isDefined) - assert(compressNode.get.files.head._1 == "") - assert(compressNode.get.files.head._2.contains("commons-compress-1.5.jar")) + compressNode.get.file.map( f => assert(f.contains("commons-compress-1.5.jar"))).orElse(fail("Not Defined")) assert(compressNode.get.dependencies.isEmpty) } @@ -290,10 +292,9 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val node: ReportNode = getReportFromJson(jsonFile) - val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.5") + val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:jar:tests:1.5") assert(compressNode.isDefined) - assert(compressNode.get.files.head._1 == "tests") - assert(compressNode.get.files.head._2.contains("commons-compress-1.5-tests.jar")) + compressNode.get.file.map( f => assert(f.contains("commons-compress-1.5-tests.jar"))).orElse(fail("Not Defined")) assert(compressNode.get.dependencies.isEmpty) } @@ -321,10 +322,10 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { assert(!node.dependencies.exists(_.coord == "org.apache.commons:commons-compress:1.5")) - val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.4.1") + val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:jar:tests:1.4.1") + assert(compressNode.isDefined) - assert(compressNode.get.files.head._1 == "tests") - assert(compressNode.get.files.head._2.contains("commons-compress-1.4.1-tests.jar")) + compressNode.get.file.map( f => assert(f.contains("commons-compress-1.4.1-tests.jar"))).orElse(fail("Not Defined")) assert(compressNode.get.dependencies.size == 1) assert(compressNode.get.dependencies.head == "org.tukaani:xz:1.0") @@ -352,10 +353,10 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { assert(!node.dependencies.exists(_.coord == "org.apache.commons:commons-compress:1.5")) - val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.4.1") + val compressNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:jar:tests:1.4.1") + assert(compressNode.isDefined) - assert(compressNode.get.files.head._1 == "tests") - assert(compressNode.get.files.head._2.contains("commons-compress-1.4.1-tests.jar")) + compressNode.get.file.map( f => assert(f.contains("commons-compress-1.4.1-tests.jar"))).orElse(fail("Not Defined")) assert(compressNode.get.dependencies.isEmpty) } @@ -381,6 +382,29 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { assert(!node.dependencies.exists(_.coord.startsWith("org.scala-lang:scala-library:2.11."))) } + "com.spotify:helios-testing:0.9.193" should "have dependencies with classifiers" in withFile() { + (excludeFile, _) => + withFile() { + (jsonFile, _) => { + val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath) + val fetchOpt = FetchOptions(common = commonOpt) + + val heliosCoord = "com.spotify:helios-testing:0.9.193" + + Fetch( + fetchOpt, + RemainingArgs(Seq(heliosCoord), Seq()) + ) + val node: ReportNode = getReportFromJson(jsonFile) + val testEntry: DepNode = node.dependencies.find(_.coord == heliosCoord).get + assert( + testEntry.dependencies.exists(_.startsWith("com.spotify:docker-client:jar:shaded:"))) + assert( + node.dependencies.exists(_.coord.startsWith("com.spotify:docker-client:jar:shaded:"))) + } + } + } + /** * Result: * |└─ org.apache.commons:commons-compress:1.5 @@ -411,11 +435,10 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val depNodes: Seq[DepNode] = node.dependencies .filter(_.coord == "org.apache.commons:commons-compress:1.5") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(depNodes.length == 1) - val urlInJsonFile = depNodes.head.files.head._2 - assert(depNodes.head.files.head._1 == "") + val urlInJsonFile = depNodes.head.file.get assert(urlInJsonFile.contains(path)) // open jar and inspect contents @@ -453,10 +476,9 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val depNodes: Seq[DepNode] = node.dependencies .filter(_.coord == "org.apache.commons:commons-compress:1.5") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(depNodes.length == 1) - assert(depNodes.head.files.head._1 == "") - assert(depNodes.head.files.head._2.contains("junit/junit/4.12/junit-4.12.jar")) + depNodes.head.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) } } @@ -487,10 +509,9 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val depNodes: Seq[DepNode] = node.dependencies .filter(_.coord == "a:b:c") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(depNodes.length == 1) - assert(depNodes.head.files.head._1 == "") - assert(depNodes.head.files.head._2.contains("junit/junit/4.12/junit-4.12.jar")) + depNodes.head.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) } } @@ -519,12 +540,96 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val node: ReportNode = getReportFromJson(jsonFile) val depNodes: Seq[DepNode] = node.dependencies - .filter(_.coord == "org.apache.commons:commons-compress:1.5") - .sortBy(_.files.head._1.length) + .filter(_.coord.startsWith("org.apache.commons:commons-compress:")) + .sortBy(fileNameLength) + + + val coords: Seq[String] = node.dependencies + .map(_.coord) + .sorted + assert(depNodes.length == 1) // classifier doesn't matter when we have a url so it is not listed - assert(depNodes.head.files.head._1 == "") - assert(depNodes.head.files.head._2.contains("junit/junit/4.12/junit-4.12.jar")) + depNodes.head.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) + } + } + + /** + * Result: + * |└─ org.apache.commons:commons-compress:1.5 + * | └─ org.tukaani:xz:1.2 + * |└─ org.tukaani:xz:1.2 // with the file from the URL + */ + "external dep url with classifier that is a transitive dep" should "fetch junit-4.12.jar and classifier gets thrown away" in withFile() { + (jsonFile, _) => { + val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath) + val fetchOpt = FetchOptions(common = commonOpt) + + // encode path to different jar than requested + val externalUrl = encode("http://central.maven.org/maven2/junit/junit/4.12/junit-4.12.jar", "UTF-8") + + Fetch.run( + fetchOpt, + RemainingArgs( + Seq( + "org.apache.commons:commons-compress:1.5", + "org.tukaani:xz:1.2,classifier=tests,url="+externalUrl + ), + Seq() + ) + ) + + val node: ReportNode = getReportFromJson(jsonFile) + val depNodes: Seq[DepNode] = node.dependencies + .filter(_.coord.startsWith("org.tukaani:xz:")) + .sortBy(fileNameLength) + val coords: Seq[String] = node.dependencies.map(_.coord).sorted + + assert(coords == Seq("org.apache.commons:commons-compress:1.5", "org.tukaani:xz:1.2")) + assert(depNodes.length == 1) + assert(depNodes.last.file.isDefined) + depNodes.last.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) + } + } + + /** + * Result: + * |└─ org.apache.commons:commons-compress:1.5,classifier=sources + * └─ org.tukaani:xz:1.2,classifier=sources + */ + "classifier sources" should "fetch sources jar" in withFile() { + (jsonFile, _) => { + val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath) + val fetchOpt = FetchOptions(common = commonOpt, sources=true) + + // encode path to different jar than requested + + Fetch.run( + fetchOpt, + RemainingArgs( + Seq( + "org.apache.commons:commons-compress:1.5,classifier=sources" + ), + Seq() + ) + ) + val node: ReportNode = getReportFromJson(jsonFile) + val coords: Seq[String] = node.dependencies.map(_.coord).sorted + val depNodes: Seq[DepNode] = node.dependencies + .filter(_.coord.startsWith("org.apache.commons")) + .sortBy(fileNameLength) + + assert(depNodes.length == 1) + assert(depNodes.head.file.isDefined) + depNodes.head.file.map(f => assert(f.contains("1.5-sources.jar"))).orElse(fail("Not Defined")) + depNodes.head.dependencies.foreach(d => { + assert(d.contains(":sources:")) + }) + + assert(coords == Seq( + "org.apache.commons:commons-compress:jar:sources:1.5", + "org.tukaani:xz:jar:sources:1.2") + ) } } @@ -559,24 +664,23 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val compressNodes = depNodes .filter(_.coord == "org.apache.commons:commons-compress:1.5") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(compressNodes.length == 1) - assert(compressNodes.head.files.head._1 == "") - assert(compressNodes.head.files.head._2.contains("junit/junit/4.12/junit-4.12.jar")) + compressNodes.head.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) val jacksonMapperNodes = depNodes .filter(_.coord == "org.codehaus.jackson:jackson-mapper-asl:1.8.8") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(jacksonMapperNodes.length == 1) - assert(jacksonMapperNodes.head.files.head._2.contains("org/codehaus/jackson/jackson-mapper-asl/1.8.8/jackson-mapper-asl-1.8.8.jar")) + jacksonMapperNodes.head.file.map( f => assert(f.contains("org/codehaus/jackson/jackson-mapper-asl/1.8.8/jackson-mapper-asl-1.8.8.jar"))).orElse(fail("Not Defined")) assert(jacksonMapperNodes.head.dependencies.size == 1) assert(jacksonMapperNodes.head.dependencies.head == "org.codehaus.jackson:jackson-core-asl:1.8.8") val jacksonCoreNodes = depNodes .filter(_.coord == "org.codehaus.jackson:jackson-core-asl:1.8.8") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(jacksonCoreNodes.length == 1) - assert(jacksonCoreNodes.head.files.head._2.contains("org/codehaus/jackson/jackson-core-asl/1.8.8/jackson-core-asl-1.8.8.jar")) + jacksonCoreNodes.head.file.map( f => assert(f.contains("org/codehaus/jackson/jackson-core-asl/1.8.8/jackson-core-asl-1.8.8.jar"))).orElse(fail("Not Defined")) } } @@ -634,8 +738,7 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val depNodes: Seq[DepNode] = node.dependencies assert(depNodes.length == 1) - assert(depNodes.head.files.head._1 == "") - assert(depNodes.head.files.head._2.contains("junit/junit/4.12/junit-4.12.jar")) + depNodes.head.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) } } @@ -666,10 +769,9 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val depNodes: Seq[DepNode] = node.dependencies .filter(_.coord == "org.apache.commons:commons-compress:1.5") - .sortBy(_.files.head._1.length) + .sortBy(fileNameLength) assert(depNodes.length == 1) - assert(depNodes.head.files.head._1 == "") - assert(depNodes.head.files.head._2.contains("junit/junit/4.12/junit-4.12.jar")) + depNodes.head.file.map( f => assert(f.contains("junit/junit/4.12/junit-4.12.jar"))).orElse(fail("Not Defined")) } } @@ -701,7 +803,7 @@ class CliFetchIntegrationTest extends FlatSpec with CliTestLib { val depNode = node.dependencies.find(_.coord == "org.apache.commons:commons-compress:1.5") assert(depNode.isDefined) - assert(depNode.get.files.head._2.contains("commons-compress-1.5.jar")) + depNode.get.file.map( f => assert(f.contains("commons-compress-1.5.jar"))).orElse(fail("Not Defined")) assert(depNode.get.dependencies.size == 1) assert(depNode.get.dependencies.head.contains("org.tukaani:xz:1.2")) diff --git a/cli/src/test/scala-2.12/coursier/cli/util/JsonReportTest.scala b/cli/src/test/scala-2.12/coursier/cli/util/JsonReportTest.scala new file mode 100644 index 000000000..39ec06e2c --- /dev/null +++ b/cli/src/test/scala-2.12/coursier/cli/util/JsonReportTest.scala @@ -0,0 +1,60 @@ +package coursier.cli.util + +import coursier.cli.CliTestLib +import org.junit.runner.RunWith +import org.scalatest.FlatSpec +import org.scalatest.junit.JUnitRunner + +@RunWith(classOf[JUnitRunner]) +class JsonReportTest extends FlatSpec with CliTestLib { + "empty JsonReport" should "be empty" in { + val report: String = JsonReport[String](IndexedSeq(), Map(), Set())( + children = _ => Seq(), + reconciledVersionStr = _ => "", + requestedVersionStr = _ => "", + getFile = _ => Option("") + ) + + assert( + report == "{\"conflict_resolution\":{},\"dependencies\":[],\"version\":\"0.1.0\"}") + } + + "JsonReport containing two deps" should "not be empty" in { + val children = Map("a" -> Seq("b"), "b" -> Seq()) + val report: String = JsonReport[String]( + roots = IndexedSeq("a", "b"), + conflictResolutionForRoots = Map(), + overrideClassifiers = Set() + )( + children = children(_), + reconciledVersionStr = s => s"$s:reconciled", + requestedVersionStr = s => s"$s:requested", + getFile = _ => Option("") + ) + + assert( + report == "{\"conflict_resolution\":{},\"dependencies\":[" + + "{\"coord\":\"a:reconciled\",\"file\":\"\",\"dependencies\":[\"b:reconciled\"]}," + + "{\"coord\":\"b:reconciled\",\"file\":\"\",\"dependencies\":[]}]," + + "\"version\":\"0.1.0\"}") + } + "JsonReport containing two deps" should "be sorted alphabetically regardless of input order" in { + val children = Map("a" -> Seq("b"), "b" -> Seq()) + val report: String = JsonReport[String]( + roots = IndexedSeq( "b", "a"), + conflictResolutionForRoots = Map(), + overrideClassifiers = Set() + )( + children = children(_), + reconciledVersionStr = s => s"$s:reconciled", + requestedVersionStr = s => s"$s:requested", + getFile = _ => Option("") + ) + + assert( + report == "{\"conflict_resolution\":{},\"dependencies\":[" + + "{\"coord\":\"a:reconciled\",\"file\":\"\",\"dependencies\":[\"b:reconciled\"]}," + + "{\"coord\":\"b:reconciled\",\"file\":\"\",\"dependencies\":[]}]," + + "\"version\":\"0.1.0\"}") + } +} diff --git a/core/shared/src/main/scala/coursier/core/Definitions.scala b/core/shared/src/main/scala/coursier/core/Definitions.scala index 82b23d584..859c9e565 100644 --- a/core/shared/src/main/scala/coursier/core/Definitions.scala +++ b/core/shared/src/main/scala/coursier/core/Definitions.scala @@ -58,6 +58,14 @@ final case class Dependency( lazy val moduleVersion = (module, version) override lazy val hashCode = Dependency.unapply(this).get.hashCode() + + def mavenPrefix: String = { + if (attributes.isEmpty) + module.orgName + else { + s"${module.orgName}:${attributes.packagingAndClassifier}" + } + } } // Maven-specific @@ -65,6 +73,19 @@ final case class Attributes( `type`: String, classifier: String ) { + def packaging: String = if (`type`.isEmpty) + "jar" + else + `type` + + def packagingAndClassifier: String = if (isEmpty) { + "" + } else if (classifier.isEmpty) { + packaging + } else { + s"$packaging:$classifier" + } + def publication(name: String, ext: String): Publication = Publication(name, `type`, ext, classifier) @@ -238,4 +259,4 @@ final case class Authentication( ) { override def toString: String = s"Authentication($user, *******)" -} \ No newline at end of file +}