diff --git a/USAGE.md b/USAGE.md index 37a3aa327..a0357fde2 100644 --- a/USAGE.md +++ b/USAGE.md @@ -5,7 +5,7 @@ Ensure you have a dependency on its artifact, e.g. add in `build.sbt`, resolvers += Resolver.sonatypeRepo("snapshots") libraryDependencies += - "eu.frowning-lambda" %% "coursier" % "0.1.0-SNAPSHOT" + "com.github.alexarchambault" %% "coursier" % "0.1.0-SNAPSHOT" ``` Then, diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index 118650904..2d2c292f7 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -27,6 +27,8 @@ case class Coursier(scope: List[String], base.relativize(f.toURI).getPath val logger: RemoteLogger with ArtifactDownloaderLogger = new RemoteLogger with ArtifactDownloaderLogger { + def println(s: String) = Console.err.println(s) + def downloading(url: String) = println(s"Downloading $url") def downloaded(url: String, success: Boolean) = @@ -91,16 +93,27 @@ case class Coursier(scope: List[String], ).run if (!res.isDone) { - println(s"Maximum number of iteration reached!") + Console.err.println(s"Maximum number of iteration reached!") sys exit 1 } - def repr(dep: Dependency) = - s"${dep.module.organization}:${dep.module.name}:${dep.`type`}:${Some(dep.classifier).filter(_.nonEmpty).map(_+":").mkString}${dep.version}" + def repr(dep: Dependency) = { + val (type0, classifier) = dep.artifacts match { + case maven: Artifacts.Maven => (maven.`type`, maven.classifier) + } + + // dep.version can be an interval, whereas the one from project can't + val version = res.projectsCache.get(dep.moduleVersion).map(_._2.version).getOrElse(dep.version) + val extra = + if (version == dep.version) "" + else s" ($version for ${dep.version})" + + s"${dep.module.organization}:${dep.module.name}:$type0:${Some(classifier).filter(_.nonEmpty).map(_+":").mkString}$version$extra" + } val trDeps = res.dependencies.toList.sortBy(repr) - println("\n" + trDeps.map(repr).mkString("\n")) + println("\n" + trDeps.map(repr).distinct.mkString("\n")) if (res.conflicts.nonEmpty) { // Needs test @@ -143,12 +156,12 @@ case class Coursier(scope: List[String], (repo, deps) <- sorted dl = downloaders(repo) dep <- deps + (_, proj) = res.projectsCache(dep.moduleVersion) } yield { - dl.artifact(dep, cachePolicy = cachePolicy).run.map { - case -\/(err) => - println(s"Failed to get ${repr(dep)}: $err") - case \/-(f) => - println(s"${repr(dep)}:\n ${fileRepr(f)}") + dl.artifacts(dep, proj, cachePolicy = cachePolicy).map { results => + val errorCount = results.count{case -\/(_) => true; case _ => false} + val resultsRepr = results.map(_.map(fileRepr).merge).map(" " + _).mkString("\n") + println(s"${repr(dep)} (${results.length} artifact(s)${if (errorCount > 0) s", $errorCount error(s)" else ""}):\n$resultsRepr") } } diff --git a/core-js/src/main/scala/coursier/core/Remote.scala b/core-js/src/main/scala/coursier/core/Remote.scala index 9145eab9d..6aa2d2700 100644 --- a/core-js/src/main/scala/coursier/core/Remote.scala +++ b/core-js/src/main/scala/coursier/core/Remote.scala @@ -77,13 +77,13 @@ trait Logger { def other(url: String, msg: String): Unit } -case class Remote(base: String, logger: Option[Logger] = None) extends Repository { +case class Remote(base: String, logger: Option[Logger] = None) extends MavenRepository { - def find(module: Module, - version: String, - cachePolicy: CachePolicy): EitherT[Task, String, Project] = { + def findNoInterval(module: Module, + version: String, + cachePolicy: CachePolicy): EitherT[Task, String, Project] = { - val relPath = { + val path = { module.organization.split('.').toSeq ++ Seq( module.name, version, @@ -91,7 +91,7 @@ case class Remote(base: String, logger: Option[Logger] = None) extends Repositor ) } .map(Remote.encodeURIComponent) - val url = base + relPath.mkString("/") + val url = base + path.mkString("/") EitherT(Task{ implicit ec => logger.foreach(_.fetching(url)) @@ -111,6 +111,28 @@ case class Remote(base: String, logger: Option[Logger] = None) extends Repositor def versions(organization: String, name: String, - cachePolicy: CachePolicy): EitherT[Task, String, Versions] = ??? + cachePolicy: CachePolicy): EitherT[Task, String, Versions] = { + + val path = { + organization.split('.').toSeq ++ Seq( + name, + "maven-metadata.xml" + ) + } .map(Remote.encodeURIComponent) + + val url = base + path.mkString("/") + + EitherT(Task{ implicit ec => + logger.foreach(_.fetching(url)) + Remote.get(url).recover{case e: Exception => Left(e.getMessage)}.map{ eitherXml => + logger.foreach(_.fetched(url)) + for { + xml <- \/.fromEither(eitherXml) + _ <- if (xml.label == "metadata") \/-(()) else -\/("Metadata not found") + versions <- Xml.versions(xml) + } yield versions + } + }) + } } diff --git a/core-js/src/main/scala/scalaz/concurrent/package.scala b/core-js/src/main/scala/scalaz/concurrent/package.scala index c0ace3e11..095f06222 100644 --- a/core-js/src/main/scala/scalaz/concurrent/package.scala +++ b/core-js/src/main/scala/scalaz/concurrent/package.scala @@ -32,10 +32,10 @@ package object concurrent { def runF(implicit ec: ExecutionContext) = Future.traverse(tasks)(_.runF) } - implicit val taskFunctor: Functor[Task] = - new Functor[Task] { - def map[A, B](fa: Task[A])(f: A => B): Task[B] = - fa.map(f) + implicit val taskMonad: Monad[Task] = + new Monad[Task] { + def point[A](a: => A): Task[A] = Task.now(a) + def bind[A,B](fa: Task[A])(f: A => Task[B]): Task[B] = fa.flatMap(f) } } diff --git a/core-js/src/test/scala/coursier/test/compatibility/package.scala b/core-js/src/test/scala/coursier/test/compatibility/package.scala index 377b8e7b2..87ef5860a 100644 --- a/core-js/src/test/scala/coursier/test/compatibility/package.scala +++ b/core-js/src/test/scala/coursier/test/compatibility/package.scala @@ -1,7 +1,26 @@ package coursier.test +import scala.concurrent.{Promise, ExecutionContext, Future} +import scala.scalajs.js +import js.Dynamic.{global => g} + package object compatibility { implicit val executionContext = scala.scalajs.concurrent.JSExecutionContext.Implicits.queue + lazy val fs = g.require("fs") + + def textResource(path: String)(implicit ec: ExecutionContext): Future[String] = { + val p = Promise[String]() + + fs.readFile("core/src/test/resources/" + path, "utf-8", { + (err: js.Dynamic, data: js.Dynamic) => + if (err == null) p.success(data.asInstanceOf[String]) + else p.failure(new Exception(err.toString)) + () + }: js.Function2[js.Dynamic, js.Dynamic, Unit]) + + p.future + } + } diff --git a/core-jvm/src/main/scala/coursier/core/Remote.scala b/core-jvm/src/main/scala/coursier/core/Remote.scala index f1c610c71..f9008f2b4 100644 --- a/core-jvm/src/main/scala/coursier/core/Remote.scala +++ b/core-jvm/src/main/scala/coursier/core/Remote.scala @@ -21,15 +21,14 @@ case class ArtifactDownloader(root: String, cache: File, logger: Option[Artifact def artifact(module: Module, version: String, - classifier: String, - `type`: String, + artifact: Artifacts.Artifact, cachePolicy: CachePolicy): EitherT[Task, String, File] = { val relPath = module.organization.split('.').toSeq ++ Seq( module.name, version, - s"${module.name}-$version${Some(classifier).filter(_.nonEmpty).map("-"+_).mkString}.${`type`}" + s"${module.name}-$version${Some(artifact.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${artifact.`type`}" ) val file = (cache /: relPath)(new File(_, _)) @@ -64,19 +63,19 @@ case class ArtifactDownloader(root: String, cache: File, logger: Option[Artifact val in = new BufferedInputStream(url.openStream(), bufferSize) try { - val w = new FileOutputStream(file) + val out = new FileOutputStream(file) try { @tailrec def helper(): Unit = { val read = in.read(b) if (read >= 0) { - w.write(b, 0, read) + out.write(b, 0, read) helper() } } helper() - } finally w.close() + } finally out.close() } finally in.close() logger.foreach(_.downloadedArtifact(urlStr, success = true)) @@ -92,9 +91,24 @@ case class ArtifactDownloader(root: String, cache: File, logger: Option[Artifact EitherT(cachePolicy(locally)(remote)) } - def artifact(dependency: Dependency, - cachePolicy: CachePolicy = CachePolicy.Default): EitherT[Task, String, File] = - artifact(dependency.module, dependency.version, dependency.classifier, dependency.`type`, cachePolicy = cachePolicy) + def artifacts(dependency: Dependency, + project: Project, + cachePolicy: CachePolicy = CachePolicy.Default): Task[Seq[String \/ File]] = { + + val artifacts0 = + dependency.artifacts match { + case s: Artifacts.Sufficient => s.artifacts + case p: Artifacts.WithProject => p.artifacts(project) + } + + val tasks = + artifacts0 .map { artifact0 => + // Important: using version from project, as the one from dependency can be an interval + artifact(dependency.module, project.version, artifact0, cachePolicy = cachePolicy).run + } + + Task.gatherUnordered(tasks) + } } @@ -138,23 +152,15 @@ object Remote { case class Remote(root: String, cache: Option[File] = None, - logger: Option[RemoteLogger] = None) extends Repository { + logger: Option[RemoteLogger] = None) extends MavenRepository { - def find(module: Module, - version: String, - cachePolicy: CachePolicy): EitherT[Task, String, Project] = { + private def get(path: Seq[String], + cachePolicy: CachePolicy): EitherT[Task, String, String] = { - val relPath = - module.organization.split('.').toSeq ++ Seq( - module.name, - version, - s"${module.name}-$version.pom" - ) - - def localFile = { + lazy val localFile = { for { cache0 <- cache.toRightDisjunction("No cache") - f = (cache0 /: relPath)(new File(_, _)) + f = (cache0 /: path)(new File(_, _)) } yield f } @@ -172,7 +178,7 @@ case class Remote(root: String, } def remote = { - val urlStr = root + relPath.mkString("/") + val urlStr = root + path.mkString("/") val url = new URL(urlStr) def log = Task(logger.foreach(_.downloading(urlStr))) @@ -196,7 +202,21 @@ case class Remote(root: String, ) } - val task = cachePolicy.saving(locally)(remote)(save) + EitherT(cachePolicy.saving(locally)(remote)(save)) + } + + def findNoInterval(module: Module, + version: String, + cachePolicy: CachePolicy): EitherT[Task, String, Project] = { + + val path = + module.organization.split('.').toSeq ++ Seq( + module.name, + version, + s"${module.name}-$version.pom" + ) + + val task = get(path, cachePolicy).run .map(eitherStr => for { str <- eitherStr @@ -213,29 +233,13 @@ case class Remote(root: String, name: String, cachePolicy: CachePolicy): EitherT[Task, String, Versions] = { - val relPath = + val path = organization.split('.').toSeq ++ Seq( name, "maven-metadata.xml" ) - def locally = { - ??? - } - - def remote = { - val urlStr = root + relPath.mkString("/") - val url = new URL(urlStr) - - Remote.readFully(url.openStream()) - } - - def save(s: String) = { - // TODO - Task.now(()) - } - - val task = cachePolicy.saving(locally)(remote)(save) + val task = get(path, cachePolicy).run .map(eitherStr => for { str <- eitherStr diff --git a/core-jvm/src/main/scala/coursier/core/package.scala b/core-jvm/src/main/scala/coursier/core/package.scala index 6191c782b..aaf241b9f 100644 --- a/core-jvm/src/main/scala/coursier/core/package.scala +++ b/core-jvm/src/main/scala/coursier/core/package.scala @@ -1,7 +1,5 @@ package coursier -import coursier.core.Resolver.ModuleVersion - import scalaz.EitherT import scalaz.concurrent.Task diff --git a/core-jvm/src/test/scala/coursier/test/compatibility/package.scala b/core-jvm/src/test/scala/coursier/test/compatibility/package.scala index 6196b421c..250a46515 100644 --- a/core-jvm/src/test/scala/coursier/test/compatibility/package.scala +++ b/core-jvm/src/test/scala/coursier/test/compatibility/package.scala @@ -1,6 +1,8 @@ package coursier.test -import scala.concurrent.Future +import coursier.core.Remote + +import scala.concurrent.{ExecutionContext, Future} import scalaz.concurrent.Task package object compatibility { @@ -11,4 +13,11 @@ package object compatibility { def runF: Future[T] = Future.successful(underlying.run) } + def textResource(path: String)(implicit ec: ExecutionContext): Future[String] = Future { + def is = getClass.getClassLoader + .getResource(path).openStream() + + new String(Remote.readFullySync(is), "UTF-8") + } + } diff --git a/core/src/main/scala/coursier/core/ComparableVersion.scala b/core/src/main/scala/coursier/core/ComparableVersion.scala deleted file mode 100644 index fcea0a119..000000000 --- a/core/src/main/scala/coursier/core/ComparableVersion.scala +++ /dev/null @@ -1,205 +0,0 @@ -package coursier.core - -import scala.annotation.tailrec -import coursier.core.compatibility._ - -/** Same kind of ordering as aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java */ -object ComparableVersion { - - sealed trait Item extends Ordered[Item] { - def compare(other: Item): Int = - (this, other) match { - case (Number(a), Number(b)) => a.compare(b) - case (BigNumber(a), BigNumber(b)) => a.compare(b) - case (Number(a), BigNumber(b)) => -b.compare(a) - case (BigNumber(a), Number(b)) => a.compare(b) - case (Qualifier(_, a), Qualifier(_, b)) => a.compare(b) - case (Literal(a), Literal(b)) => a.compareToIgnoreCase(b) - - case _ => - val rel0 = compareToEmpty - val rel1 = other.compareToEmpty - - if (rel0 == rel1) order.compare(other.order) - else rel0.compare(rel1) - } - - def order: Int - def isEmpty: Boolean = compareToEmpty == 0 - def compareToEmpty: Int = 1 - } - - sealed trait Numeric extends Item - case class Number(value: Int) extends Numeric { - val order = 0 - override def compareToEmpty = value.compare(0) - } - case class BigNumber(value: BigInt) extends Numeric { - val order = 0 - override def compareToEmpty = value.compare(0) - } - case class Qualifier(value: String, level: Int) extends Item { - val order = -2 - override def compareToEmpty = level.compare(0) - } - case class Literal(value: String) extends Item { - val order = -1 - override def compareToEmpty = if (value.isEmpty) 0 else 1 - } - - case object Min extends Item { - val order = -8 - override def compareToEmpty = -1 - } - case object Max extends Item { - val order = 8 - } - - val empty = Number(0) - - val qualifiers = Seq[Qualifier]( - Qualifier("alpha", -5), - Qualifier("beta", -4), - Qualifier("milestone", -3), - Qualifier("cr", -2), - Qualifier("rc", -2), - Qualifier("snapshot", -1), - Qualifier("ga", 0), - Qualifier("final", 0), - Qualifier("sp", 1) - ) - - val qualifiersMap = qualifiers.map(q => q.value -> q).toMap - - object Tokenizer { - sealed trait Separator - case object Dot extends Separator - case object Hyphen extends Separator - case object Underscore extends Separator - case object None extends Separator - - def apply(s: String): (Item, Stream[(Separator, Item)]) = { - def parseItem(s: Stream[Char]): (Item, Stream[Char]) = { - if (s.isEmpty || !s.head.letterOrDigit) (empty, s) - else if (s.head.isDigit) { - def digits(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) = - if (s.isEmpty || !s.head.isDigit) (b.result(), s) - else digits(b + s.head, s.tail) - - val (digits0, rem) = digits(new StringBuilder, s) - val item = - if (digits0.length >= 10) BigNumber(BigInt(digits0)) - else Number(digits0.toInt) - - (item, rem) - } else { - assert(s.head.letter) - - def letters(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) = - if (s.isEmpty || !s.head.letter) (b.result().toLowerCase, s) - else letters(b + s.head, s.tail) - - val (letters0, rem) = letters(new StringBuilder, s) - val item = - qualifiersMap.getOrElse(letters0, Literal(letters0)) - - (item, rem) - } - } - - def parseSeparator(s: Stream[Char]): (Separator, Stream[Char]) = { - assert(s.nonEmpty) - - s.head match { - case '.' => (Dot, s.tail) - case '-' => (Hyphen, s.tail) - case '_' => (Underscore, s.tail) - case _ => (None, s) - } - } - - def helper(s: Stream[Char]): Stream[(Separator, Item)] = { - if (s.isEmpty) Stream() - else { - val (sep, rem0) = parseSeparator(s) - val (item, rem) = parseItem(rem0) - - (sep, item) #:: helper(rem) - } - } - - val (first, rem) = parseItem(s.toStream) - (first, helper(rem)) - } - } - - def parse(s: String): ComparableVersion = { - val (first, tokens) = Tokenizer(s) - - def isNumeric(item: Item) = item match { case _: Numeric => true; case _ => false } - - def postProcess(prevIsNumeric: Option[Boolean], item: Item, tokens0: Stream[(Tokenizer.Separator, Item)]): Stream[Item] = { - val tokens = { - var _tokens = tokens0 - - if (isNumeric(item)) { - val nextNonDotZero = _tokens.dropWhile{case (Tokenizer.Dot, n: Numeric) => n.isEmpty; case _ => false } - if (nextNonDotZero.forall(t => t._1 == Tokenizer.Hyphen || ((t._1 == Tokenizer.Dot || t._1 == Tokenizer.None) && !isNumeric(t._2)))) { // Dot && isNumeric(t._2) - _tokens = nextNonDotZero - } - } - - _tokens - } - - def ifFollowedByNumberElse(ifFollowedByNumber: Item, default: Item) = { - val followedByNumber = tokens.headOption - .exists{ case (Tokenizer.None, num: Numeric) if !num.isEmpty => true; case _ => false } - - if (followedByNumber) ifFollowedByNumber - else default - } - - def next = - if (tokens.isEmpty) Stream() - else postProcess(Some(isNumeric(item)), tokens.head._2, tokens.tail) - - item match { - case Literal("min") => Min #:: next - case Literal("max") => Max #:: next - case Literal("a") => - ifFollowedByNumberElse(qualifiersMap("alpha"), item) #:: next - case Literal("b") => - ifFollowedByNumberElse(qualifiersMap("beta"), item) #:: next - case Literal("m") => - ifFollowedByNumberElse(qualifiersMap("milestone"), item) #:: next - case _ => - item #:: next - } - } - - ComparableVersion(postProcess(None, first, tokens).toList) - } - - @tailrec - def listCompare(first: List[Item], second: List[Item]): Int = { - if (first.isEmpty && second.isEmpty) 0 - else if (first.isEmpty) { - assert(second.nonEmpty) - -second.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty) - } else if (second.isEmpty) { - assert(first.nonEmpty) - first.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty) - } else { - val rel = first.head.compare(second.head) - if (rel == 0) listCompare(first.tail, second.tail) - else rel - } - } - -} - -case class ComparableVersion(items: List[ComparableVersion.Item]) extends Ordered[ComparableVersion] { - def compare(other: ComparableVersion) = ComparableVersion.listCompare(items, other.items) - def isEmpty = items.forall(_.isEmpty) -} diff --git a/core/src/main/scala/coursier/core/Definitions.scala b/core/src/main/scala/coursier/core/Definitions.scala index 3ecfaf01f..ee48bd04e 100644 --- a/core/src/main/scala/coursier/core/Definitions.scala +++ b/core/src/main/scala/coursier/core/Definitions.scala @@ -32,20 +32,44 @@ sealed abstract class Scope(val name: String) case class Dependency(module: Module, version: String, scope: Scope, - `type`: String, - classifier: String, + artifacts: Artifacts, exclusions: Set[(String, String)], optional: Boolean) { def moduleVersion = (module, version) } +sealed trait Artifacts + +object Artifacts { + /** + * May become a bit more complicated with Ivy support, + * but should still point at one single artifact. + */ + case class Artifact(`type`: String, + classifier: String) + + sealed trait WithProject extends Artifacts { + def artifacts(project: Project): Seq[Artifact] + } + + sealed trait Sufficient extends Artifacts { + def artifacts: Seq[Artifact] + } + + case class Maven(`type`: String, + classifier: String) extends Sufficient { + def artifacts: Seq[Artifact] = Seq(Artifact(`type`, classifier)) + } +} + case class Project(module: Module, version: String, dependencies: Seq[Dependency], parent: Option[(Module, String)], dependencyManagement: Seq[Dependency], properties: Map[String, String], - profiles: Seq[Profile]) { + profiles: Seq[Profile], + versions: Option[Versions]) { def moduleVersion = (module, version) } @@ -66,3 +90,12 @@ case class Profile(id: String, dependencies: Seq[Dependency], dependencyManagement: Seq[Dependency], properties: Map[String, String]) + +case class Versions(latest: String, + release: String, + available: List[String], + lastUpdated: Option[Versions.DateTime]) + +object Versions { + case class DateTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) +} diff --git a/core/src/main/scala/coursier/core/Parse.scala b/core/src/main/scala/coursier/core/Parse.scala index 083d9010e..4d89c35ea 100644 --- a/core/src/main/scala/coursier/core/Parse.scala +++ b/core/src/main/scala/coursier/core/Parse.scala @@ -29,7 +29,7 @@ object Parse { strTo = s0.drop(commaIdx + 1) from <- if (strFrom.isEmpty) Some(None) else version(strFrom).map(Some(_)) to <- if (strTo.isEmpty) Some(None) else version(strTo).map(Some(_)) - } yield VersionInterval(from.filterNot(_.cmp.isEmpty), to.filterNot(_.cmp.isEmpty), fromIncluded, toIncluded) + } yield VersionInterval(from.filterNot(_.isEmpty), to.filterNot(_.isEmpty), fromIncluded, toIncluded) } def versionConstraint(s: String): Option[VersionConstraint] = { diff --git a/core/src/main/scala/coursier/core/Repository.scala b/core/src/main/scala/coursier/core/Repository.scala index 52ca12a8f..c953d64cc 100644 --- a/core/src/main/scala/coursier/core/Repository.scala +++ b/core/src/main/scala/coursier/core/Repository.scala @@ -1,6 +1,6 @@ package coursier.core -import scalaz.{\/, EitherT} +import scalaz.{-\/, \/-, \/, EitherT} import scalaz.concurrent.Task trait Repository { @@ -36,3 +36,38 @@ object CachePolicy { remote } } + +trait MavenRepository extends Repository { + + def find(module: Module, + version: String, + cachePolicy: CachePolicy): EitherT[Task, String, Project] = { + + Parse.versionInterval(version).filter(_.isValid) match { + case None => findNoInterval(module, version, cachePolicy) + case Some(itv) => + versions(module.organization, module.name, cachePolicy).flatMap { versions0 => + val eitherVersion = { + val release = Version(versions0.release) + if (itv.contains(release)) \/-(versions0.release) + else { + val inInterval = versions0.available.map(Version(_)).filter(itv.contains) + if (inInterval.isEmpty) -\/(s"No version found for $version") + else \/-(inInterval.max.repr) + } + } + + eitherVersion match { + case -\/(reason) => EitherT[Task, String, Project](Task.now(-\/(reason))) + case \/-(version0) => findNoInterval(module, version0, cachePolicy) + .map(_.copy(versions = Some(versions0))) + } + } + } + } + + def findNoInterval(module: Module, + version: String, + cachePolicy: CachePolicy): EitherT[Task, String, Project] + +} diff --git a/core/src/main/scala/coursier/core/Resolver.scala b/core/src/main/scala/coursier/core/Resolver.scala index dbede60c0..0c2bd39da 100644 --- a/core/src/main/scala/coursier/core/Resolver.scala +++ b/core/src/main/scala/coursier/core/Resolver.scala @@ -70,7 +70,9 @@ object Resolver { type DepMgmtKey = (String, String, String) def dependencyManagementKey(dep: Dependency): DepMgmtKey = - (dep.module.organization, dep.module.name, dep.`type`) + dep.artifacts match { + case Artifacts.Maven(type0, _) => (dep.module.organization, dep.module.name, type0) + } def dependencyManagementAdd(m: Map[DepMgmtKey, Dependency], dep: Dependency): Map[DepMgmtKey, Dependency] = { val key = dependencyManagementKey(dep) if (m.contains(key)) m else m + (key -> dep) @@ -119,7 +121,13 @@ object Resolver { name = substituteProps(dep.module.name) ), version = substituteProps(dep.version), - `type` = substituteProps(dep.`type`), + artifacts = dep.artifacts match { + case maven: Artifacts.Maven => + maven.copy( + `type` = substituteProps(maven.`type`), + classifier = substituteProps(maven.classifier) + ) + }, scope = Parse.scope(substituteProps(dep.scope.name)), exclusions = dep.exclusions .map{case (org, name) => (substituteProps(org), substituteProps(name))} @@ -249,49 +257,6 @@ object Resolver { } } - /** - * Intersection of exclusions. A module is excluded by the result if it is excluded - * by both `first` and `second`. - */ - def exclusionsIntersect(first: Set[(String, String)], - second: Set[(String, String)]): Set[(String, String)] = { - - val (firstAll, firstNonAll) = first.partition{case ("*", "*") => true; case _ => false } - val (secondAll, secondNonAll) = second.partition{case ("*", "*") => true; case _ => false } - - if (firstAll.nonEmpty && secondAll.nonEmpty) Set(("*", "*")) - else { - val firstOrgWildcards = firstNonAll.collect{ case ("*", name) => name } - val firstNameWildcards = firstNonAll.collect{ case (org, "*") => org } - val secondOrgWildcards = secondNonAll.collect{ case ("*", name) => name } - val secondNameWildcards = secondNonAll.collect{ case (org, "*") => org } - - val orgWildcards = - (firstOrgWildcards intersect secondOrgWildcards) ++ - (if (secondAll.nonEmpty) firstOrgWildcards else Set.empty) ++ - (if (firstAll.nonEmpty) secondOrgWildcards else Set.empty) - - val nameWildcards = - (firstNameWildcards intersect secondNameWildcards) ++ - (if (secondAll.nonEmpty) firstNameWildcards else Set.empty) ++ - (if (firstAll.nonEmpty) secondNameWildcards else Set.empty) - - val firstRemaining = firstNonAll.filter{ case (org, name) => org != "*" && name != "*" } - val secondRemaining = secondNonAll.filter{ case (org, name) => org != "*" && name != "*" } - - val remaining = - (firstRemaining intersect secondRemaining) ++ - (if (secondAll.nonEmpty) firstRemaining else Set.empty) ++ - (if (firstAll.nonEmpty) secondRemaining else Set.empty) ++ - (if (secondOrgWildcards.nonEmpty) firstRemaining.filter(e => secondOrgWildcards(e._2)) else Set.empty) ++ - (if (firstOrgWildcards.nonEmpty) secondRemaining.filter(e => firstOrgWildcards(e._2)) else Set.empty) ++ - (if (secondNameWildcards.nonEmpty) firstRemaining.filter(e => secondNameWildcards(e._1)) else Set.empty) ++ - (if (firstNameWildcards.nonEmpty) secondRemaining.filter(e => firstNameWildcards(e._1)) else Set.empty) - - orgWildcards.map(name => ("*", name)) ++ nameWildcards.map(org => (org, "*")) ++ remaining - } - } - def withDefaultScope(dep: Dependency): Dependency = if (dep.scope.name.isEmpty) dep.copy(scope = Scope.Compile) else dep @@ -393,7 +358,7 @@ object Resolver { * Transitive dependencies of the current dependencies, according to what there currently is in cache. * No attempt is made to solve version conflicts here. */ - def transitiveDependencies = + def transitiveDependencies: Seq[Dependency] = for { dep <- (dependencies -- conflicts).toList trDep <- finalDependencies0(dep) @@ -404,11 +369,11 @@ object Resolver { * trying to solve version conflicts. Transitive dependencies are calculated with the current cache. * * May contain dependencies added in previous iterations, but no more required. These are filtered below, see - * @newDependencies. + * `newDependencies`. * * Returns a tuple made of the conflicting dependencies, and all the dependencies. */ - def nextDependenciesAndConflicts = { + def nextDependenciesAndConflicts: (Seq[Dependency], Seq[Dependency]) = { merge(dependencies ++ transitiveDependencies) } @@ -436,23 +401,23 @@ object Resolver { missingFromCache.isEmpty && isFixPoint } - private def key(dep: Dependency) = - (dep.module.organization, dep.module.name, dep.scope) + private def eraseVersion(dep: Dependency) = dep.copy(version = "") /** - * Returns a map giving the dependency that brought each of the dependency of the "next" dependency set, - * along with the exclusions that the source dependency adds to it. + * Returns a map giving the dependencies that brought each of the dependency of the "next" dependency set. + * + * The versions of all the dependencies returned are erased (emptied). */ - def reverseDependenciesAndExclusions = { + def reverseDependencies: Map[Dependency, Vector[Dependency]] = { val (updatedConflicts, updatedDeps) = nextDependenciesAndConflicts val trDepsSeq = for { dep <- updatedDeps trDep <- finalDependencies0(dep) - } yield key(trDep) -> (key(dep), trDep.exclusions) + } yield eraseVersion(trDep) -> eraseVersion(dep) - val knownDeps = (updatedDeps ++ updatedConflicts).map(key).toSet + val knownDeps = (updatedDeps ++ updatedConflicts).map(eraseVersion).toSet trDepsSeq .groupBy(_._1) @@ -462,48 +427,38 @@ object Resolver { } /** - * Returns a map, whose keys are the dependencies from the "next" dependency set, - * filtering out those that are no more required, and whose values are the exclusions - * added to them by the dependencies that brought them here. + * Returns dependencies from the "next" dependency set, filtering out + * those that are no more required. + * + * The versions of all the dependencies returned are erased (emptied). */ - def remainingDependenciesAndExclusions = { - val rootDependenciesExclusions = rootDependencies - .map(dep => key(dep) -> dep.exclusions) - .toMap - - type D = (String, String, Scope) + def remainingDependencies: Set[Dependency] = { + val rootDependencies0 = rootDependencies.map(eraseVersion) @tailrec - def helper[T](reverseDeps: Map[D, Vector[(D, T)]]): Map[D, Vector[(D, T)]] = { - val (toRemove, remaining) = reverseDeps.partition(kv => kv._2.isEmpty && !rootDependenciesExclusions.contains(kv._1)) + def helper(reverseDeps: Map[Dependency, Vector[Dependency]]): Map[Dependency, Vector[Dependency]] = { + val (toRemove, remaining) = reverseDeps.partition(kv => kv._2.isEmpty && !rootDependencies0(kv._1)) if (toRemove.isEmpty) reverseDeps - else helper(remaining.mapValues(_.filter(x => remaining.contains(x._1) || rootDependenciesExclusions.contains(x._1))).toList.toMap) + else helper(remaining.mapValues(_.filter(x => remaining.contains(x) || rootDependencies0(x))).toList.toMap) } - val filteredReverseDependenciesAndExclusions = helper(reverseDependenciesAndExclusions) + val filteredReverseDependencies = helper(reverseDependencies) - (rootDependenciesExclusions.keySet ++ filteredReverseDependenciesAndExclusions.keySet) - .toList - .map{case dep => dep -> - (filteredReverseDependenciesAndExclusions.get(dep).map(_.map(_._2)).getOrElse(Nil) ++ rootDependenciesExclusions.get(dep)) - .reduce(exclusionsIntersect) - } - .toMap + rootDependencies0 ++ filteredReverseDependencies.keys } /** * The final next dependency set, stripped of no more required ones. */ - def newDependencies = { - val remainingDeps0 = remainingDependenciesAndExclusions + def newDependencies: Set[Dependency] = { + val remainingDependencies0 = remainingDependencies nextDependenciesAndConflicts._2 - .filter(dep => remainingDeps0.contains(key(dep))) - .map(dep => dep.copy(exclusions = remainingDeps0(key(dep)))) + .filter(dep => remainingDependencies0(eraseVersion(dep))) .toSet } - private def nextNoMissingUnsafe(): Resolution = { + private def nextNoMissingUnsafe: Resolution = { val (newConflicts, _) = nextDependenciesAndConflicts copy(dependencies = newDependencies ++ newConflicts, conflicts = newConflicts.toSet) } @@ -512,9 +467,9 @@ object Resolver { * If no module info is missing, the next state of the resolution, which can be immediately calculated. * Else, the current resolution itself. */ - def nextIfNoMissing(): Resolution = { + def nextIfNoMissing: Resolution = { val missing = missingFromCache - if (missing.isEmpty) nextNoMissingUnsafe() + if (missing.isEmpty) nextNoMissingUnsafe else this } @@ -523,8 +478,8 @@ object Resolver { */ def next(fetchModule: ModuleVersion => EitherT[Task, List[String], (Repository, Project)]): Task[Resolution] = { val missing = missingFromCache - if (missing.isEmpty) Task.now(nextNoMissingUnsafe()) - else fetch(missing.toList, fetchModule).map(_.nextIfNoMissing()) + if (missing.isEmpty) Task.now(nextNoMissingUnsafe) + else fetch(missing.toList, fetchModule).map(_.nextIfNoMissing) } /** @@ -636,20 +591,20 @@ object Resolver { val lookups = modules.map(dep => fetchModule(dep).run.map(dep -> _)) val gatheredLookups = Task.gatherUnordered(lookups, exceptionCancels = true) gatheredLookups.flatMap{ lookupResults => - val errors0 = errors ++ lookupResults.collect{case (mod, -\/(repoErrors)) => mod -> repoErrors} - val newProjects = lookupResults.collect{case (mod, \/-(proj)) => mod -> proj} + val errors0 = errors ++ lookupResults.collect{case (modVer, -\/(repoErrors)) => modVer -> repoErrors} + val newProjects = lookupResults.collect{case (modVer, \/-(proj)) => modVer -> proj} /* * newProjects are project definitions, fresh from the repositories. We need to add * dependency management / inheritance-related bits to them. */ - newProjects.foldLeft(Task.now(copy(errors = errors0))) { case (accTask, (mod, (repo, proj))) => + newProjects.foldLeft(Task.now(copy(errors = errors0))) { case (accTask, (modVer, (repo, proj))) => for { current <- accTask updated <- current.fetch(current.dependencyManagementMissing(proj).toList, fetchModule) proj0 = updated.withDependencyManagement(proj) - } yield updated.copy(projectsCache = updated.projectsCache + (proj0.moduleVersion -> (repo, proj0))) + } yield updated.copy(projectsCache = updated.projectsCache + (modVer -> (repo, proj0))) } } } diff --git a/core/src/main/scala/coursier/core/Version.scala b/core/src/main/scala/coursier/core/Version.scala index 5a970d0bb..e01ebfb77 100644 --- a/core/src/main/scala/coursier/core/Version.scala +++ b/core/src/main/scala/coursier/core/Version.scala @@ -1,104 +1,210 @@ package coursier.core +import scala.annotation.tailrec import coursier.core.compatibility._ -case class Versions(latest: String, - release: String, - available: List[String], - lastUpdated: Option[DateTime]) - -/** Used internally by Resolver */ +/** + * Used internally by Resolver. + * + * Same kind of ordering as aether-util/src/main/java/org/eclipse/aether/util/version/GenericVersion.java + */ case class Version(repr: String) extends Ordered[Version] { - - lazy val cmp = ComparableVersion.parse(repr) - - def compare(other: Version): Int = { - cmp.compare(other.cmp) - } + lazy val items = Version.items(repr) + def compare(other: Version) = Version.listCompare(items, other.items) + def isEmpty = items.forall(_.isEmpty) } -case class VersionInterval(from: Option[Version], - to: Option[Version], - fromIncluded: Boolean, - toIncluded: Boolean) { +object Version { - def isValid: Boolean = { - val fromToOrder = - for { - f <- from - t <- to - cmd = f.compare(t) - } yield cmd < 0 || (cmd == 0 && fromIncluded && toIncluded) + sealed trait Item extends Ordered[Item] { + def compare(other: Item): Int = + (this, other) match { + case (Number(a), Number(b)) => a.compare(b) + case (BigNumber(a), BigNumber(b)) => a.compare(b) + case (Number(a), BigNumber(b)) => -b.compare(a) + case (BigNumber(a), Number(b)) => a.compare(b) + case (Qualifier(_, a), Qualifier(_, b)) => a.compare(b) + case (Literal(a), Literal(b)) => a.compareToIgnoreCase(b) - fromToOrder.forall(x => x) && (from.nonEmpty || !fromIncluded) && (to.nonEmpty || !toIncluded) - } + case _ => + val rel0 = compareToEmpty + val rel1 = other.compareToEmpty - def merge(other: VersionInterval): Option[VersionInterval] = { - val (newFrom, newFromIncluded) = - (from, other.from) match { - case (Some(a), Some(b)) => - val cmp = a.compare(b) - if (cmp < 0) (Some(b), other.fromIncluded) - else if (cmp > 0) (Some(a), fromIncluded) - else (Some(a), fromIncluded && other.fromIncluded) - - case (Some(a), None) => (Some(a), fromIncluded) - case (None, Some(b)) => (Some(b), other.fromIncluded) - case (None, None) => (None, false) + if (rel0 == rel1) order.compare(other.order) + else rel0.compare(rel1) } - val (newTo, newToIncluded) = - (to, other.to) match { - case (Some(a), Some(b)) => - val cmp = a.compare(b) - if (cmp < 0) (Some(a), toIncluded) - else if (cmp > 0) (Some(b), other.toIncluded) - else (Some(a), toIncluded && other.toIncluded) - - case (Some(a), None) => (Some(a), toIncluded) - case (None, Some(b)) => (Some(b), other.toIncluded) - case (None, None) => (None, false) - } - - Some(VersionInterval(newFrom, newTo, newFromIncluded, newToIncluded)) - .filter(_.isValid) + def order: Int + def isEmpty: Boolean = compareToEmpty == 0 + def compareToEmpty: Int = 1 } - def constraint: VersionConstraint = - this match { - case VersionInterval.zero => VersionConstraint.None - case VersionInterval(Some(version), None, true, false) => VersionConstraint.Preferred(version) - case itv => VersionConstraint.Interval(itv) + sealed trait Numeric extends Item + case class Number(value: Int) extends Numeric { + val order = 0 + override def compareToEmpty = value.compare(0) + } + case class BigNumber(value: BigInt) extends Numeric { + val order = 0 + override def compareToEmpty = value.compare(0) + } + case class Qualifier(value: String, level: Int) extends Item { + val order = -2 + override def compareToEmpty = level.compare(0) + } + case class Literal(value: String) extends Item { + val order = -1 + override def compareToEmpty = if (value.isEmpty) 0 else 1 + } + + case object Min extends Item { + val order = -8 + override def compareToEmpty = -1 + } + case object Max extends Item { + val order = 8 + } + + val empty = Number(0) + + val qualifiers = Seq[Qualifier]( + Qualifier("alpha", -5), + Qualifier("beta", -4), + Qualifier("milestone", -3), + Qualifier("cr", -2), + Qualifier("rc", -2), + Qualifier("snapshot", -1), + Qualifier("ga", 0), + Qualifier("final", 0), + Qualifier("sp", 1) + ) + + val qualifiersMap = qualifiers.map(q => q.value -> q).toMap + + object Tokenizer { + sealed trait Separator + case object Dot extends Separator + case object Hyphen extends Separator + case object Underscore extends Separator + case object None extends Separator + + def apply(s: String): (Item, Stream[(Separator, Item)]) = { + def parseItem(s: Stream[Char]): (Item, Stream[Char]) = { + if (s.isEmpty || !s.head.letterOrDigit) (empty, s) + else if (s.head.isDigit) { + def digits(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) = + if (s.isEmpty || !s.head.isDigit) (b.result(), s) + else digits(b + s.head, s.tail) + + val (digits0, rem) = digits(new StringBuilder, s) + val item = + if (digits0.length >= 10) BigNumber(BigInt(digits0)) + else Number(digits0.toInt) + + (item, rem) + } else { + assert(s.head.letter) + + def letters(b: StringBuilder, s: Stream[Char]): (String, Stream[Char]) = + if (s.isEmpty || !s.head.letter) (b.result().toLowerCase, s) + else letters(b + s.head, s.tail) + + val (letters0, rem) = letters(new StringBuilder, s) + val item = + qualifiersMap.getOrElse(letters0, Literal(letters0)) + + (item, rem) + } + } + + def parseSeparator(s: Stream[Char]): (Separator, Stream[Char]) = { + assert(s.nonEmpty) + + s.head match { + case '.' => (Dot, s.tail) + case '-' => (Hyphen, s.tail) + case '_' => (Underscore, s.tail) + case _ => (None, s) + } + } + + def helper(s: Stream[Char]): Stream[(Separator, Item)] = { + if (s.isEmpty) Stream() + else { + val (sep, rem0) = parseSeparator(s) + val (item, rem) = parseItem(rem0) + + (sep, item) #:: helper(rem) + } + } + + val (first, rem) = parseItem(s.toStream) + (first, helper(rem)) + } + } + + def items(repr: String): List[Item] = { + val (first, tokens) = Tokenizer(repr) + + def isNumeric(item: Item) = item match { case _: Numeric => true; case _ => false } + + def postProcess(prevIsNumeric: Option[Boolean], item: Item, tokens0: Stream[(Tokenizer.Separator, Item)]): Stream[Item] = { + val tokens = { + var _tokens = tokens0 + + if (isNumeric(item)) { + val nextNonDotZero = _tokens.dropWhile{case (Tokenizer.Dot, n: Numeric) => n.isEmpty; case _ => false } + if (nextNonDotZero.forall(t => t._1 == Tokenizer.Hyphen || ((t._1 == Tokenizer.Dot || t._1 == Tokenizer.None) && !isNumeric(t._2)))) { // Dot && isNumeric(t._2) + _tokens = nextNonDotZero + } + } + + _tokens + } + + def ifFollowedByNumberElse(ifFollowedByNumber: Item, default: Item) = { + val followedByNumber = tokens.headOption + .exists{ case (Tokenizer.None, num: Numeric) if !num.isEmpty => true; case _ => false } + + if (followedByNumber) ifFollowedByNumber + else default + } + + def next = + if (tokens.isEmpty) Stream() + else postProcess(Some(isNumeric(item)), tokens.head._2, tokens.tail) + + item match { + case Literal("min") => Min #:: next + case Literal("max") => Max #:: next + case Literal("a") => + ifFollowedByNumberElse(qualifiersMap("alpha"), item) #:: next + case Literal("b") => + ifFollowedByNumberElse(qualifiersMap("beta"), item) #:: next + case Literal("m") => + ifFollowedByNumberElse(qualifiersMap("milestone"), item) #:: next + case _ => + item #:: next + } } - def repr: String = Seq( - if (fromIncluded) "[" else "(", - from.map(_.repr).mkString, - ",", - to.map(_.repr).mkString, - if (toIncluded) "]" else ")" - ).mkString -} + postProcess(None, first, tokens).toList + } -object VersionInterval { - val zero = VersionInterval(None, None, fromIncluded = false, toIncluded = false) -} + @tailrec + def listCompare(first: List[Item], second: List[Item]): Int = { + if (first.isEmpty && second.isEmpty) 0 + else if (first.isEmpty) { + assert(second.nonEmpty) + -second.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty) + } else if (second.isEmpty) { + assert(first.nonEmpty) + first.dropWhile(_.isEmpty).headOption.fold(0)(_.compareToEmpty) + } else { + val rel = first.head.compare(second.head) + if (rel == 0) listCompare(first.tail, second.tail) + else rel + } + } -sealed trait VersionConstraint { - def interval: VersionInterval - def repr: String } -object VersionConstraint { - /** Currently treated as minimum... */ - case class Preferred(version: Version) extends VersionConstraint { - def interval: VersionInterval = VersionInterval(Some(version), Option.empty, fromIncluded = true, toIncluded = false) - def repr: String = version.repr - } - case class Interval(interval: VersionInterval) extends VersionConstraint { - def repr: String = interval.repr - } - case object None extends VersionConstraint { - val interval = VersionInterval.zero - def repr: String = "" // Once parsed, "(,)" becomes "" because of this - } -} \ No newline at end of file diff --git a/core/src/main/scala/coursier/core/Versions.scala b/core/src/main/scala/coursier/core/Versions.scala new file mode 100644 index 000000000..ae4877200 --- /dev/null +++ b/core/src/main/scala/coursier/core/Versions.scala @@ -0,0 +1,102 @@ +package coursier.core + +case class VersionInterval(from: Option[Version], + to: Option[Version], + fromIncluded: Boolean, + toIncluded: Boolean) { + + def isValid: Boolean = { + val fromToOrder = + for { + f <- from + t <- to + cmd = f.compare(t) + } yield cmd < 0 || (cmd == 0 && fromIncluded && toIncluded) + + fromToOrder.forall(x => x) && (from.nonEmpty || !fromIncluded) && (to.nonEmpty || !toIncluded) + } + + def contains(version: Version): Boolean = { + val fromCond = + from.forall { from0 => + val cmp = from0.compare(version) + cmp < 0 || cmp == 0 && fromIncluded + } + lazy val toCond = + to.forall { to0 => + val cmp = version.compare(to0) + cmp < 0 || cmp == 0 && toIncluded + } + + fromCond && toCond + } + + def merge(other: VersionInterval): Option[VersionInterval] = { + val (newFrom, newFromIncluded) = + (from, other.from) match { + case (Some(a), Some(b)) => + val cmp = a.compare(b) + if (cmp < 0) (Some(b), other.fromIncluded) + else if (cmp > 0) (Some(a), fromIncluded) + else (Some(a), fromIncluded && other.fromIncluded) + + case (Some(a), None) => (Some(a), fromIncluded) + case (None, Some(b)) => (Some(b), other.fromIncluded) + case (None, None) => (None, false) + } + + val (newTo, newToIncluded) = + (to, other.to) match { + case (Some(a), Some(b)) => + val cmp = a.compare(b) + if (cmp < 0) (Some(a), toIncluded) + else if (cmp > 0) (Some(b), other.toIncluded) + else (Some(a), toIncluded && other.toIncluded) + + case (Some(a), None) => (Some(a), toIncluded) + case (None, Some(b)) => (Some(b), other.toIncluded) + case (None, None) => (None, false) + } + + Some(VersionInterval(newFrom, newTo, newFromIncluded, newToIncluded)) + .filter(_.isValid) + } + + def constraint: VersionConstraint = + this match { + case VersionInterval.zero => VersionConstraint.None + case VersionInterval(Some(version), None, true, false) => VersionConstraint.Preferred(version) + case itv => VersionConstraint.Interval(itv) + } + + def repr: String = Seq( + if (fromIncluded) "[" else "(", + from.map(_.repr).mkString, + ",", + to.map(_.repr).mkString, + if (toIncluded) "]" else ")" + ).mkString +} + +object VersionInterval { + val zero = VersionInterval(None, None, fromIncluded = false, toIncluded = false) +} + +sealed trait VersionConstraint { + def interval: VersionInterval + def repr: String +} +object VersionConstraint { + /** Currently treated as minimum... */ + case class Preferred(version: Version) extends VersionConstraint { + def interval: VersionInterval = VersionInterval(Some(version), Option.empty, fromIncluded = true, toIncluded = false) + def repr: String = version.repr + } + case class Interval(interval: VersionInterval) extends VersionConstraint { + def repr: String = interval.repr + } + case object None extends VersionConstraint { + val interval = VersionInterval.zero + def repr: String = "" // Once parsed, "(,)" becomes "" because of this + } +} \ No newline at end of file diff --git a/core/src/main/scala/coursier/core/Xml.scala b/core/src/main/scala/coursier/core/Xml.scala index 56cff7bfd..621b3272f 100644 --- a/core/src/main/scala/coursier/core/Xml.scala +++ b/core/src/main/scala/coursier/core/Xml.scala @@ -1,7 +1,5 @@ package coursier.core -import coursier.core.compatibility.DateTime - import scalaz._ object Xml { @@ -87,8 +85,7 @@ object Xml { mod, version0, scopeOpt getOrElse defaultScope, - typeOpt getOrElse defaultType, - classifierOpt getOrElse defaultClassifier, + Artifacts.Maven(typeOpt getOrElse defaultType, classifierOpt getOrElse defaultClassifier), exclusions.map(mod => (mod.organization, mod.name)).toSet, optional ) @@ -117,17 +114,18 @@ object Xml { def profile(node: Node): String \/ Profile = { import Scalaz._ + val id = text(node, "id", "Profile ID").getOrElse("") + + val xmlActivationOpt = node.child + .find(_.label == "activation") + val (activeByDefault, activation) = xmlActivationOpt.fold((Option.empty[Boolean], Activation(Nil)))(profileActivation) + + val xmlDeps = node.child + .find(_.label == "dependencies") + .map(_.child.filter(_.label == "dependency")) + .getOrElse(Seq.empty) + for { - id <- text(node, "id", "Profile ID") - - xmlActivationOpt = node.child - .find(_.label == "activation") - (activeByDefault, activation) = xmlActivationOpt.fold((Option.empty[Boolean], Activation(Nil)))(profileActivation) - - xmlDeps = node.child - .find(_.label == "dependencies") - .map(_.child.filter(_.label == "dependency")) - .getOrElse(Seq.empty) deps <- xmlDeps.toList.traverseU(dependency) xmlDepMgmts = node.child @@ -141,6 +139,7 @@ object Xml { .find(_.label == "properties") .map(_.child.collect{case elem if elem.isElement => elem}) .getOrElse(Seq.empty) + properties <- { import Scalaz._ xmlProperties.toList.traverseU(property) @@ -210,7 +209,8 @@ object Xml { parentModuleOpt.map((_, parentVersionOpt.getOrElse(""))), depMgmts, properties.toMap, - profiles + profiles, + None ) } @@ -238,7 +238,7 @@ object Xml { lastUpdatedOpt = text(xmlVersioning, "lastUpdated", "Last update date and time") .toOption .filter(s => s.length == 14 && s.forall(_.isDigit)) - .map(s => DateTime( + .map(s => Versions.DateTime( s.substring(0, 4).toInt, s.substring(4, 6).toInt, s.substring(6, 8).toInt, diff --git a/core/src/main/scala/coursier/core/compatibility/DateTime.scala b/core/src/main/scala/coursier/core/compatibility/DateTime.scala deleted file mode 100644 index 9b7e727aa..000000000 --- a/core/src/main/scala/coursier/core/compatibility/DateTime.scala +++ /dev/null @@ -1,263 +0,0 @@ -package coursier.core.compatibility - -// Cut-n-pasted from http4s 0.7.0 -// Replaced Writer usages with StringBuilder - -final class DateTime private (val year: Int, // the year - val month: Int, // the month of the year. January is 1. - val day: Int, // the day of the month. The first day is 1. - val hour: Int, // the hour of the day. The first hour is 0. - val minute: Int, // the minute of the hour. The first minute is 0. - val second: Int, // the second of the minute. The first second is 0. - val weekday: Int, // the day of the week. Sunday is 0. - val clicks: Long) // milliseconds since January 1, 1970, 00:00:00 GMT - extends Ordered[DateTime] with Product6[Int, Int, Int, Int, Int, Int] { - import DateTime.StringBuilderExtensions - - /** - * The day of the week as a 3 letter abbreviation: - * `Sun`, `Mon`, `Tue`, `Wed`, `Thu`, `Fri` or `Sat` - */ - def weekdayStr: String = DateTime.WEEKDAYS(weekday) - - /** - * The day of the month as a 3 letter abbreviation: - * `Jan`, `Feb`, `Mar`, `Apr`, `May`, `Jun`, `Jul`, `Aug`, `Sep`, `Oct`, `Nov` or `Dec` - */ - def monthStr: String = DateTime.MONTHS(month - 1) - - /** - * Creates a new `DateTime` that represents the point in time the given number of ms later. - */ - def +(millis: Long): DateTime = DateTime(clicks + millis) - - /** - * Creates a new `DateTime` that represents the point in time the given number of ms earlier. - */ - def -(millis: Long): DateTime = DateTime(clicks - millis) - - /** - * `yyyy-mm-ddThh:mm:ss` - */ - override def toString = toIsoDateTimeString - - /** - * `yyyy-mm-dd` - */ - def renderIsoDate(w: StringBuilder): w.type = { - put_##(put_##(w << year << '-', month) << '-', day) - w - } - - /** - * `yyyy-mm-dd` - */ - def toIsoDateString = { val w = new StringBuilder; renderIsoDate(w).result() } - - /** - * `yyyy-mm-ddThh:mm:ss` - */ - def renderIsoDateTimeString(w: StringBuilder): w.type = { - put_##(put_##(put_##(renderIsoDate(w) << 'T', hour) << ':', minute) << ':', second) - w - } - - /** - * `yyyy-mm-ddThh:mm:ss` - */ - def toIsoDateTimeString = { val w = new StringBuilder; renderIsoDateTimeString(w).result() } - - /** - * `yyyy-mm-dd hh:mm:ss` - */ - def renderIsoLikeDateTimeString(w: StringBuilder): w.type = { - put_##(put_##(put_##(renderIsoDate(w) << ' ', hour) << ':', minute) << ':', second) - w - } - - /** - * `yyyy-mm-dd hh:mm:ss` - */ - def toIsoLikeDateTimeString = { val w = new StringBuilder; renderIsoLikeDateTimeString(w).result() } - - /** - * RFC1123 date string, e.g. `Sun, 06 Nov 1994 08:49:37 GMT` - */ - def renderRfc1123DateTimeString(w: StringBuilder): w.type = { - w ++= weekdayStr - w ++= ", " - put_##(put_##(put_##(put_##(w << weekdayStr << ',' << ' ', day) << ' ' << monthStr << ' ' << year << ' ', hour) << ':', minute) << ':', second) << " GMT" - w - } - - /** - * RFC1123 date string, e.g. `Sun, 06 Nov 1994 08:49:37 GMT` - */ - def toRfc1123DateTimeString = { val w = new StringBuilder; renderRfc1123DateTimeString(w).result() } - - private def put_##(w: StringBuilder, i: Int): w.type = { - w << (i / 10 + '0').toChar << (i % 10 + '0').toChar - w - } - - def compare(that: DateTime): Int = math.signum(clicks - that.clicks).toInt - - override def hashCode() = clicks.## - - override def equals(obj: Any) = obj match { - case x: DateTime ⇒ x.clicks == clicks - case _ ⇒ false - } - - override def _1: Int = year - override def _2: Int = month - override def _3: Int = day - override def _4: Int = hour - override def _5: Int = minute - override def _6: Int = second - - override def canEqual(that: Any): Boolean = that.isInstanceOf[DateTime] -} - -object DateTime { - - private implicit class StringBuilderExtensions(val b: StringBuilder) extends AnyVal { - def <<(s: String): b.type = b ++= s - def <<(c: Char): b.type = b += c - def <<(n: Int): b.type = b ++= n.toString - } - - val WEEKDAYS = Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") - val MONTHS = Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") - val MinValue = DateTime(1800, 1, 1) - val MaxValue = DateTime(2199, 12, 31, 23, 59, 59) - - /** - * Creates a new `DateTime` with the given properties. - * Note that this implementation discards milliseconds (i.e. rounds down to full seconds). - */ - def apply(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0): DateTime = { - require(1800 <= year && year <= 9999, "year must be >= 1800 and <= 9999") - require(1 <= month && month <= 12, "month must be >= 1 and <= 12") - require(1 <= day && day <= 31, "day must be >= 1 and <= 31") - require(0 <= hour && hour <= 23, "hour must be >= 0 and <= 23") - require(0 <= minute && minute <= 59, "minute_ must be >= 0 and <= 59") - require(0 <= second && second <= 59, "second must be >= 0 and <= 59") - - // compute yearday from month/monthday - val m = month - 1 - var d = (m % 7) * 30 + (m % 7 + 1) / 2 + day - val isLeap = ((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0) - if (m >= 7) d += 214 - if (d >= 61) d -= 1 // skip non-existent Feb 30 - if (!isLeap && (d >= 60)) d -= 1 // skip non-existent Feb 29 - - // convert year/yearday to days since Jan 1, 1970, 00:00:00 - val y = year - 1 - d += y * 365 + y / 4 - y / 100 + y / 400 - val dn = d - (1969 * 365 + 492 - 19 + 4) - val c = (dn - 1) * 86400L + hour * 3600L + minute * 60L + second // seconds since Jan 1, 1970, 00:00:00 - - new DateTime(year, month, day, hour, minute, second, weekday = d % 7, clicks = c * 1000) - } - - /** - * Creates a new `DateTime` from the number of milli seconds - * since the start of "the epoch", namely January 1, 1970, 00:00:00 GMT. - * Note that this implementation discards milliseconds (i.e. rounds down to full seconds). - */ - def apply(clicks: Long): DateTime = { - require(DateTime.MinValue.clicks <= clicks && clicks <= DateTime.MaxValue.clicks, - "DateTime value must be >= " + DateTime.MinValue + " and <= " + DateTime.MaxValue) - - // based on a fast RFC1123 implementation (C) 2000 by Tim Kientzle - val c = clicks - clicks % 1000 - - // compute day number, seconds since beginning of day - var s = c - if (s >= 0) s /= 1000 // seconds since 1 Jan 1970 - else s = (s - 999) / 1000 // floor(sec/1000) - - var dn = (s / 86400).toInt - s %= 86400 // positive seconds since beginning of day - if (s < 0) { s += 86400; dn -= 1 } - dn += 1969 * 365 + 492 - 19 + 4 // days since "1 Jan, year 1" - - // convert days since 1 Jan, year 1 to year/yearday - var y = 400 * (dn / 146097).toInt + 1 - var d = dn % 146097 - if (d == 146096) { y += 399; d = 365 } // last year of 400 is long - else { - y += 100 * (d / 36524) - d %= 36524 - y += 4 * (d / 1461) - d %= 1461 - if (d == 1460) { y += 3; d = 365 } // last year out of 4 is long - else { - y += d / 365 - d %= 365 - } - } - - val isLeap = ((y % 4 == 0) && !(y % 100 == 0)) || (y % 400 == 0) - - // compute month/monthday from year/yearday - if (!isLeap && (d >= 59)) d += 1 // skip non-existent Feb 29 - if (d >= 60) d += 1 // skip non-existent Feb 30 - var mon = ((d % 214) / 61) * 2 + ((d % 214) % 61) / 31 - if (d > 213) mon += 7 - d = ((d % 214) % 61) % 31 + 1 - - // convert second to hour/min/sec - var m = (s / 60).toInt - val h = m / 60 - m %= 60 - s %= 60 - val w = (dn + 1) % 7 // day of week, 0==Sun - - new DateTime(year = y, month = mon + 1, day = d, hour = h, minute = m, second = s.toInt, weekday = w, clicks = c) - } - - /** - * Creates a new `DateTime` instance for the current point in time. - * Note that this implementation discards milliseconds (i.e. rounds down to full seconds). - */ - def now: DateTime = apply(System.currentTimeMillis) - - /** - * Creates a new DateTime instance from the given String, - * if it adheres to the format `yyyy-mm-ddThh:mm:ss[.SSSZ]`. - * Note that this implementation discards milliseconds (i.e. rounds down to full seconds). - */ - def fromIsoDateTimeString(string: String): Option[DateTime] = { - def c(ix: Int) = string.charAt(ix) - def isDigit(c: Char) = '0' <= c && c <= '9' - def i(ix: Int) = { - val x = c(ix) - require(isDigit(x)) - x - '0' - } - def check(len: Int): Boolean = - len match { - case 19 ⇒ c(4) == '-' && c(7) == '-' && c(10) == 'T' && c(13) == ':' && c(16) == ':' - case 24 ⇒ check(19) && c(19) == '.' && isDigit(c(20)) && isDigit(c(21)) && isDigit(c(22)) && c(23) == 'Z' - case _ ⇒ false - } - if (check(string.length)) { - try { - val year = i(0) * 1000 + i(1) * 100 + i(2) * 10 + i(3) - val month = i(5) * 10 + i(6) - val day = i(8) * 10 + i(9) - val hour = i(11) * 10 + i(12) - val min = i(14) * 10 + i(15) - val sec = i(17) * 10 + i(18) - Some(DateTime(year, month, day, hour, min, sec)) - } catch { case _: IllegalArgumentException ⇒ None } - } else None - } - - val UnixEpoch = DateTime(0L) - - def unapply(dt: DateTime): Option[(Int, Int, Int, Int, Int, Int)] = - Some((dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)) -} \ No newline at end of file diff --git a/core/src/main/scala/coursier/package.scala b/core/src/main/scala/coursier/package.scala index 0ac3d7d90..01a2083b5 100644 --- a/core/src/main/scala/coursier/package.scala +++ b/core/src/main/scala/coursier/package.scala @@ -7,12 +7,21 @@ package object coursier { object Dependency { def apply(module: Module, version: String, - scope: Scope = Scope.Other(""), // Subsituted by Resolver with its own default scope (compile) - `type`: String = "jar", - classifier: String = "", + scope: Scope = Scope.Other(""), // Substituted by Resolver with its own default scope (compile) + artifacts: Artifacts = Artifacts.Maven(), exclusions: Set[(String, String)] = Set.empty, optional: Boolean = false): Dependency = - core.Dependency(module, version, scope, `type`, classifier, exclusions, optional) + core.Dependency(module, version, scope, artifacts, exclusions, optional) + } + + type Artifacts = core.Artifacts + object Artifacts { + type Maven = core.Artifacts.Maven + object Maven { + def apply(`type`: String = "jar", + classifier: String = ""): Maven = + core.Artifacts.Maven(`type`, classifier) + } } type Project = core.Project @@ -23,8 +32,9 @@ package object coursier { parent: Option[ModuleVersion] = None, dependencyManagement: Seq[Dependency] = Seq.empty, properties: Map[String, String] = Map.empty, - profiles: Seq[Profile] = Seq.empty): Project = - core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles) + profiles: Seq[Profile] = Seq.empty, + versions: Option[core.Versions] = None): Project = + core.Project(module, version, dependencies, parent, dependencyManagement, properties, profiles, versions) } type Profile = core.Profile diff --git a/core/src/test/resources/resolutions/com.github.alexarchambault:argonaut-shapeless_6.1_2.11:0.2.0 b/core/src/test/resources/resolutions/com.github.alexarchambault:argonaut-shapeless_6.1_2.11:0.2.0 new file mode 100644 index 000000000..5f0ead3af --- /dev/null +++ b/core/src/test/resources/resolutions/com.github.alexarchambault:argonaut-shapeless_6.1_2.11:0.2.0 @@ -0,0 +1,10 @@ +com.chuusai:shapeless_2.11:jar:2.2.0 +com.github.alexarchambault:argonaut-shapeless_6.1_2.11:jar:0.2.0 +com.github.julien-truffaut:monocle-core_2.11:jar:1.1.0 +com.github.julien-truffaut:monocle-macro_2.11:jar:1.1.0 +io.argonaut:argonaut_2.11:jar:6.1 +org.scala-lang.modules:scala-parser-combinators_2.11:jar:1.0.2 +org.scala-lang.modules:scala-xml_2.11:jar:1.0.2 +org.scala-lang:scala-library:jar:2.11.6 +org.scala-lang:scala-reflect:jar:2.11.6 +org.scalaz:scalaz-core_2.11:jar:7.1.1 diff --git a/core/src/test/resources/resolutions/com.github.alexarchambault:argonaut-shapeless_6.1_2.11:0.2.0.jcabi b/core/src/test/resources/resolutions/com.github.alexarchambault:argonaut-shapeless_6.1_2.11:0.2.0.jcabi new file mode 100644 index 000000000..5f0ead3af --- /dev/null +++ b/core/src/test/resources/resolutions/com.github.alexarchambault:argonaut-shapeless_6.1_2.11:0.2.0.jcabi @@ -0,0 +1,10 @@ +com.chuusai:shapeless_2.11:jar:2.2.0 +com.github.alexarchambault:argonaut-shapeless_6.1_2.11:jar:0.2.0 +com.github.julien-truffaut:monocle-core_2.11:jar:1.1.0 +com.github.julien-truffaut:monocle-macro_2.11:jar:1.1.0 +io.argonaut:argonaut_2.11:jar:6.1 +org.scala-lang.modules:scala-parser-combinators_2.11:jar:1.0.2 +org.scala-lang.modules:scala-xml_2.11:jar:1.0.2 +org.scala-lang:scala-library:jar:2.11.6 +org.scala-lang:scala-reflect:jar:2.11.6 +org.scalaz:scalaz-core_2.11:jar:7.1.1 diff --git a/core/src/test/resources/resolutions/org.apache.spark:spark-core_2.11:1.3.1 b/core/src/test/resources/resolutions/org.apache.spark:spark-core_2.11:1.3.1 new file mode 100644 index 000000000..d51bfae70 --- /dev/null +++ b/core/src/test/resources/resolutions/org.apache.spark:spark-core_2.11:1.3.1 @@ -0,0 +1,128 @@ +aopalliance:aopalliance:jar:1.0 +asm:asm:jar:3.1 +com.clearspring.analytics:stream:jar:2.7.0 +com.esotericsoftware.kryo:kryo:jar:2.21 +com.esotericsoftware.minlog:minlog:jar:1.2 +com.esotericsoftware.reflectasm:reflectasm:jar:shaded:1.07 +com.fasterxml.jackson.core:jackson-annotations:jar:2.4.4 +com.fasterxml.jackson.core:jackson-core:jar:2.4.4 +com.fasterxml.jackson.core:jackson-databind:jar:2.4.4 +com.fasterxml.jackson.module:jackson-module-scala_2.11:jar:2.4.4 +com.google.code.findbugs:jsr305:jar:2.0.1 +com.google.guava:guava:jar:14.0.1 +com.google.inject:guice:jar:3.0 +com.google.protobuf:protobuf-java:jar:2.5.0 +com.ning:compress-lzf:jar:1.0.0 +com.sun.jersey.contribs:jersey-guice:jar:1.9 +com.sun.jersey.jersey-test-framework:jersey-test-framework-core:jar:1.9 +com.sun.jersey.jersey-test-framework:jersey-test-framework-grizzly2:jar:1.9 +com.sun.jersey:jersey-client:jar:1.9 +com.sun.jersey:jersey-core:jar:1.9 +com.sun.jersey:jersey-grizzly2:jar:1.9 +com.sun.jersey:jersey-json:jar:1.9 +com.sun.jersey:jersey-server:jar:1.9 +com.sun.xml.bind:jaxb-impl:jar:2.2.3-1 +com.thoughtworks.paranamer:paranamer:jar:2.6 +com.twitter:chill-java:jar:0.5.0 +com.twitter:chill_2.11:jar:0.5.0 +com.typesafe:config:jar:1.2.1 +commons-beanutils:commons-beanutils-core:jar:1.8.0 +commons-beanutils:commons-beanutils:jar:1.7.0 +commons-cli:commons-cli:jar:1.2 +commons-codec:commons-codec:jar:1.4 +commons-collections:commons-collections:jar:3.2.1 +commons-configuration:commons-configuration:jar:1.6 +commons-digester:commons-digester:jar:1.8 +commons-httpclient:commons-httpclient:jar:3.1 +commons-io:commons-io:jar:2.4 +commons-lang:commons-lang:jar:2.5 +commons-logging:commons-logging:jar:1.1.1 +commons-net:commons-net:jar:3.1 +io.dropwizard.metrics:metrics-core:jar:3.1.0 +io.dropwizard.metrics:metrics-graphite:jar:3.1.0 +io.dropwizard.metrics:metrics-json:jar:3.1.0 +io.dropwizard.metrics:metrics-jvm:jar:3.1.0 +io.netty:netty-all:jar:4.0.23.Final +io.netty:netty:jar:3.8.0.Final +javax.activation:activation:jar:1.1 +javax.inject:javax.inject:jar:1 +javax.servlet:javax.servlet-api:jar:3.0.1 +javax.xml.bind:jaxb-api:jar:2.2.2 +jline:jline:jar:0.9.94 +log4j:log4j:jar:1.2.17 +net.java.dev.jets3t:jets3t:jar:0.7.1 +net.jpountz.lz4:lz4:jar:1.2.0 +net.sf.py4j:py4j:jar:0.8.2.1 +org.apache.avro:avro:jar:1.7.4 +org.apache.commons:commons-compress:jar:1.4.1 +org.apache.commons:commons-lang3:jar:3.3.2 +org.apache.commons:commons-math3:jar:3.1.1 +org.apache.commons:commons-math:jar:2.1 +org.apache.curator:curator-client:jar:2.4.0 +org.apache.curator:curator-framework:jar:2.4.0 +org.apache.curator:curator-recipes:jar:2.4.0 +org.apache.hadoop:hadoop-annotations:jar:2.2.0 +org.apache.hadoop:hadoop-auth:jar:2.2.0 +org.apache.hadoop:hadoop-client:jar:2.2.0 +org.apache.hadoop:hadoop-common:jar:2.2.0 +org.apache.hadoop:hadoop-hdfs:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-app:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-common:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-core:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-jobclient:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-shuffle:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-api:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-client:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-common:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-server-common:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-server-nodemanager:jar:2.2.0 +org.apache.ivy:ivy:jar:2.4.0 +org.apache.mesos:mesos:jar:shaded-protobuf:0.21.0 +org.apache.spark:spark-core_2.11:jar:1.3.1 +org.apache.spark:spark-network-common_2.11:jar:1.3.1 +org.apache.spark:spark-network-shuffle_2.11:jar:1.3.1 +org.apache.zookeeper:zookeeper:jar:3.4.5 +org.codehaus.jackson:jackson-core-asl:jar:1.8.8 +org.codehaus.jackson:jackson-jaxrs:jar:1.8.3 +org.codehaus.jackson:jackson-mapper-asl:jar:1.8.8 +org.codehaus.jackson:jackson-xc:jar:1.8.3 +org.codehaus.jettison:jettison:jar:1.1 +org.eclipse.jetty.orbit:javax.servlet:jar:3.0.0.v201112011016 +org.glassfish.external:management-api:jar:3.0.0-b012 +org.glassfish.gmbal:gmbal-api-only:jar:3.0.0-b023 +org.glassfish.grizzly:grizzly-framework:jar:2.1.2 +org.glassfish.grizzly:grizzly-http-server:jar:2.1.2 +org.glassfish.grizzly:grizzly-http-servlet:jar:2.1.2 +org.glassfish.grizzly:grizzly-http:jar:2.1.2 +org.glassfish.grizzly:grizzly-rcm:jar:2.1.2 +org.glassfish:javax.servlet:jar:3.1 +org.json4s:json4s-ast_2.11:jar:3.2.10 +org.json4s:json4s-core_2.11:jar:3.2.10 +org.json4s:json4s-jackson_2.11:jar:3.2.10 +org.mortbay.jetty:jetty-util:jar:6.1.26 +org.objenesis:objenesis:jar:1.2 +org.roaringbitmap:RoaringBitmap:jar:0.4.5 +org.scala-lang.modules:scala-parser-combinators_2.11:jar:1.0.1 +org.scala-lang.modules:scala-xml_2.11:jar:1.0.1 +org.scala-lang:scala-compiler:jar:2.11.0 +org.scala-lang:scala-library:jar:2.11.2 +org.scala-lang:scala-reflect:jar:2.11.2 +org.scala-lang:scalap:jar:2.11.0 +org.slf4j:jcl-over-slf4j:jar:1.7.10 +org.slf4j:jul-to-slf4j:jar:1.7.10 +org.slf4j:slf4j-api:jar:1.7.10 +org.slf4j:slf4j-log4j12:jar:1.7.10 +org.spark-project.akka:akka-actor_2.11:jar:2.3.4-spark +org.spark-project.akka:akka-remote_2.11:jar:2.3.4-spark +org.spark-project.akka:akka-slf4j_2.11:jar:2.3.4-spark +org.spark-project.protobuf:protobuf-java:jar:2.5.0-spark +org.spark-project.spark:unused:jar:1.0.0 +org.spark-project:pyrolite:jar:2.0.1 +org.tachyonproject:tachyon-client:jar:0.5.0 +org.tachyonproject:tachyon:jar:0.5.0 +org.tukaani:xz:jar:1.0 +org.uncommons.maths:uncommons-maths:jar:1.2.2a +org.xerial.snappy:snappy-java:jar:1.1.1.6 +oro:oro:jar:2.0.8 +stax:stax-api:jar:1.0.1 +xmlenc:xmlenc:jar:0.52 diff --git a/core/src/test/resources/resolutions/org.apache.spark:spark-core_2.11:1.3.1.jcabi b/core/src/test/resources/resolutions/org.apache.spark:spark-core_2.11:1.3.1.jcabi new file mode 100644 index 000000000..29f0ba550 --- /dev/null +++ b/core/src/test/resources/resolutions/org.apache.spark:spark-core_2.11:1.3.1.jcabi @@ -0,0 +1,127 @@ +aopalliance:aopalliance:jar:1.0 +asm:asm:jar:3.1 +com.clearspring.analytics:stream:jar:2.7.0 +com.esotericsoftware.kryo:kryo:jar:2.21 +com.esotericsoftware.minlog:minlog:jar:1.2 +com.esotericsoftware.reflectasm:reflectasm:jar:shaded:1.07 +com.fasterxml.jackson.core:jackson-annotations:jar:2.4.0 +com.fasterxml.jackson.core:jackson-core:jar:2.4.4 +com.fasterxml.jackson.core:jackson-databind:jar:2.4.4 +com.fasterxml.jackson.module:jackson-module-scala_2.11:jar:2.4.4 +com.google.code.findbugs:jsr305:jar:1.3.9 +com.google.guava:guava:jar:14.0.1 +com.google.inject:guice:jar:3.0 +com.google.protobuf:protobuf-java:jar:2.5.0 +com.ning:compress-lzf:jar:1.0.0 +com.sun.jersey.contribs:jersey-guice:jar:1.9 +com.sun.jersey.jersey-test-framework:jersey-test-framework-core:jar:1.9 +com.sun.jersey.jersey-test-framework:jersey-test-framework-grizzly2:jar:1.9 +com.sun.jersey:jersey-client:jar:1.9 +com.sun.jersey:jersey-core:jar:1.9 +com.sun.jersey:jersey-grizzly2:jar:1.9 +com.sun.jersey:jersey-json:jar:1.9 +com.sun.jersey:jersey-server:jar:1.9 +com.sun.xml.bind:jaxb-impl:jar:2.2.3-1 +com.thoughtworks.paranamer:paranamer:jar:2.6 +com.twitter:chill-java:jar:0.5.0 +com.twitter:chill_2.11:jar:0.5.0 +com.typesafe:config:jar:1.2.1 +commons-beanutils:commons-beanutils-core:jar:1.8.0 +commons-beanutils:commons-beanutils:jar:1.7.0 +commons-cli:commons-cli:jar:1.2 +commons-codec:commons-codec:jar:1.3 +commons-collections:commons-collections:jar:3.2.1 +commons-configuration:commons-configuration:jar:1.6 +commons-digester:commons-digester:jar:1.8 +commons-httpclient:commons-httpclient:jar:3.1 +commons-io:commons-io:jar:2.1 +commons-lang:commons-lang:jar:2.5 +commons-logging:commons-logging:jar:1.1.1 +commons-net:commons-net:jar:2.2 +io.dropwizard.metrics:metrics-core:jar:3.1.0 +io.dropwizard.metrics:metrics-graphite:jar:3.1.0 +io.dropwizard.metrics:metrics-json:jar:3.1.0 +io.dropwizard.metrics:metrics-jvm:jar:3.1.0 +io.netty:netty-all:jar:4.0.23.Final +io.netty:netty:jar:3.8.0.Final +javax.activation:activation:jar:1.1 +javax.inject:javax.inject:jar:1 +javax.servlet:javax.servlet-api:jar:3.0.1 +javax.xml.bind:jaxb-api:jar:2.2.2 +jline:jline:jar:0.9.94 +log4j:log4j:jar:1.2.17 +net.java.dev.jets3t:jets3t:jar:0.7.1 +net.jpountz.lz4:lz4:jar:1.2.0 +net.sf.py4j:py4j:jar:0.8.2.1 +org.apache.avro:avro:jar:1.7.4 +org.apache.commons:commons-compress:jar:1.4.1 +org.apache.commons:commons-lang3:jar:3.3.2 +org.apache.commons:commons-math3:jar:3.1.1 +org.apache.commons:commons-math:jar:2.1 +org.apache.curator:curator-client:jar:2.4.0 +org.apache.curator:curator-framework:jar:2.4.0 +org.apache.curator:curator-recipes:jar:2.4.0 +org.apache.hadoop:hadoop-annotations:jar:2.2.0 +org.apache.hadoop:hadoop-auth:jar:2.2.0 +org.apache.hadoop:hadoop-client:jar:2.2.0 +org.apache.hadoop:hadoop-common:jar:2.2.0 +org.apache.hadoop:hadoop-hdfs:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-app:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-common:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-core:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-jobclient:jar:2.2.0 +org.apache.hadoop:hadoop-mapreduce-client-shuffle:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-api:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-client:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-common:jar:2.2.0 +org.apache.hadoop:hadoop-yarn-server-common:jar:2.2.0 +org.apache.ivy:ivy:jar:2.4.0 +org.apache.mesos:mesos:jar:shaded-protobuf:0.21.0 +org.apache.spark:spark-core_2.11:jar:1.3.1 +org.apache.spark:spark-network-common_2.11:jar:1.3.1 +org.apache.spark:spark-network-shuffle_2.11:jar:1.3.1 +org.apache.zookeeper:zookeeper:jar:3.4.5 +org.codehaus.jackson:jackson-core-asl:jar:1.8.8 +org.codehaus.jackson:jackson-jaxrs:jar:1.8.3 +org.codehaus.jackson:jackson-mapper-asl:jar:1.8.8 +org.codehaus.jackson:jackson-xc:jar:1.8.3 +org.codehaus.jettison:jettison:jar:1.1 +org.eclipse.jetty.orbit:javax.servlet:jar:3.0.0.v201112011016 +org.glassfish.external:management-api:jar:3.0.0-b012 +org.glassfish.gmbal:gmbal-api-only:jar:3.0.0-b023 +org.glassfish.grizzly:grizzly-framework:jar:2.1.2 +org.glassfish.grizzly:grizzly-http-server:jar:2.1.2 +org.glassfish.grizzly:grizzly-http-servlet:jar:2.1.2 +org.glassfish.grizzly:grizzly-http:jar:2.1.2 +org.glassfish.grizzly:grizzly-rcm:jar:2.1.2 +org.glassfish:javax.servlet:jar:3.1 +org.json4s:json4s-ast_2.11:jar:3.2.10 +org.json4s:json4s-core_2.11:jar:3.2.10 +org.json4s:json4s-jackson_2.11:jar:3.2.10 +org.mortbay.jetty:jetty-util:jar:6.1.26 +org.objenesis:objenesis:jar:1.2 +org.roaringbitmap:RoaringBitmap:jar:0.4.5 +org.scala-lang.modules:scala-parser-combinators_2.11:jar:1.0.1 +org.scala-lang.modules:scala-xml_2.11:jar:1.0.1 +org.scala-lang:scala-compiler:jar:2.11.0 +org.scala-lang:scala-library:jar:2.11.2 +org.scala-lang:scala-reflect:jar:2.11.2 +org.scala-lang:scalap:jar:2.11.0 +org.slf4j:jcl-over-slf4j:jar:1.7.10 +org.slf4j:jul-to-slf4j:jar:1.7.10 +org.slf4j:slf4j-api:jar:1.7.10 +org.slf4j:slf4j-log4j12:jar:1.7.10 +org.spark-project.akka:akka-actor_2.11:jar:2.3.4-spark +org.spark-project.akka:akka-remote_2.11:jar:2.3.4-spark +org.spark-project.akka:akka-slf4j_2.11:jar:2.3.4-spark +org.spark-project.protobuf:protobuf-java:jar:2.5.0-spark +org.spark-project.spark:unused:jar:1.0.0 +org.spark-project:pyrolite:jar:2.0.1 +org.tachyonproject:tachyon-client:jar:0.5.0 +org.tachyonproject:tachyon:jar:0.5.0 +org.tukaani:xz:jar:1.0 +org.uncommons.maths:uncommons-maths:jar:1.2.2a +org.xerial.snappy:snappy-java:jar:1.1.1.6 +oro:oro:jar:2.0.8 +stax:stax-api:jar:1.0.1 +xmlenc:xmlenc:jar:0.52 diff --git a/core/src/test/scala/coursier/test/CentralTests.scala b/core/src/test/scala/coursier/test/CentralTests.scala index bfd0abe9e..1d58286d6 100644 --- a/core/src/test/scala/coursier/test/CentralTests.scala +++ b/core/src/test/scala/coursier/test/CentralTests.scala @@ -12,26 +12,41 @@ object CentralTests extends TestSuite { repository.mavenCentral ) + def repr(dep: Dependency) = { + val (type0, classifier) = dep.artifacts match { + case maven: Artifacts.Maven => (maven.`type`, maven.classifier) + } + s"${dep.module.organization}:${dep.module.name}:$type0:${Some(classifier).filter(_.nonEmpty).map(_+":").mkString}${dep.version}" + } + + def resolutionCheck(module: Module, version: String) = + async { + val expected = await(textResource(s"resolutions/${module.organization}:${module.name}:$version")).split('\n').toSeq + + val dep = Dependency(module, version) + val res = await(resolve(Set(dep), fetchFrom(repositories)).runF) + + val result = res.dependencies.toVector.map(repr).sorted.distinct + + for (((e, r), idx) <- expected.zip(result).zipWithIndex if e != r) + println(s"Line $idx:\n expected: $e\n got:$r") + + assert(result == expected) + } + val tests = TestSuite { 'logback{ async { val dep = Dependency(Module("ch.qos.logback", "logback-classic"), "1.1.3") - val res0 = - await(resolve(Set(dep), fetchFrom(repositories)) - .runF) - - val res = res0.copy( - projectsCache = Map.empty, errors = Map.empty // No validating these here - ) + val res = await(resolve(Set(dep), fetchFrom(repositories)).runF) + .copy(projectsCache = Map.empty, errors = Map.empty) // No validating these here val expected = Resolution( rootDependencies = Set(dep.withCompileScope), dependencies = Set( dep.withCompileScope, Dependency(Module("ch.qos.logback", "logback-core"), "1.1.3").withCompileScope, - Dependency(Module("org.slf4j", "slf4j-api"), "1.7.7").withCompileScope - ) - ) + Dependency(Module("org.slf4j", "slf4j-api"), "1.7.7").withCompileScope)) assert(res == expected) } @@ -39,26 +54,43 @@ object CentralTests extends TestSuite { 'asm{ async { val dep = Dependency(Module("org.ow2.asm", "asm-commons"), "5.0.2") - val res0 = - await(resolve(Set(dep), fetchFrom(repositories)) - .runF) - - val res = res0.copy( - projectsCache = Map.empty, errors = Map.empty // No validating these here - ) + val res = await(resolve(Set(dep), fetchFrom(repositories)).runF) + .copy(projectsCache = Map.empty, errors = Map.empty) // No validating these here val expected = Resolution( rootDependencies = Set(dep.withCompileScope), dependencies = Set( dep.withCompileScope, Dependency(Module("org.ow2.asm", "asm-tree"), "5.0.2").withCompileScope, - Dependency(Module("org.ow2.asm", "asm"), "5.0.2").withCompileScope - ) - ) + Dependency(Module("org.ow2.asm", "asm"), "5.0.2").withCompileScope)) assert(res == expected) } } + 'jodaVersionInterval{ + async { + val dep = Dependency(Module("joda-time", "joda-time"), "[2.2,2.8]") + val res0 = await(resolve(Set(dep), fetchFrom(repositories)).runF) + val res = res0.copy(projectsCache = Map.empty, errors = Map.empty) + + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set( + dep.withCompileScope)) + + assert(res == expected) + assert(res0.projectsCache.contains(dep.moduleVersion)) + + val (_, proj) = res0.projectsCache(dep.moduleVersion) + assert(proj.version == "2.8") + } + } + 'spark{ + resolutionCheck(Module("org.apache.spark", "spark-core_2.11"), "1.3.1") + } + 'argonautShapeless{ + resolutionCheck(Module("com.github.alexarchambault", "argonaut-shapeless_6.1_2.11"), "0.2.0") + } } } diff --git a/core/src/test/scala/coursier/test/ExclusionsTests.scala b/core/src/test/scala/coursier/test/ExclusionsTests.scala index 906c95ab5..3eb775d95 100644 --- a/core/src/test/scala/coursier/test/ExclusionsTests.scala +++ b/core/src/test/scala/coursier/test/ExclusionsTests.scala @@ -2,7 +2,7 @@ package coursier package test import utest._ -import core.Resolver.{ exclusionsAdd, exclusionsIntersect } +import core.Resolver.exclusionsAdd object ExclusionsTests extends TestSuite { @@ -66,59 +66,6 @@ object ExclusionsTests extends TestSuite { assert(resultb2 == eb) } } - - 'intersect{ - 'basicZero{ - val result1l = exclusionsIntersect(e1, Set.empty) - val result1r = exclusionsIntersect(Set.empty, e1) - val result2l = exclusionsIntersect(e2, Set.empty) - val result2r = exclusionsIntersect(Set.empty, e2) - assert(result1l == Set.empty) - assert(result1r == Set.empty) - assert(result2l == Set.empty) - assert(result2r == Set.empty) - } - 'basic{ - val expected = e1 ++ e2 - val result12 = exclusionsIntersect(e1, e2) - val result21 = exclusionsIntersect(e2, e1) - assert(result12 == Set.empty) - assert(result21 == Set.empty) - } - - 'nameBlob{ - val result1b = exclusionsIntersect(e1, enb) - val resultb1 = exclusionsIntersect(enb, e1) - val result2b = exclusionsIntersect(e2, enb) - val resultb2 = exclusionsIntersect(enb, e2) - assert(result1b == e1) - assert(resultb1 == e1) - assert(result2b == Set.empty) - assert(resultb2 == Set.empty) - } - - 'orgBlob{ - val result1b = exclusionsIntersect(e1, eob) - val resultb1 = exclusionsIntersect(eob, e1) - val result2b = exclusionsIntersect(e2, eob) - val resultb2 = exclusionsIntersect(eob, e2) - assert(result1b == e1) - assert(resultb1 == e1) - assert(result2b == Set.empty) - assert(resultb2 == Set.empty) - } - - 'blob{ - val result1b = exclusionsIntersect(e1, eb) - val resultb1 = exclusionsIntersect(eb, e1) - val result2b = exclusionsIntersect(e2, eb) - val resultb2 = exclusionsIntersect(eb, e2) - assert(result1b == e1) - assert(resultb1 == e1) - assert(result2b == e2) - assert(resultb2 == e2) - } - } } } diff --git a/core/src/test/scala/coursier/test/PomParsingTests.scala b/core/src/test/scala/coursier/test/PomParsingTests.scala index 733e42f61..83d339acf 100644 --- a/core/src/test/scala/coursier/test/PomParsingTests.scala +++ b/core/src/test/scala/coursier/test/PomParsingTests.scala @@ -21,7 +21,7 @@ object PomParsingTests extends TestSuite { """ - val expected = \/-(Dependency(Module("comp", "lib"), "2.1", classifier = "extra")) + val expected = \/-(Dependency(Module("comp", "lib"), "2.1", artifacts = Artifacts.Maven(classifier = "extra"))) val result = Xml.dependency(xmlParse(depNode).right.get) @@ -40,6 +40,21 @@ object PomParsingTests extends TestSuite { assert(result == expected) } + 'beFineWithProfilesWithNoId{ + val profileNode = """ + + + true + + + """ + + val expected = \/-(Profile("", Some(true), Activation(Nil), Nil, Nil, Map.empty)) + + val result = Xml.profile(xmlParse(profileNode).right.get) + + assert(result == expected) + } 'readProfileActivatedByDefault{ val profileNode =""" diff --git a/core/src/test/scala/coursier/test/ResolverTests.scala b/core/src/test/scala/coursier/test/ResolverTests.scala index a4ea2a752..58f1457c6 100644 --- a/core/src/test/scala/coursier/test/ResolverTests.scala +++ b/core/src/test/scala/coursier/test/ResolverTests.scala @@ -22,7 +22,7 @@ object ResolverTests extends TestSuite { Project(Module("acme", "play-json"), "2.4.0"), Project(Module("acme", "play"), "2.4.1", - Seq( + dependencies = Seq( Dependency(Module("acme", "play-json"), "${playJsonVersion}"), Dependency(Module("${project.groupId}", "${configName}"), "1.3.0")), properties = Map( @@ -52,9 +52,8 @@ object ResolverTests extends TestSuite { exclusions = Set(("acme", "play-json"))))), Project(Module("se.ikea", "billy"), "18.0", - Seq( - Dependency(Module("acme", "play"), "") - ), + dependencies = Seq( + Dependency(Module("acme", "play"), "")), parent = Some(Module("se.ikea", "parent"), "18.0")), Project(Module("org.gnome", "parent"), "7.0", @@ -62,7 +61,7 @@ object ResolverTests extends TestSuite { Dependency(Module("org.gnu", "glib"), "13.4"))), Project(Module("org.gnome", "panel-legacy"), "7.0", - Seq( + dependencies = Seq( Dependency(Module("org.gnome", "desktop"), "${project.version}")), parent = Some(Module("org.gnome", "parent"), "7.0")), @@ -71,13 +70,13 @@ object ResolverTests extends TestSuite { Dependency(Module("gov.nsa", "crypto"), "536.89"))), Project(Module("com.mailapp", "mail-client"), "2.1", - Seq( + dependencies = Seq( Dependency(Module("gov.nsa", "secure-pgp"), "10.0", exclusions = Set(("*", "${crypto.name}")))), properties = Map("crypto.name" -> "crypto", "dummy" -> "2")), Project(Module("com.thoughtworks.paranamer", "paranamer-parent"), "2.6", - Seq( + dependencies = Seq( Dependency(Module("junit", "junit"), "")), dependencyManagement = Seq( Dependency(Module("junit", "junit"), "4.11", scope = Scope.Test))), @@ -88,44 +87,33 @@ object ResolverTests extends TestSuite { Project(Module("com.github.dummy", "libb"), "0.3.3", profiles = Seq( Profile("default", activeByDefault = Some(true), dependencies = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6") - )) - )), + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))))), Project(Module("com.github.dummy", "libb"), "0.4.2", - Seq( - Dependency(Module("org.scalaverification", "scala-verification"), "1.12.4") - ), + dependencies = Seq( + Dependency(Module("org.scalaverification", "scala-verification"), "1.12.4")), profiles = Seq( Profile("default", activeByDefault = Some(true), dependencies = Seq( Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"), - Dependency(Module("org.scalaverification", "scala-verification"), "1.12.4", scope = Scope.Test) - )) - )), + Dependency(Module("org.scalaverification", "scala-verification"), "1.12.4", scope = Scope.Test))))), Project(Module("com.github.dummy", "libb"), "0.5.3", properties = Map("special" -> "true"), profiles = Seq( Profile("default", activation = Profile.Activation(properties = Seq("special" -> None)), dependencies = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6") - )) - )), + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))))), Project(Module("com.github.dummy", "libb"), "0.5.4", properties = Map("special" -> "true"), profiles = Seq( Profile("default", activation = Profile.Activation(properties = Seq("special" -> Some("true"))), dependencies = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6") - )) - )), + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))))), Project(Module("com.github.dummy", "libb"), "0.5.5", properties = Map("special" -> "true"), profiles = Seq( Profile("default", activation = Profile.Activation(properties = Seq("special" -> Some("!false"))), dependencies = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6") - )) - )), + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))))), Project(Module("com.github.dummy", "libb-parent"), "0.5.6", properties = Map("special" -> "true")), @@ -135,9 +123,21 @@ object ResolverTests extends TestSuite { properties = Map("special" -> "true"), profiles = Seq( Profile("default", activation = Profile.Activation(properties = Seq("special" -> Some("!false"))), dependencies = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6") - )) - )) + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6"))))), + + Project(Module("an-org", "a-name"), "1.0"), + + Project(Module("an-org", "a-lib"), "1.0", + Seq(Dependency(Module("an-org", "a-name"), "1.0"))), + + Project(Module("an-org", "another-lib"), "1.0", + Seq(Dependency(Module("an-org", "a-name"), "1.0"))), + + // Must bring transitively an-org:a-name, as an optional dependency + Project(Module("an-org", "an-app"), "1.0", + Seq( + Dependency(Module("an-org", "a-lib"), "1.0", exclusions = Set(("an-org", "a-name"))), + Dependency(Module("an-org", "another-lib"), "1.0", optional = true))) ) val projectsMap = projects.map(p => p.moduleVersion -> p).toMap @@ -153,8 +153,7 @@ object ResolverTests extends TestSuite { val res = await(resolve( Set.empty, fetchFrom(repositories) - ).runF - ) + ).runF) assert(res == Resolution.empty) } @@ -178,236 +177,215 @@ object ResolverTests extends TestSuite { } 'single{ async { - val dep = Dependency(Module("acme", "config"), "1.3.0") - val res = await(resolve( - Set(dep), - fetchFrom(repositories) - ).runF) + val dep = Dependency(Module("acme", "config"), "1.3.0") + val res = await(resolve( + Set(dep), + fetchFrom(repositories) + ).runF) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope), - projectsCache = Map(dep.moduleVersion -> (testRepository, projectsMap(dep.moduleVersion))) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope), + projectsCache = Map(dep.moduleVersion -> (testRepository, projectsMap(dep.moduleVersion))) + ) - assert(res == expected) + assert(res == expected) } } 'oneTransitiveDependency{ async { - val dep = Dependency(Module("acme", "play"), "2.4.0") - val trDep = Dependency(Module("acme", "play-json"), "2.4.0") - val res = await(resolve( - Set(dep), - fetchFrom(repositories) - ).runF) + val dep = Dependency(Module("acme", "play"), "2.4.0") + val trDep = Dependency(Module("acme", "play-json"), "2.4.0") + val res = await(resolve( + Set(dep), + fetchFrom(repositories) + ).runF) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope, trDep.withCompileScope), - projectsCache = Map( - projectsMap(dep.moduleVersion).kv, - projectsMap(trDep.moduleVersion).kv + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope, trDep.withCompileScope), + projectsCache = Map( + projectsMap(dep.moduleVersion).kv, + projectsMap(trDep.moduleVersion).kv + ) ) - ) - assert(res == expected) + assert(res == expected) } } 'twoTransitiveDependencyWithProps{ async { - val dep = Dependency(Module("acme", "play"), "2.4.1") - val trDeps = Seq( - Dependency(Module("acme", "play-json"), "2.4.0"), - Dependency(Module("acme", "config"), "1.3.0") - ) - val res = await(resolve( - Set(dep), - fetchFrom(repositories) - ).runF) + val dep = Dependency(Module("acme", "play"), "2.4.1") + val trDeps = Seq( + Dependency(Module("acme", "play-json"), "2.4.0"), + Dependency(Module("acme", "config"), "1.3.0") + ) + val res = await(resolve( + Set(dep), + fetchFrom(repositories) + ).runF) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope), - projectsCache = Map( - projectsMap(dep.moduleVersion).kv - ) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope), + projectsCache = Map( + projectsMap(dep.moduleVersion).kv + ) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv) + ) - assert(res == expected) + assert(res == expected) } } 'exclude{ async { - val dep = Dependency(Module("acme", "play-extra-no-config"), "2.4.1") - val trDeps = Seq( - Dependency(Module("acme", "play"), "2.4.1", - exclusions = Set(("acme", "config"))), - Dependency(Module("acme", "play-json"), "2.4.0", - exclusions = Set(("acme", "config"))) - ) - val res = await(resolve( - Set(dep), - fetchFrom(repositories) - ).runF) + val dep = Dependency(Module("acme", "play-extra-no-config"), "2.4.1") + val trDeps = Seq( + Dependency(Module("acme", "play"), "2.4.1", + exclusions = Set(("acme", "config"))), + Dependency(Module("acme", "play-json"), "2.4.0", + exclusions = Set(("acme", "config"))) + ) + val res = await(resolve( + Set(dep), + fetchFrom(repositories) + ).runF) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope), - projectsCache = Map( - projectsMap(dep.moduleVersion).kv - ) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope), + projectsCache = Map( + projectsMap(dep.moduleVersion).kv + ) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv) + ) - assert(res == expected) + assert(res == expected) } } 'excludeOrgWildcard{ async { - val dep = Dependency(Module("acme", "play-extra-no-config-no"), "2.4.1") - val trDeps = Seq( - Dependency(Module("acme", "play"), "2.4.1", - exclusions = Set(("*", "config"))), - Dependency(Module("acme", "play-json"), "2.4.0", - exclusions = Set(("*", "config"))) - ) - val res = await(resolve( - Set(dep), - fetchFrom(repositories) - ).runF) + val dep = Dependency(Module("acme", "play-extra-no-config-no"), "2.4.1") + val trDeps = Seq( + Dependency(Module("acme", "play"), "2.4.1", + exclusions = Set(("*", "config"))), + Dependency(Module("acme", "play-json"), "2.4.0", + exclusions = Set(("*", "config"))) + ) + val res = await(resolve( + Set(dep), + fetchFrom(repositories) + ).runF) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope), - projectsCache = Map( - projectsMap(dep.moduleVersion).kv - ) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope), + projectsCache = Map( + projectsMap(dep.moduleVersion).kv + ) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv) + ) - assert(res == expected) + assert(res == expected) } } 'filter{ async { - val dep = Dependency(Module("hudsucker", "mail"), "10.0") - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None) + val dep = Dependency(Module("hudsucker", "mail"), "10.0") + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope), - projectsCache = Map( - projectsMap(dep.moduleVersion).kv + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope), + projectsCache = Map( + projectsMap(dep.moduleVersion).kv + ) ) - ) - assert(res == expected) + assert(res == expected) } } 'parentDepMgmt{ async { - val dep = Dependency(Module("se.ikea", "billy"), "18.0") - val trDeps = Seq( - Dependency(Module("acme", "play"), "2.4.0", - exclusions = Set(("acme", "play-json"))) - ) - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None, projectsCache = Map.empty) + val dep = Dependency(Module("se.ikea", "billy"), "18.0") + val trDeps = Seq( + Dependency(Module("acme", "play"), "2.4.0", + exclusions = Set(("acme", "play-json"))) + ) + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) + ) - assert(res == expected) + assert(res == expected) } } 'parentDependencies{ async { - val dep = Dependency(Module("org.gnome", "panel-legacy"), "7.0") - val trDeps = Seq( - Dependency(Module("org.gnu", "glib"), "13.4"), - Dependency(Module("org.gnome", "desktop"), "7.0")) - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + val dep = Dependency(Module("org.gnome", "panel-legacy"), "7.0") + val trDeps = Seq( + Dependency(Module("org.gnu", "glib"), "13.4"), + Dependency(Module("org.gnome", "desktop"), "7.0")) + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) + ) - assert(res == expected) + assert(res == expected) } } 'propertiesInExclusions{ async { - val dep = Dependency(Module("com.mailapp", "mail-client"), "2.1") - val trDeps = Seq( - Dependency(Module("gov.nsa", "secure-pgp"), "10.0", exclusions = Set(("*", "crypto")))) - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + val dep = Dependency(Module("com.mailapp", "mail-client"), "2.1") + val trDeps = Seq( + Dependency(Module("gov.nsa", "secure-pgp"), "10.0", exclusions = Set(("*", "crypto")))) + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) + ) - assert(res == expected) + assert(res == expected) } } 'depMgmtInParentDeps{ async { - val dep = Dependency(Module("com.thoughtworks.paranamer", "paranamer"), "2.6") - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + val dep = Dependency(Module("com.thoughtworks.paranamer", "paranamer"), "2.6") + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) + ) - assert(res == expected) + assert(res == expected) } } 'depsFromDefaultProfile{ async { - val dep = Dependency(Module("com.github.dummy", "libb"), "0.3.3") - val trDeps = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) - - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) - ) - - assert(res == expected) - } - } - 'depsFromPropertyActivatedProfile{ - val f = - for (version <- Seq("0.5.3", "0.5.4", "0.5.5", "0.5.6")) yield { - async { - val dep = Dependency(Module("com.github.dummy", "libb"), version) + val dep = Dependency(Module("com.github.dummy", "libb"), "0.3.3") val trDeps = Seq( Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) val res = await(resolve( @@ -423,28 +401,95 @@ object ResolverTests extends TestSuite { assert(res == expected) } - } + } + 'depsFromPropertyActivatedProfile{ + val f = + for (version <- Seq("0.5.3", "0.5.4", "0.5.5", "0.5.6")) yield { + async { + val dep = Dependency(Module("com.github.dummy", "libb"), version) + val trDeps = Seq( + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) + ) + + assert(res == expected) + } + } scala.concurrent.Future.sequence(f) } 'depsScopeOverrideFromProfile{ async { - // Like com.google.inject:guice:3.0 with org.sonatype.sisu.inject:cglib - val dep = Dependency(Module("com.github.dummy", "libb"), "0.4.2") - val trDeps = Seq( - Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) - val res = await(resolve( - Set(dep), - fetchFrom(repositories), - filter = Some(_.scope == Scope.Compile) - ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + // Like com.google.inject:guice:3.0 with org.sonatype.sisu.inject:cglib + val dep = Dependency(Module("com.github.dummy", "libb"), "0.4.2") + val trDeps = Seq( + Dependency(Module("org.escalier", "librairie-standard"), "2.11.6")) + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) - val expected = Resolution( - rootDependencies = Set(dep.withCompileScope), - dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) - ) + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) + ) - assert(res == expected) + assert(res == expected) + } + } + + 'exclusionsAndOptionalShouldGoAlong{ + async { + val dep = Dependency(Module("an-org", "an-app"), "1.0") + val trDeps = Seq( + Dependency(Module("an-org", "a-lib"), "1.0", exclusions = Set(("an-org", "a-name"))), + Dependency(Module("an-org", "another-lib"), "1.0", optional = true), + Dependency(Module("an-org", "a-name"), "1.0", optional = true)) + val res = await(resolve( + Set(dep), + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + + val expected = Resolution( + rootDependencies = Set(dep.withCompileScope), + dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope) + ) + + assert(res == expected) + } + } + + 'exclusionsOfDependenciesFromDifferentPathsShouldNotCollide{ + async { + val deps = Set( + Dependency(Module("an-org", "an-app"), "1.0"), + Dependency(Module("an-org", "a-lib"), "1.0", optional = true)) + val trDeps = Seq( + Dependency(Module("an-org", "a-lib"), "1.0", exclusions = Set(("an-org", "a-name"))), + Dependency(Module("an-org", "another-lib"), "1.0", optional = true), + Dependency(Module("an-org", "a-name"), "1.0", optional = true)) + val res = await(resolve( + deps, + fetchFrom(repositories), + filter = Some(_.scope == Scope.Compile) + ).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty) + + val expected = Resolution( + rootDependencies = deps.map(_.withCompileScope), + dependencies = (deps ++ trDeps).map(_.withCompileScope) + ) + + assert(res == expected) } } diff --git a/core/src/test/scala/coursier/test/VersionIntervalTests.scala b/core/src/test/scala/coursier/test/VersionIntervalTests.scala index 4a7c06288..932df2ca8 100644 --- a/core/src/test/scala/coursier/test/VersionIntervalTests.scala +++ b/core/src/test/scala/coursier/test/VersionIntervalTests.scala @@ -121,6 +121,46 @@ object VersionIntervalTests extends TestSuite { } } + 'contains{ + val v21 = Version("2.1") + val v22 = Version("2.2") + val v23 = Version("2.3") + val v24 = Version("2.4") + val v25 = Version("2.5") + val v26 = Version("2.6") + val v27 = Version("2.7") + val v28 = Version("2.8") + + 'basic{ + val itv = Parse.versionInterval("[2.2,)").get + + assert(!itv.contains(v21)) + assert(itv.contains(v22)) + assert(itv.contains(v23)) + assert(itv.contains(v24)) + } + 'open{ + val itv = Parse.versionInterval("(2.2,)").get + + assert(!itv.contains(v21)) + assert(!itv.contains(v22)) + assert(itv.contains(v23)) + assert(itv.contains(v24)) + } + 'segment{ + val itv = Parse.versionInterval("[2.2,2.8]").get + + assert(!itv.contains(v21)) + assert(itv.contains(v22)) + assert(itv.contains(v23)) + assert(itv.contains(v24)) + assert(itv.contains(v25)) + assert(itv.contains(v26)) + assert(itv.contains(v27)) + assert(itv.contains(v28)) + } + } + 'parse{ 'malformed{ val s1 = "[1.1]" @@ -225,77 +265,6 @@ object VersionIntervalTests extends TestSuite { } } - 'repr{ - 'basic { - val s1 = "[1.1,1.3]" - val repr1 = Parse.versionInterval(s1).map(_.repr) - assert(repr1 == Some(s1)) - - val s2 = "(1.1,1.3]" - val repr2 = Parse.versionInterval(s2).map(_.repr) - assert(repr2 == Some(s2)) - - val s3 = "[1.1,1.3)" - val repr3 = Parse.versionInterval(s3).map(_.repr) - assert(repr3 == Some(s3)) - - val s4 = "(1.1,1.3)" - val repr4 = Parse.versionInterval(s4).map(_.repr) - assert(repr4 == Some(s4)) - } - 'leftEmptyVersions { - val s1 = "[,1.3]" - val repr1 = Parse.versionInterval(s1).map(_.repr) - assert(repr1 == Some(s1)) - - val s2 = "(,1.3]" - val repr2 = Parse.versionInterval(s2).map(_.repr) - assert(repr2 == Some(s2)) - - val s3 = "[,1.3)" - val repr3 = Parse.versionInterval(s3).map(_.repr) - assert(repr3 == Some(s3)) - - val s4 = "(,1.3)" - val repr4 = Parse.versionInterval(s4).map(_.repr) - assert(repr4 == Some(s4)) - } - 'rightEmptyVersions { - val s1 = "[1.3,]" - val repr1 = Parse.versionInterval(s1).map(_.repr) - assert(repr1 == Some(s1)) - - val s2 = "(1.3,]" - val repr2 = Parse.versionInterval(s2).map(_.repr) - assert(repr2 == Some(s2)) - - val s3 = "[1.3,)" - val repr3 = Parse.versionInterval(s3).map(_.repr) - assert(repr3 == Some(s3)) - - val s4 = "(1.3,)" - val repr4 = Parse.versionInterval(s4).map(_.repr) - assert(repr4 == Some(s4)) - } - 'bothEmptyVersions { - val s1 = "[,]" - val repr1 = Parse.versionInterval(s1).map(_.repr) - assert(repr1 == Some(s1)) - - val s2 = "(,]" - val repr2 = Parse.versionInterval(s2).map(_.repr) - assert(repr2 == Some(s2)) - - val s3 = "[,)" - val repr3 = Parse.versionInterval(s3).map(_.repr) - assert(repr3 == Some(s3)) - - val s4 = "(,]" - val repr4 = Parse.versionInterval(s4).map(_.repr) - assert(repr4 == Some(s4)) - } - } - 'constraint{ 'none{ val s1 = "(,)" diff --git a/core/src/test/scala/coursier/test/ComparableVersionTests.scala b/core/src/test/scala/coursier/test/VersionTests.scala similarity index 95% rename from core/src/test/scala/coursier/test/ComparableVersionTests.scala rename to core/src/test/scala/coursier/test/VersionTests.scala index d86dd6450..5b1918f70 100644 --- a/core/src/test/scala/coursier/test/ComparableVersionTests.scala +++ b/core/src/test/scala/coursier/test/VersionTests.scala @@ -1,14 +1,12 @@ package coursier package test -import java.util.Locale - import utest._ -object ComparableVersionTests extends TestSuite { - import core.ComparableVersion.parse +object VersionTests extends TestSuite { + import core.Version - def compare(first: String, second: String) = parse(first).compare(parse(second)) + def compare(first: String, second: String) = Version(first).compare(Version(second)) def increasing(versions: String*): Boolean = versions.iterator.sliding(2).withPartial(false).forall{case Seq(a, b) => compare(a, b) < 0 } @@ -16,18 +14,31 @@ object ComparableVersionTests extends TestSuite { val tests = TestSuite { 'stackOverflow{ val s = "." * 100000 - val v = parse(s) + val v = Version(s) assert(v.isEmpty) } 'empty{ - val v0 = parse("0") - val v = parse("") + val v0 = Version("0") + val v = Version("") assert(v0.isEmpty) assert(v.isEmpty) } + 'max{ + val v21 = Version("2.1") + val v22 = Version("2.2") + val v23 = Version("2.3") + val v24 = Version("2.4") + val v241 = Version("2.4.1") + + val l = Seq(v21, v22, v23, v24, v241) + val max = l.max + + assert(max == v241) + } + 'numericOrdering{ assert(compare("1.2", "1.10") < 0) } @@ -328,6 +339,7 @@ object ComparableVersionTests extends TestSuite { // 'CaseInsensitiveOrderingOfQualifiersIsLocaleIndependent // { +// import java.util.Locale // val orig = Locale.getDefault // try { // for ( locale <- Seq(Locale.ENGLISH, new Locale( "tr" )) ) { diff --git a/project/Coursier.scala b/project/Coursier.scala index d5d6f6ad3..60e22572e 100644 --- a/project/Coursier.scala +++ b/project/Coursier.scala @@ -8,6 +8,9 @@ import sbtrelease.ReleasePlugin.ReleaseKeys.{ publishArtifactsAction, versionBum import sbtrelease.Version.Bump import com.typesafe.sbt.pgp.PgpKeys +import xerial.sbt.Pack._ + + object CoursierBuild extends Build { lazy val publishingSettings = Seq[Setting[_]]( @@ -53,7 +56,7 @@ object CoursierBuild extends Build { ) ++ releaseSettings lazy val commonSettings = Seq[Setting[_]]( - organization := "eu.frowning-lambda", + organization := "com.github.alexarchambault", version := "0.1.0-SNAPSHOT", scalaVersion := "2.11.6", crossScalaVersions := Seq("2.10.5", "2.11.6"), @@ -65,6 +68,8 @@ object CoursierBuild extends Build { libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.9.1" % "provided", unmanagedSourceDirectories in Compile += (baseDirectory in LocalRootProject).value / "core" / "src" / "main" / "scala", unmanagedSourceDirectories in Test += (baseDirectory in LocalRootProject).value / "core" / "src" / "test" / "scala", + unmanagedResourceDirectories in Compile += (baseDirectory in LocalRootProject).value / "core" / "src" / "main" / "resources", + unmanagedResourceDirectories in Test += (baseDirectory in LocalRootProject).value / "core" / "src" / "test" / "resources", testFrameworks += new TestFramework("utest.runner.Framework") ) @@ -98,8 +103,14 @@ object CoursierBuild extends Build { lazy val cli = Project(id = "cli", base = file("cli")) .dependsOn(coreJvm) - .settings(commonSettings ++ xerial.sbt.Pack.packAutoSettings: _*) + .settings(commonSettings ++ packAutoSettings ++ publishPackTxzArchive ++ publishPackZipArchive: _*) .settings( + packArchivePrefix := s"coursier-cli_${scalaBinaryVersion.value}", + packArchiveTxzArtifact := Artifact("coursier-cli", "arch", "tar.xz"), + packArchiveZipArtifact := Artifact("coursier-cli", "arch", "zip") + ) + .settings( + name := "coursier-cli", libraryDependencies ++= Seq( "com.github.alexarchambault" %% "case-app" % "0.2.2", "ch.qos.logback" % "logback-classic" % "1.1.3" diff --git a/project/push-gh-pages.sh b/project/push-gh-pages.sh index 536aaacca..de7b56857 100755 --- a/project/push-gh-pages.sh +++ b/project/push-gh-pages.sh @@ -21,7 +21,7 @@ cp -R "$DIR/classes/css" staging for i in staging/*.html; do mv "$i" "$i.0" - grep -v "src=\"\.\.\/web-" < "$i.0" > "$i" + sed "s/src=\"\.\.\/web-/src=\"web-/g" < "$i.0" > "$i" rm -f "$i.0" done diff --git a/web/src/main/resources/index.html b/web/src/main/resources/index.html index d45bf2097..1dd32b94e 100644 --- a/web/src/main/resources/index.html +++ b/web/src/main/resources/index.html @@ -6,8 +6,8 @@ + -