mirror of https://github.com/sbt/sbt.git
Handling version interval properly
Fixes https://github.com/alexarchambault/coursier/issues/2
This commit is contained in:
parent
35b5790be7
commit
4391859a72
|
|
@ -101,7 +101,14 @@ case class Coursier(scope: List[String],
|
|||
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}"
|
||||
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,8 @@ case class ArtifactDownloader(root: String, cache: File, logger: Option[Artifact
|
|||
|
||||
val tasks =
|
||||
artifacts0 .map { artifact0 =>
|
||||
artifact(dependency.module, dependency.version, artifact0, cachePolicy = cachePolicy).run
|
||||
// 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)
|
||||
|
|
@ -151,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
|
||||
}
|
||||
|
||||
|
|
@ -185,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)))
|
||||
|
|
@ -209,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
|
||||
|
|
@ -226,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
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ case class Project(module: Module,
|
|||
parent: Option[(Module, String)],
|
||||
dependencyManagement: Seq[Dependency],
|
||||
properties: Map[String, String],
|
||||
profiles: Seq[Profile]) {
|
||||
profiles: Seq[Profile],
|
||||
versions: Option[Versions]) {
|
||||
def moduleVersion = (module, version)
|
||||
}
|
||||
|
||||
|
|
@ -89,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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -591,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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
package coursier.core
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
case class VersionInterval(from: Option[Version],
|
||||
to: Option[Version],
|
||||
fromIncluded: Boolean,
|
||||
|
|
@ -27,6 +16,21 @@ case class VersionInterval(from: Option[Version],
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -209,7 +209,8 @@ object Xml {
|
|||
parentModuleOpt.map((_, parentVersionOpt.getOrElse(""))),
|
||||
depMgmts,
|
||||
properties.toMap,
|
||||
profiles
|
||||
profiles,
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,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
|
||||
|
|
|
|||
|
|
@ -67,6 +67,24 @@ object CentralTests extends TestSuite {
|
|||
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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,19 @@ object VersionTests extends TestSuite {
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ object App {
|
|||
label
|
||||
)
|
||||
|
||||
def depItem(dep: Dependency) = {
|
||||
def depItem(dep: Dependency, finalVersionOpt: Option[String]) = {
|
||||
val (type0, classifier) = dep.artifacts match {
|
||||
case maven: Artifacts.Maven => (maven.`type`, maven.classifier)
|
||||
}
|
||||
|
|
@ -226,7 +226,7 @@ object App {
|
|||
^.`class` := (if (res.errors.contains(dep.moduleVersion)) "danger" else ""),
|
||||
<.td(dep.module.organization),
|
||||
<.td(dep.module.name),
|
||||
<.td(dep.version),
|
||||
<.td(finalVersionOpt.fold(dep.version)(finalVersion => s"$finalVersion (for ${dep.version})")),
|
||||
<.td(Seq[Seq[TagMod]](
|
||||
if (dep.scope == Scope.Compile) Seq() else Seq(infoLabel(dep.scope.name)),
|
||||
if (type0.isEmpty || type0 == "jar") Seq() else Seq(infoLabel(type0)),
|
||||
|
|
@ -239,11 +239,12 @@ object App {
|
|||
res.projectsCache.get(dep.moduleVersion) match {
|
||||
case Some((repo: Remote, _)) =>
|
||||
// FIXME Maven specific, generalize if/when adding support for Ivy
|
||||
val version0 = finalVersionOpt getOrElse dep.version
|
||||
val relPath =
|
||||
dep.module.organization.split('.').toSeq ++ Seq(
|
||||
dep.module.name,
|
||||
dep.version,
|
||||
s"${dep.module.name}-${dep.version}"
|
||||
version0,
|
||||
s"${dep.module.name}-$version0"
|
||||
)
|
||||
|
||||
Seq(
|
||||
|
|
@ -275,7 +276,7 @@ object App {
|
|||
)
|
||||
),
|
||||
<.tbody(
|
||||
sortedDeps.map(depItem)
|
||||
sortedDeps.map(dep => depItem(dep, res.projectsCache.get(dep.moduleVersion).map(_._2.version).filter(_ != dep.version)))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue