diff --git a/core/shared/src/main/scala/coursier/core/Parse.scala b/core/shared/src/main/scala/coursier/core/Parse.scala index a813b26f1..eacc42788 100644 --- a/core/shared/src/main/scala/coursier/core/Parse.scala +++ b/core/shared/src/main/scala/coursier/core/Parse.scala @@ -10,6 +10,21 @@ object Parse { else Some(Version(s)) } + def ivyLatestSubRevisionInterval(s: String): Option[VersionInterval] = + if (s.endsWith(".+")) { + for { + from <- version(s.stripSuffix(".+")) + if from.rawItems.nonEmpty + last <- Some(from.rawItems.last).collect { case n: Version.Numeric => n } + // a bit loose, but should do the job + if from.repr.endsWith(last.repr) + to <- version(from.repr.stripSuffix(last.repr) + last.next.repr) + // the contrary would mean something went wrong in the loose substitution above + if from.rawItems.init == to.rawItems.init + } yield VersionInterval(Some(from), Some(to), fromIncluded = true, toIncluded = false) + } else + None + def versionInterval(s: String): Option[VersionInterval] = { for { fromIncluded <- if (s.startsWith("[")) Some(true) else if (s.startsWith("(")) Some(false) else None @@ -28,6 +43,7 @@ object Parse { def noConstraint = if (s.isEmpty) Some(VersionConstraint.None) else None noConstraint + .orElse(ivyLatestSubRevisionInterval(s).map(VersionConstraint.Interval)) .orElse(version(s).map(VersionConstraint.Preferred)) .orElse(versionInterval(s).map(VersionConstraint.Interval)) } diff --git a/core/shared/src/main/scala/coursier/core/Version.scala b/core/shared/src/main/scala/coursier/core/Version.scala index e01ebfb77..b15d3f9ff 100644 --- a/core/shared/src/main/scala/coursier/core/Version.scala +++ b/core/shared/src/main/scala/coursier/core/Version.scala @@ -10,6 +10,10 @@ import coursier.core.compatibility._ */ case class Version(repr: String) extends Ordered[Version] { lazy val items = Version.items(repr) + lazy val rawItems: Seq[Version.Item] = { + val (first, tokens) = Version.Tokenizer(repr) + first +: tokens.toVector.map { case (_, item) => item } + } def compare(other: Version) = Version.listCompare(items, other.items) def isEmpty = items.forall(_.isEmpty) } @@ -39,13 +43,20 @@ object Version { def compareToEmpty: Int = 1 } - sealed trait Numeric extends Item + sealed trait Numeric extends Item { + def repr: String + def next: Numeric + } case class Number(value: Int) extends Numeric { val order = 0 + def next: Number = Number(value + 1) + def repr: String = value.toString override def compareToEmpty = value.compare(0) } case class BigNumber(value: BigInt) extends Numeric { val order = 0 + def next: BigNumber = BigNumber(value + 1) + def repr: String = value.toString override def compareToEmpty = value.compare(0) } case class Qualifier(value: String, level: Int) extends Item { diff --git a/core/shared/src/main/scala/coursier/maven/MavenRepository.scala b/core/shared/src/main/scala/coursier/maven/MavenRepository.scala index 3eddb4b7c..f08bfcb97 100644 --- a/core/shared/src/main/scala/coursier/maven/MavenRepository.scala +++ b/core/shared/src/main/scala/coursier/maven/MavenRepository.scala @@ -261,6 +261,7 @@ case class MavenRepository( ): EitherT[F, String, (Artifact.Source, Project)] = { Parse.versionInterval(version) + .orElse(Parse.ivyLatestSubRevisionInterval(version)) .filter(_.isValid) match { case None => findNoInterval(module, version, fetch).map((source, _)) diff --git a/tests/shared/src/test/resources/resolutions/com.chuusai/shapeless_2.11/2.2.+ b/tests/shared/src/test/resources/resolutions/com.chuusai/shapeless_2.11/2.2.+ new file mode 100644 index 000000000..be25eaf98 --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/com.chuusai/shapeless_2.11/2.2.+ @@ -0,0 +1,2 @@ +com.chuusai:shapeless_2.11:jar:2.2.5 +org.scala-lang:scala-library:jar:2.11.7 diff --git a/tests/shared/src/test/resources/resolutions/com.chuusai/shapeless_2.11/[2.2.0,2.3.0) b/tests/shared/src/test/resources/resolutions/com.chuusai/shapeless_2.11/[2.2.0,2.3.0) new file mode 100644 index 000000000..be25eaf98 --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/com.chuusai/shapeless_2.11/[2.2.0,2.3.0) @@ -0,0 +1,2 @@ +com.chuusai:shapeless_2.11:jar:2.2.5 +org.scala-lang:scala-library:jar:2.11.7 diff --git a/tests/shared/src/test/resources/resolutions/com.googlecode.libphonenumber/libphonenumber/7.0.+ b/tests/shared/src/test/resources/resolutions/com.googlecode.libphonenumber/libphonenumber/7.0.+ new file mode 100644 index 000000000..2c9c7326a --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/com.googlecode.libphonenumber/libphonenumber/7.0.+ @@ -0,0 +1 @@ +com.googlecode.libphonenumber:libphonenumber:jar:7.0.11 diff --git a/tests/shared/src/test/resources/resolutions/com.googlecode.libphonenumber/libphonenumber/[7.0,7.1) b/tests/shared/src/test/resources/resolutions/com.googlecode.libphonenumber/libphonenumber/[7.0,7.1) new file mode 100644 index 000000000..2c9c7326a --- /dev/null +++ b/tests/shared/src/test/resources/resolutions/com.googlecode.libphonenumber/libphonenumber/[7.0,7.1) @@ -0,0 +1 @@ +com.googlecode.libphonenumber:libphonenumber:jar:7.0.11 diff --git a/tests/shared/src/test/scala/coursier/test/CentralTests.scala b/tests/shared/src/test/scala/coursier/test/CentralTests.scala index 8da90698c..3e0704f39 100644 --- a/tests/shared/src/test/scala/coursier/test/CentralTests.scala +++ b/tests/shared/src/test/scala/coursier/test/CentralTests.scala @@ -61,7 +61,15 @@ object CentralTests extends TestSuite { val result = res .dependencies .toVector - .map(repr) + .map { dep => + val projOpt = res.projectCache + .get(dep.moduleVersion) + .map { case (_, proj) => proj } + val dep0 = dep.copy( + version = projOpt.fold(dep.version)(_.version) + ) + repr(dep0) + } .sorted .distinct @@ -149,6 +157,27 @@ object CentralTests extends TestSuite { "1.1.2" ) } + 'latestRevision - { + resolutionCheck( + Module("com.chuusai", "shapeless_2.11"), + "[2.2.0,2.3.0)" + ) + + resolutionCheck( + Module("com.chuusai", "shapeless_2.11"), + "2.2.+" + ) + + resolutionCheck( + Module("com.googlecode.libphonenumber", "libphonenumber"), + "[7.0,7.1)" + ) + + resolutionCheck( + Module("com.googlecode.libphonenumber", "libphonenumber"), + "7.0.+" + ) + } } }