Specify classifier on module level on CLI (#735)

This commit is contained in:
Yi Cheng 2018-01-29 12:02:49 -08:00 committed by Alexandre Archambault
parent 796ec19493
commit 849d128b90
8 changed files with 513 additions and 77 deletions

View File

@ -7,8 +7,23 @@ scala_library(
"core:core",
"extra/src/main/scala/coursier/extra:extra",
"extra/src/main/scala-2.11/coursier/extra:native",
":util",
],
sources = rglobs("*.scala"),
sources = globs(
"coursier/cli/scaladex/*.scala",
"coursier/cli/spark/*.scala",
"coursier/cli/*.scala",
),
)
scala_library(
name = "util",
dependencies = [
"3rdparty/jvm:argonaut-shapeless",
"cache/src/main/scala:cache",
"core:core",
],
sources = globs("coursier/cli/util/*.scala"),
)
jvm_binary(

View File

@ -10,6 +10,7 @@ import coursier.cli.scaladex.Scaladex
import coursier.cli.util.{JsonElem, JsonPrintRequirement, JsonReport}
import coursier.extra.Typelevel
import coursier.ivy.IvyRepository
import coursier.util.Parse.ModuleRequirements
import coursier.util.{Parse, Print}
import scala.annotation.tailrec
@ -83,9 +84,9 @@ class Helper(
isolated: IsolatedLoaderOptions = IsolatedLoaderOptions(),
warnBaseLoaderNotFound: Boolean = true
) {
import common._
import Helper.errPrintln
import Util._
import common._
val ttl0 =
if (ttl.isEmpty)
@ -152,7 +153,7 @@ class Helper(
val (scaladexRawDependencies, otherRawDependencies) =
rawDependencies.partition(s => s.contains("/") || !s.contains(":"))
val scaladexModuleVersionConfigs =
val scaladexDeps: List[Dependency] =
if (scaladexRawDependencies.isEmpty)
Nil
else {
@ -212,29 +213,9 @@ class Helper(
res
.collect { case \/-(l) => l }
.flatten
.map { case (mod, ver) => (mod, ver, None) }
.map { case (mod, ver) => Dependency(mod, ver) }
}
val (modVerCfgErrors, moduleVersionConfigs) =
Parse.moduleVersionConfigs(otherRawDependencies, scalaVersion)
val (intransitiveModVerCfgErrors, intransitiveModuleVersionConfigs) =
Parse.moduleVersionConfigs(intransitive, scalaVersion)
def allModuleVersionConfigs =
// FIXME Order of the dependencies is not respected here (scaladex ones go first)
scaladexModuleVersionConfigs ++ moduleVersionConfigs
prematureExitIf(modVerCfgErrors.nonEmpty) {
s"Cannot parse dependencies:\n" + modVerCfgErrors.map(" "+_).mkString("\n")
}
prematureExitIf(intransitiveModVerCfgErrors.nonEmpty) {
s"Cannot parse intransitive dependencies:\n" +
intransitiveModVerCfgErrors.map(" "+_).mkString("\n")
}
val (forceVersionErrors, forceVersions0) = Parse.moduleVersions(forceVersion, scalaVersion)
prematureExitIf(forceVersionErrors.nonEmpty) {
@ -265,12 +246,12 @@ class Helper(
prematureExitIf(excludesWithAttr.nonEmpty) {
s"Excluded modules with attributes not supported:\n" +
excludesWithAttr
.map(" " + _)
.mkString("\n")
excludesWithAttr
.map(" " + _)
.mkString("\n")
}
val excludes: Set[(String, String)] = excludesNoAttr.map { mod =>
val globalExcludes: Set[(String, String)] = excludesNoAttr.map { mod =>
(mod.organization, mod.name)
}.toSet
@ -296,30 +277,28 @@ class Helper(
}).groupBy(_._1).mapValues(_.map(_._2).toSet).toMap
}
val baseDependencies = allModuleVersionConfigs.map {
case (module, version, configOpt) =>
Dependency(
module,
version,
attributes = Attributes("", ""),
configuration = configOpt.getOrElse(defaultConfiguration),
exclusions = localExcludeMap.getOrElse(module.orgName, Set()) | excludes
)
val moduleReq = ModuleRequirements(globalExcludes, localExcludeMap, defaultConfiguration)
val (modVerCfgErrors: Seq[String], normalDeps: Seq[Dependency]) =
Parse.moduleVersionConfigs(otherRawDependencies, moduleReq, transitive=true, scalaVersion)
val (intransitiveModVerCfgErrors: Seq[String], intransitiveDeps: Seq[Dependency]) =
Parse.moduleVersionConfigs(intransitive, moduleReq, transitive=false, scalaVersion)
prematureExitIf(modVerCfgErrors.nonEmpty) {
s"Cannot parse dependencies:\n" + modVerCfgErrors.map(" "+_).mkString("\n")
}
val intransitiveDependencies = intransitiveModuleVersionConfigs.map {
case (module, version, configOpt) =>
Dependency(
module,
version,
attributes = Attributes("", ""),
configuration = configOpt.getOrElse(defaultConfiguration),
exclusions = excludes,
transitive = false
)
prematureExitIf(intransitiveModVerCfgErrors.nonEmpty) {
s"Cannot parse intransitive dependencies:\n" +
intransitiveModVerCfgErrors.map(" "+_).mkString("\n")
}
val dependencies = baseDependencies ++ intransitiveDependencies
val transitiveDeps: Seq[Dependency] =
// FIXME Order of the dependencies is not respected here (scaladex ones go first)
scaladexDeps ++ normalDeps
val allDependencies: Seq[Dependency] = transitiveDeps ++ intransitiveDeps
val checksums = {
val splitChecksumArgs = checksum.flatMap(_.split(',')).filter(_.nonEmpty)
@ -335,7 +314,7 @@ class Helper(
val userEnabledProfiles = profile.toSet
val startRes = Resolution(
dependencies.toSet,
allDependencies.toSet,
forceVersions = forceVersions,
filter = Some(dep => keepOptional || !dep.optional),
userActivations =
@ -376,7 +355,7 @@ class Helper(
errPrintln(
s" Dependencies:\n" +
Print.dependenciesUnknownConfigs(
dependencies,
allDependencies,
Map.empty,
printExclusions = verbosityLevel >= 2
)
@ -507,7 +486,7 @@ class Helper(
val depsStr =
if (reverseTree || tree)
Print.dependencyTree(
dependencies,
allDependencies,
res,
printExclusions = verbosityLevel >= 1,
reverse = reverseTree
@ -711,7 +690,7 @@ class Helper(
val deps: Seq[Dependency] = Set(getDepArtifactsForClassifier(sources, javadoc, res).map(_._1): _*).toSeq
// A map from requested org:name:version to reconciled org:name:version
val conflictResolutionForRoots: Map[String, String] = dependencies.map({ dep =>
val conflictResolutionForRoots: Map[String, String] = allDependencies.map({ dep =>
val reconciledVersion: String = res.reconciledVersions
.getOrElse(dep.module, dep.version)
if (reconciledVersion != dep.version) {
@ -842,7 +821,8 @@ class Helper(
// Trying to get the main class of the first artifact
val mainClassOpt = for {
(module, _, _) <- allModuleVersionConfigs.headOption
dep: Dependency <- transitiveDeps.headOption
module = dep.module
mainClass <- mainClasses.collectFirst {
case ((org, name), mainClass)
if org == module.organization && (
@ -854,7 +834,8 @@ class Helper(
} yield mainClass
def sameOrgOnlyMainClassOpt = for {
(module, _, _) <- allModuleVersionConfigs.headOption
dep: Dependency <- transitiveDeps.headOption
module = dep.module
orgMainClasses = mainClasses.collect {
case ((org, name), mainClass)
if org == module.organization =>

View File

@ -2,8 +2,8 @@ package coursier.cli
import java.io.{File, FileWriter}
import coursier.cli.util.ReportNode
import argonaut._, Argonaut._
import argonaut.Argonaut._
import coursier.cli.util.{DepNode, ReportNode}
import org.junit.runner.RunWith
import org.scalatest.FlatSpec
import org.scalatest.junit.JUnitRunner
@ -215,7 +215,185 @@ class CliIntegrationTest extends FlatSpec {
assert(node.conflict_resolution == Map("org.tukaani:xz:1.1" -> "org.tukaani:xz:1.2"))
}
}
}
/**
* Result:
* |└─ org.apache.commons:commons-compress:1.5
* | └─ org.tukaani:xz:1.2
*/
"classifier tests" should "have tests.jar" in withFile() {
(excludeFile, _) =>
withFile() {
(jsonFile, _) => {
val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath)
val fetchOpt = FetchOptions(common = commonOpt)
val fetch = new Fetch(fetchOpt) with TestOnlyExtraArgsApp
fetch.setRemainingArgs(Seq("org.apache.commons:commons-compress:1.5,classifier=tests"), Seq())
fetch.apply()
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 == "tests")
assert(compressNode.get.files.head._2.contains("commons-compress-1.5-tests.jar"))
assert(compressNode.get.dependencies.contains("org.tukaani:xz:1.2"))
}
}
}
/**
* Result:
* |├─ org.apache.commons:commons-compress:1.5
* |│ └─ org.tukaani:xz:1.2
* |└─ org.apache.commons:commons-compress:1.5
* | └─ org.tukaani:xz:1.2
*/
"mixed vanilla and classifier " should "have tests.jar and .jar" in withFile() {
(excludeFile, _) =>
withFile() {
(jsonFile, _) => {
val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath)
val fetchOpt = FetchOptions(common = commonOpt)
val fetch = new Fetch(fetchOpt) with TestOnlyExtraArgsApp
fetch.setRemainingArgs(
Seq("org.apache.commons:commons-compress:1.5,classifier=tests",
"org.apache.commons:commons-compress:1.5"),
Seq())
fetch.apply()
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"))
assert(compressNodes.last.files.head._1 == "tests")
assert(compressNodes.last.files.head._2.contains("commons-compress-1.5-tests.jar"))
}
}
}
/**
* Result:
* |└─ org.apache.commons:commons-compress:1.5
* | └─ org.tukaani:xz:1.2 // should not be fetched
*/
"intransitive" should "only fetch a single jar" in withFile() {
(_, _) =>
withFile() {
(jsonFile, _) => {
val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath, intransitive = List("org.apache.commons:commons-compress:1.5"))
val fetchOpt = FetchOptions(common = commonOpt)
val fetch = new Fetch(fetchOpt) with TestOnlyExtraArgsApp
fetch.apply()
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"))
assert(compressNode.get.dependencies.isEmpty)
}
}
}
/**
* Result:
* |└─ org.apache.commons:commons-compress:1.5
* | └─ org.tukaani:xz:1.2
*/
"intransitive classifier" should "only fetch a single tests jar" in withFile() {
(excludeFile, _) =>
withFile() {
(jsonFile, _) => {
val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath, intransitive = List("org.apache.commons:commons-compress:1.5,classifier=tests"))
val fetchOpt = FetchOptions(common = commonOpt)
val fetch = new Fetch(fetchOpt) with TestOnlyExtraArgsApp
fetch.setRemainingArgs(Seq(), Seq())
fetch.apply()
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 == "tests")
assert(compressNode.get.files.head._2.contains("commons-compress-1.5-tests.jar"))
assert(compressNode.get.dependencies.isEmpty)
}
}
}
/**
* Result:
* |└─ org.apache.commons:commons-compress:1.5 -> 1.4.1
* | └─ org.tukaani:xz:1.0
*/
"classifier with forced version" should "fetch tests jar" in withFile() {
(excludeFile, _) =>
withFile() {
(jsonFile, _) => {
val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath, forceVersion = List("org.apache.commons:commons-compress:1.4.1"))
val fetchOpt = FetchOptions(common = commonOpt)
val fetch = new Fetch(fetchOpt) with TestOnlyExtraArgsApp
fetch.setRemainingArgs(Seq("org.apache.commons:commons-compress:1.5,classifier=tests"), Seq())
fetch.apply()
val node: ReportNode = getReportFromJson(jsonFile)
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")
assert(compressNode.isDefined)
assert(compressNode.get.files.head._1 == "tests")
assert(compressNode.get.files.head._2.contains("commons-compress-1.4.1-tests.jar"))
assert(compressNode.get.dependencies.size == 1)
assert(compressNode.get.dependencies.head == "org.tukaani:xz:1.0")
}
}
}
/**
* Result:
* |└─ org.apache.commons:commons-compress:1.5 -> 1.4.1
* | └─ org.tukaani:xz:1.0 // should not be there
*/
"intransitive, classifier, forced version" should "fetch a single tests jar" in withFile() {
(excludeFile, _) =>
withFile() {
(jsonFile, _) => {
val commonOpt = CommonOptions(jsonOutputFile = jsonFile.getPath,
intransitive = List("org.apache.commons:commons-compress:1.5,classifier=tests"),
forceVersion = List("org.apache.commons:commons-compress:1.4.1"))
val fetchOpt = FetchOptions(common = commonOpt)
val fetch = new Fetch(fetchOpt) with TestOnlyExtraArgsApp
fetch.setRemainingArgs(Seq(), Seq())
fetch.apply()
val node: ReportNode = getReportFromJson(jsonFile)
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")
assert(compressNode.isDefined)
assert(compressNode.get.files.head._1 == "tests")
assert(compressNode.get.files.head._2.contains("commons-compress-1.4.1-tests.jar"))
assert(compressNode.get.dependencies.isEmpty)
}
}
}
}

View File

@ -1057,8 +1057,23 @@ final case class Resolution(
(source, proj) <- projectCache
.get(dep.moduleVersion)
.toSeq
classifiers = {
if (!dep.attributes.classifier.isEmpty) {
val stringSeq: Seq[String] = overrideClassifiers.getOrElse(Seq()) ++ Seq(dep.attributes.classifier)
if (stringSeq.isEmpty) {
Option.empty
}
else {
Some(stringSeq)
}
} else {
overrideClassifiers
}
}
artifact <- source
.artifacts(dep, proj, overrideClassifiers)
.artifacts(dep, proj, classifiers)
if optional || !artifact.isOptional
} yield dep -> artifact

View File

@ -1,11 +1,11 @@
package coursier.util
import coursier.core.{Repository, Module}
import coursier.{Attributes, Dependency}
import coursier.core.{Module, Repository}
import coursier.ivy.IvyRepository
import coursier.maven.MavenRepository
import scala.collection.mutable.ArrayBuffer
import scalaz.\/
import scalaz.Scalaz.ToEitherOps
@ -113,44 +113,131 @@ object Parse {
}
}
class ModuleParseError(private val message: String = "",
private val cause: Throwable = None.orNull)
extends Exception(message, cause)
@deprecated("use the variant accepting a default scala version", "1.0.0-M13")
def moduleVersionConfig(s: String): Either[String, (Module, String, Option[String])] =
moduleVersionConfig(s, defaultScalaVersion)
def moduleVersionConfig(s: String, defaultScalaVersion: String): Either[String, (Module, String, Option[String])] = {
val mvc: Either[String, Dependency] = moduleVersionConfig(s, ModuleRequirements(), transitive = true, defaultScalaVersion)
mvc match {
case Left(x) => Left(x)
case Right(d) => Right(d.module, d.version, Option(d.configuration).filter(_.trim.nonEmpty))
}
}
@deprecated("use the variant accepting a default scala version", "1.0.0-M13")
def moduleVersionConfig(s: String): Either[String, (Module, String, Option[String])] = {
val mvc: Either[String, Dependency] = moduleVersionConfig(s, ModuleRequirements(), transitive = true, defaultScalaVersion)
mvc match {
case Left(x) => Left(x)
case Right(d) => Right(d.module, d.version, Option(d.configuration).filter(_.trim.nonEmpty))
}
}
/**
* Parses coordinates like
* org:name:version
* possibly with attributes, like
* org:name;attr1=val1;attr2=val2:version
* with attributes, like
* org:name:version,attr1=val1,attr2=val2
* and a configuration, like
* org:name:version:config
* or
* org:name;attr1=val1;attr2=val2:version:config
* org:name:version:config,attr1=val1,attr2=val2
*
* Currently only "classifier" attribute is used, and others are ignored.
*/
def moduleVersionConfig(s: String, defaultScalaVersion: String): Either[String, (Module, String, Option[String])] = {
def moduleVersionConfig(s: String,
req: ModuleRequirements,
transitive: Boolean,
defaultScalaVersion: String): Either[String, Dependency] = {
val parts = s.split(":", 5)
// Assume org:name:version,attr1=val1,attr2=val2
// That is ',' has to go after ':'.
// E.g. "org:name,attr1=val1,attr2=val2:version:config" is illegal.
val attrSeparator = ","
val argSeparator = ":"
val strings = s.split(attrSeparator)
val coords = strings.head
val attrs = strings.drop(1).map({ x => {
if (x.mkString.contains(argSeparator)) {
throw new ModuleParseError(s"'$argSeparator' is not allowed in attribute '$x' in '$s'. Please follow the format " +
s"'org${argSeparator}name[${argSeparator}version][${argSeparator}config]${attrSeparator}attr1=val1${attrSeparator}attr2=val2'")
}
val y = x.split("=")
if (y.length != 2) {
throw new ModuleParseError(s"Failed to parse attribute '$x' in '$s'. Keyword argument expected such as 'classifier=tests'")
}
(y(0), y(1))
}
}).toMap
val parts = coords.split(":", 5)
val attributes = attrs.get("classifier") match {
case Some(c) => Attributes("", c)
case None => Attributes("", "")
}
val localExcludes = req.localExcludes
val globalExcludes = req.globalExcludes
val defaultConfig = req.defaultConfiguration
parts match {
case Array(org, "", rawName, version, config) =>
module(s"$org::$rawName", defaultScalaVersion)
.right
.map((_, version, Some(config)))
.map(mod => {
Dependency(
mod,
version,
config,
attributes,
transitive = transitive,
exclusions = localExcludes.getOrElse(mod.orgName, Set()) | globalExcludes)
})
case Array(org, "", rawName, version) =>
module(s"$org::$rawName", defaultScalaVersion)
.right
.map((_, version, None))
.map(mod => {
Dependency(
mod,
version,
configuration = defaultConfig,
attributes = attributes,
transitive = transitive,
exclusions = localExcludes.getOrElse(mod.orgName, Set()) | globalExcludes)
})
case Array(org, rawName, version, config) =>
module(s"$org:$rawName", defaultScalaVersion)
.right
.map((_, version, Some(config)))
.map(mod => {
Dependency(
mod,
version,
config,
attributes,
transitive = transitive,
exclusions = localExcludes.getOrElse(mod.orgName, Set()) | globalExcludes)
})
case Array(org, rawName, version) =>
module(s"$org:$rawName", defaultScalaVersion)
.right
.map((_, version, None))
.map(mod => {
Dependency(
mod,
version,
configuration = defaultConfig,
attributes = attributes,
transitive = transitive,
exclusions = localExcludes.getOrElse(mod.orgName, Set()) | globalExcludes)
})
case _ =>
Left(s"Malformed dependency: $s")
@ -170,16 +257,39 @@ object Parse {
valuesAndErrors(moduleVersion(_, defaultScalaVersion), l)
@deprecated("use the variant accepting a default scala version", "1.0.0-M13")
def moduleVersionConfigs(l: Seq[String]): (Seq[String], Seq[(Module, String, Option[String])]) =
moduleVersionConfigs(l, defaultScalaVersion)
def moduleVersionConfigs(l: Seq[String]): (Seq[String], Seq[(Module, String, Option[String])]) = {
val mvc: (Seq[String], Seq[Dependency]) = moduleVersionConfigs(l, ModuleRequirements(), transitive = true, defaultScalaVersion)
// convert empty config to None
(mvc._1, mvc._2.map(d => (d.module, d.version, Option(d.configuration).filter(_.trim.nonEmpty))))
}
@deprecated("use the variant accepting a default scala version", "1.0.0-M13")
def moduleVersionConfigs(l: Seq[String], defaultScalaVersion: String): (Seq[String], Seq[(Module, String, Option[String])]) = {
val mvc: (Seq[String], Seq[Dependency]) = moduleVersionConfigs(l, ModuleRequirements(), transitive = true, defaultScalaVersion)
(mvc._1, mvc._2.map(d => (d.module, d.version, Option(d.configuration).filter(_.trim.nonEmpty))))
}
/**
* Data holder for additional info that needs to be considered when parsing the module.
*
* @param globalExcludes global excludes that need to be applied to all modules
* @param localExcludes excludes to be applied to specific modules
* @param defaultConfiguration default configuration
*/
case class ModuleRequirements(globalExcludes: Set[(String, String)] = Set(),
localExcludes: Map[String, Set[(String, String)]] = Map(),
defaultConfiguration: String = "default(compile)")
/**
* Parses a sequence of coordinates having an optional configuration.
*
* @return Sequence of errors, and sequence of modules / versions / optional configurations
*/
def moduleVersionConfigs(l: Seq[String], defaultScalaVersion: String): (Seq[String], Seq[(Module, String, Option[String])]) =
valuesAndErrors(moduleVersionConfig(_, defaultScalaVersion), l)
def moduleVersionConfigs(l: Seq[String],
req: ModuleRequirements,
transitive: Boolean,
defaultScalaVersion: String): (Seq[String], Seq[Dependency]) =
valuesAndErrors(moduleVersionConfig(_, req, transitive, defaultScalaVersion), l)
def repository(s: String): String \/ Repository =
if (s == "central")

View File

@ -33,3 +33,6 @@ jvm_options: [
# TODO: Remove after https://github.com/pantsbuild/pants/issues/4477 is pulled in.
'-Dzinc.analysis.cache.limit=5000',
]
[test.junit]
jvm_options = ['-Xmx2048m']

View File

@ -2,8 +2,9 @@ package coursier
package test
import utest._
import scala.async.Async.{ async, await }
import scala.async.Async.{async, await}
import coursier.MavenRepository
import coursier.Platform.fetch
import coursier.test.compatibility._
@ -503,6 +504,57 @@ abstract class CentralTests extends TestSuite {
}
}
'classifier - {
// Adding extra repo so it's agnostic from nexus which only has the poms
val extraRepo = MavenRepository("https://repo1.maven.org/maven2")
'vanilla - {
async {
val deps = Set(
Dependency(
Module("org.apache.avro", "avro"), "1.8.1"
)
)
val res = await(resolve(deps, extraRepos = Seq(extraRepo)))
val filenames: Set[String] = res.artifacts.map(_.url.split("/").last).toSet
assert(filenames.contains("avro-1.8.1.jar"))
assert(!filenames.contains("avro-1.8.1-tests.jar"))
}
}
'tests - {
async {
val deps = Set(
Dependency(
Module("org.apache.avro", "avro"), "1.8.1", attributes = Attributes("", "tests")
)
)
val res = await(resolve(deps, extraRepos = Seq(extraRepo)))
val filenames: Set[String] = res.artifacts.map(_.url.split("/").last).toSet
assert(!filenames.contains("avro-1.8.1.jar"))
assert(filenames.contains("avro-1.8.1-tests.jar"))
}
}
'mixed - {
async {
val deps = Set(
Dependency(
Module("org.apache.avro", "avro"), "1.8.1"
),
Dependency(
Module("org.apache.avro", "avro"), "1.8.1", attributes = Attributes("", "tests")
)
)
val res = await(resolve(deps, extraRepos = Seq(extraRepo)))
val filenames: Set[String] = res.artifacts.map(_.url.split("/").last).toSet
assert(filenames.contains("avro-1.8.1.jar"))
assert(filenames.contains("avro-1.8.1-tests.jar"))
}
}
}
'artifacts - {
'uniqueness - {
async {

View File

@ -1,8 +1,9 @@
package coursier.test
import coursier.{MavenRepository, Repository}
import coursier.{Attributes, MavenRepository, Repository}
import coursier.ivy.IvyRepository
import coursier.util.Parse
import coursier.util.Parse.{ModuleParseError, ModuleRequirements}
import utest._
object ParseTests extends TestSuite {
@ -47,5 +48,86 @@ object ParseTests extends TestSuite {
val res = Parse.repository("jitpack")
assert(res.exists(isMavenRepo))
}
// Module parsing tests
"org:name:version" - {
Parse.moduleVersionConfig("org.apache.avro:avro:1.7.4", ModuleRequirements(), transitive = true, "2.11.11") match {
case Left(err) => assert(false)
case Right(dep) =>
assert(dep.module.organization == "org.apache.avro")
assert(dep.module.name == "avro")
assert(dep.version == "1.7.4")
assert(dep.configuration == "default(compile)")
assert(dep.attributes == Attributes())
}
}
"org:name:version:conifg" - {
Parse.moduleVersionConfig("org.apache.avro:avro:1.7.4:runtime", ModuleRequirements(), transitive = true, "2.11.11") match {
case Left(err) => assert(false)
case Right(dep) =>
assert(dep.module.organization == "org.apache.avro")
assert(dep.module.name == "avro")
assert(dep.version == "1.7.4")
assert(dep.configuration == "runtime")
assert(dep.attributes == Attributes())
}
}
"single attr" - {
Parse.moduleVersionConfig("org.apache.avro:avro:1.7.4:runtime,classifier=tests", ModuleRequirements(), transitive = true, "2.11.11") match {
case Left(err) => assert(false)
case Right(dep) =>
assert(dep.module.organization == "org.apache.avro")
assert(dep.module.name == "avro")
assert(dep.version == "1.7.4")
assert(dep.configuration == "runtime")
assert(dep.attributes == Attributes("", "tests"))
}
}
"multiple attrs" - {
Parse.moduleVersionConfig("org.apache.avro:avro:1.7.4:runtime,classifier=tests,nickname=superman", ModuleRequirements(), transitive = true, "2.11.11") match {
case Left(err) => assert(false)
case Right(dep) =>
assert(dep.module.organization == "org.apache.avro")
assert(dep.module.name == "avro")
assert(dep.version == "1.7.4")
assert(dep.configuration == "runtime")
assert(dep.attributes == Attributes("", "tests"))
}
}
"single attr with org::name:version" - {
Parse.moduleVersionConfig("io.get-coursier.scala-native::sandbox_native0.3:0.3.0-coursier-1,attr1=val1", ModuleRequirements(), transitive = true, "2.11.11") match {
case Left(err) => assert(false)
case Right(dep) =>
assert(dep.module.organization == "io.get-coursier.scala-native")
assert(dep.module.name.contains("sandbox_native0.3")) // use `contains` to be scala version agnostic
assert(dep.version == "0.3.0-coursier-1")
}
}
"illegal 1" - {
try {
Parse.moduleVersionConfig("org.apache.avro:avro,1.7.4:runtime,classifier=tests", ModuleRequirements(), transitive = true, "2.11.11")
assert(false) // Parsing should fail but succeeded.
}
catch {
case foo: ModuleParseError => assert(foo.getMessage().contains("':' is not allowed in attribute")) // do nothing
case _: Throwable => assert(false) // Unexpected exception
}
}
"illegal 2" - {
try {
Parse.moduleVersionConfig("junit:junit:4.12,attr", ModuleRequirements(), transitive = true, "2.11.11")
assert(false) // Parsing should fail but succeeded.
}
catch {
case foo: ModuleParseError => assert(foo.getMessage().contains("Failed to parse attribute")) // do nothing
case _: Throwable => assert(false) // Unexpected exception
}
}
}
}