mirror of https://github.com/sbt/sbt.git
Specify classifier on module level on CLI (#735)
This commit is contained in:
parent
796ec19493
commit
849d128b90
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue