json-report: one file per dependency instead of multiple; use m2 coords (#782)

When classifiers are used as part of dependency specifications, it's important to be able to select just the classified artifact. Unfortunately, in the current json, dependencies don't specify classifiers, so it isn't possible to just get one of the artifacts for a dependency when a classifier is required.

This patch introduces maven style artifact prefixes in order to include classifier and packaging information in the coordinates. By doing that, we can use them as keys in dependency lists more easily and it allows consumers of the json to treat those dependency keys as mostly opaque ids rather than having to parse them.

Addresses #743
This commit is contained in:
Nick Howard 2018-03-09 15:47:27 -07:00 committed by Yi Cheng
parent 2d4ab34408
commit d0b46864c8
6 changed files with 308 additions and 82 deletions

View File

@ -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()

View File

@ -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)
}

View File

@ -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(

View File

@ -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"))

View File

@ -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\"}")
}
}

View File

@ -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, *******)"
}
}