summaryrefslogtreecommitdiff
path: root/scalalib/src/dependency/versions
diff options
context:
space:
mode:
Diffstat (limited to 'scalalib/src/dependency/versions')
-rw-r--r--scalalib/src/dependency/versions/ModuleDependenciesVersions.scala12
-rw-r--r--scalalib/src/dependency/versions/Version.scala227
-rw-r--r--scalalib/src/dependency/versions/VersionParser.scala30
-rw-r--r--scalalib/src/dependency/versions/VersionsFinder.scala73
4 files changed, 342 insertions, 0 deletions
diff --git a/scalalib/src/dependency/versions/ModuleDependenciesVersions.scala b/scalalib/src/dependency/versions/ModuleDependenciesVersions.scala
new file mode 100644
index 00000000..12d57059
--- /dev/null
+++ b/scalalib/src/dependency/versions/ModuleDependenciesVersions.scala
@@ -0,0 +1,12 @@
+package mill.scalalib.dependency.versions
+
+import mill.scalalib.JavaModule
+
+private[dependency] final case class ModuleDependenciesVersions(
+ module: JavaModule,
+ dependencies: Seq[DependencyVersions])
+
+private[dependency] final case class DependencyVersions(
+ dependency: coursier.Dependency,
+ currentVersion: Version,
+ allversions: Set[Version])
diff --git a/scalalib/src/dependency/versions/Version.scala b/scalalib/src/dependency/versions/Version.scala
new file mode 100644
index 00000000..a2719023
--- /dev/null
+++ b/scalalib/src/dependency/versions/Version.scala
@@ -0,0 +1,227 @@
+/*
+ * This file contains code originally published under the following license:
+ *
+ * Copyright (c) 2012, Roman Timushev
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package mill.scalalib.dependency.versions
+
+import scala.util.matching.Regex
+import scala.util.matching.Regex.Groups
+
+private[dependency] sealed trait Version {
+ def major: Long
+
+ def minor: Long
+
+ def patch: Long
+}
+
+private[dependency] case class ValidVersion(text: String,
+ releasePart: List[Long],
+ preReleasePart: List[String],
+ buildPart: List[String])
+ extends Version {
+ def major: Long = releasePart.headOption getOrElse 0
+
+ def minor: Long = releasePart.drop(1).headOption getOrElse 1
+
+ def patch: Long = releasePart.drop(2).headOption getOrElse 1
+
+ override def toString: String = text
+}
+
+private[dependency] case class InvalidVersion(text: String) extends Version {
+ def major: Long = -1
+
+ def minor: Long = -1
+
+ def patch: Long = -1
+}
+
+private[dependency] object ReleaseVersion {
+ private val releaseKeyword: Regex = "(?i)final|release".r
+
+ def unapply(v: Version): Option[List[Long]] = v match {
+ case ValidVersion(_, releasePart, Nil, Nil) => Some(releasePart)
+ case ValidVersion(_, releasePart, releaseKeyword() :: Nil, Nil) =>
+ Some(releasePart)
+ case _ => None
+ }
+}
+
+private[dependency] object PreReleaseVersion {
+ def unapply(v: Version): Option[(List[Long], List[String])] = v match {
+ case ValidVersion(_, releasePart, preReleasePart, Nil)
+ if preReleasePart.nonEmpty =>
+ Some(releasePart, preReleasePart)
+ case _ => None
+ }
+}
+
+private[dependency] object PreReleaseBuildVersion {
+ def unapply(v: Version): Option[(List[Long], List[String], List[String])] =
+ v match {
+ case ValidVersion(_, releasePart, preReleasePart, buildPart)
+ if preReleasePart.nonEmpty && buildPart.nonEmpty =>
+ Some(releasePart, preReleasePart, buildPart)
+ case _ => None
+ }
+}
+
+private[dependency] object SnapshotVersion {
+ def unapply(v: Version): Option[(List[Long], List[String], List[String])] =
+ v match {
+ case ValidVersion(_, releasePart, preReleasePart, buildPart)
+ if preReleasePart.lastOption.contains("SNAPSHOT") =>
+ Some(releasePart, preReleasePart, buildPart)
+ case _ => None
+ }
+}
+
+private[dependency] object BuildVersion {
+ def unapply(v: Version): Option[(List[Long], List[String])] = v match {
+ case ValidVersion(_, releasePart, Nil, buildPart) if buildPart.nonEmpty =>
+ Some(releasePart, buildPart)
+ case _ => None
+ }
+}
+
+private[dependency] object Version {
+ def apply(text: String): Version = synchronized {
+ VersionParser
+ .parse(text)
+ .fold(
+ (_, _, _) => InvalidVersion(text),
+ { case ((a, b, c), _) => ValidVersion(text, a.toList, b.toList, c.toList)}
+ )
+ }
+
+ implicit def versionOrdering: Ordering[Version] = VersionOrdering
+}
+
+private[dependency] object VersionOrdering extends Ordering[Version] {
+
+ private val subParts = "(\\d+)?(\\D+)?".r
+
+ private def parsePart(s: String): Seq[Either[Int, String]] =
+ try {
+ subParts
+ .findAllIn(s)
+ .matchData
+ .flatMap {
+ case Groups(num, str) =>
+ Seq(Option(num).map(_.toInt).map(Left.apply),
+ Option(str).map(Right.apply))
+ }
+ .flatten
+ .toList
+ } catch {
+ case _: NumberFormatException => List(Right(s))
+ }
+
+ private def toOpt(x: Int): Option[Int] = if (x == 0) None else Some(x)
+
+ private def comparePart(a: String, b: String) = {
+ if (a == b) None
+ else
+ (parsePart(a) zip parsePart(b)) map {
+ case (Left(x), Left(y)) => x compareTo y
+ case (Left(_), Right(_)) => -1
+ case (Right(_), Left(_)) => 1
+ case (Right(x), Right(y)) => x compareTo y
+ } find (0 != _) orElse Some(a compareTo b)
+ }
+
+ private def compareNumericParts(a: List[Long], b: List[Long]): Option[Int] =
+ (a, b) match {
+ case (ah :: at, bh :: bt) =>
+ toOpt(ah compareTo bh) orElse compareNumericParts(at, bt)
+ case (ah :: at, Nil) =>
+ toOpt(ah compareTo 0L) orElse compareNumericParts(at, Nil)
+ case (Nil, bh :: bt) =>
+ toOpt(0L compareTo bh) orElse compareNumericParts(Nil, bt)
+ case (Nil, Nil) =>
+ None
+ }
+
+ private def compareParts(a: List[String], b: List[String]): Option[Int] =
+ (a, b) match {
+ case (ah :: at, bh :: bt) =>
+ comparePart(ah, bh) orElse compareParts(at, bt)
+ case (_ :: _, Nil) =>
+ Some(1)
+ case (Nil, _ :: _) =>
+ Some(-1)
+ case (Nil, Nil) =>
+ None
+ }
+
+ def compare(x: Version, y: Version): Int = (x, y) match {
+ case (InvalidVersion(a), InvalidVersion(b)) =>
+ a compareTo b
+ case (InvalidVersion(_), _) =>
+ -1
+ case (_, InvalidVersion(_)) =>
+ 1
+ case (ReleaseVersion(r1), ReleaseVersion(r2)) =>
+ compareNumericParts(r1, r2) getOrElse 0
+ case (ReleaseVersion(r1), PreReleaseVersion(r2, p2)) =>
+ compareNumericParts(r1, r2) getOrElse 1
+ case (ReleaseVersion(r1), PreReleaseBuildVersion(r2, p2, b2)) =>
+ compareNumericParts(r1, r2) getOrElse 1
+ case (ReleaseVersion(r1), BuildVersion(r2, b2)) =>
+ compareNumericParts(r1, r2) getOrElse -1
+ case (PreReleaseVersion(r1, p1), ReleaseVersion(r2)) =>
+ compareNumericParts(r1, r2) getOrElse -1
+ case (PreReleaseVersion(r1, p1), PreReleaseVersion(r2, p2)) =>
+ compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 0
+ case (PreReleaseVersion(r1, p1), PreReleaseBuildVersion(r2, p2, b2)) =>
+ compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse -1
+ case (PreReleaseVersion(r1, p1), BuildVersion(r2, b2)) =>
+ compareNumericParts(r1, r2) getOrElse -1
+ case (PreReleaseBuildVersion(r1, p1, b1), ReleaseVersion(r2)) =>
+ compareNumericParts(r1, r2) getOrElse -1
+ case (PreReleaseBuildVersion(r1, p1, b1), PreReleaseVersion(r2, p2)) =>
+ compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 1
+ case (PreReleaseBuildVersion(r1, p1, b1),
+ PreReleaseBuildVersion(r2, p2, b2)) =>
+ compareNumericParts(r1, r2) orElse
+ compareParts(p1, p2) orElse
+ compareParts(b1, b2) getOrElse
+ 0
+ case (PreReleaseBuildVersion(r1, p1, b1), BuildVersion(r2, b2)) =>
+ compareNumericParts(r1, r2) getOrElse -1
+ case (BuildVersion(r1, b1), ReleaseVersion(r2)) =>
+ compareNumericParts(r1, r2) getOrElse 1
+ case (BuildVersion(r1, b1), PreReleaseVersion(r2, p2)) =>
+ compareNumericParts(r1, r2) getOrElse 1
+ case (BuildVersion(r1, b1), PreReleaseBuildVersion(r2, p2, b2)) =>
+ compareNumericParts(r1, r2) getOrElse 1
+ case (BuildVersion(r1, b1), BuildVersion(r2, b2)) =>
+ compareNumericParts(r1, r2) orElse compareParts(b1, b2) getOrElse 0
+ }
+
+}
diff --git a/scalalib/src/dependency/versions/VersionParser.scala b/scalalib/src/dependency/versions/VersionParser.scala
new file mode 100644
index 00000000..10aebd73
--- /dev/null
+++ b/scalalib/src/dependency/versions/VersionParser.scala
@@ -0,0 +1,30 @@
+package mill.scalalib.dependency.versions
+
+import fastparse._, NoWhitespace._
+
+private[dependency] object VersionParser {
+
+ private def numberParser[_: P] =
+ P(CharIn("0-9").rep(1).!.map(_.toLong))
+ private def numericPartParser[_: P] =
+ P(numberParser ~ &(CharIn(".\\-+") | End)).rep(min = 1, sep = ".")
+
+ private def tokenParser[_: P] =
+ CharPred(c => c != '.' && c != '-' && c != '+').rep(1).!
+ private def tokenPartParser[_: P] =
+ tokenParser.rep(sep = CharIn(".\\-"))
+
+ private def firstPartParser[_: P] =
+ P(CharIn(".\\-") ~ tokenPartParser).?
+
+ private def secondPartParser[_: P] =
+ P("+" ~ tokenPartParser).?
+
+ private def versionParser[_: P] =
+ P(numericPartParser ~ firstPartParser ~ secondPartParser).map {
+ case (a, b, c) => (a, b.getOrElse(Seq.empty), c.getOrElse(Seq.empty))
+ }
+
+ def parse(text: String): Parsed[(Seq[Long], Seq[String], Seq[String])] =
+ fastparse.parse(text, versionParser(_))
+}
diff --git a/scalalib/src/dependency/versions/VersionsFinder.scala b/scalalib/src/dependency/versions/VersionsFinder.scala
new file mode 100644
index 00000000..a831ffc3
--- /dev/null
+++ b/scalalib/src/dependency/versions/VersionsFinder.scala
@@ -0,0 +1,73 @@
+package mill.scalalib.dependency.versions
+
+import mill.define.{BaseModule, Task}
+import mill.eval.Evaluator
+import mill.scalalib.dependency.metadata.MetadataLoaderFactory
+import mill.scalalib.{Dep, JavaModule, Lib}
+import mill.api.Ctx.{Home, Log}
+import mill.util.{Loose, Strict}
+
+private[dependency] object VersionsFinder {
+
+ def findVersions(ctx: Log with Home,
+ rootModule: BaseModule): Seq[ModuleDependenciesVersions] = {
+ val evaluator =
+ new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log)
+
+ val javaModules = rootModule.millInternal.modules.collect {
+ case javaModule: JavaModule => javaModule
+ }
+
+ val resolvedDependencies = resolveDependencies(evaluator, javaModules)
+ resolveVersions(resolvedDependencies)
+ }
+
+ private def resolveDependencies(evaluator: Evaluator,
+ javaModules: Seq[JavaModule]) =
+ javaModules.map { javaModule =>
+ val depToDependency =
+ eval(evaluator, javaModule.resolveCoursierDependency)
+ val deps = evalOrElse(evaluator, javaModule.ivyDeps, Loose.Agg.empty[Dep])
+
+ val (dependencies, _) =
+ Lib.resolveDependenciesMetadata(javaModule.repositories,
+ depToDependency,
+ deps)
+
+ (javaModule, dependencies)
+ }
+
+ private def resolveVersions(resolvedDependencies: Seq[ResolvedDependencies]) =
+ resolvedDependencies.map {
+ case (javaModule, dependencies) =>
+ val metadataLoaders =
+ javaModule.repositories.flatMap(MetadataLoaderFactory(_))
+
+ val versions = dependencies.map { dependency =>
+ val currentVersion = Version(dependency.version)
+ val allVersions =
+ metadataLoaders
+ .flatMap(_.getVersions(dependency.module))
+ .toSet
+ DependencyVersions(dependency, currentVersion, allVersions)
+ }
+
+ ModuleDependenciesVersions(javaModule, versions)
+ }
+
+ private def eval[T](evaluator: Evaluator, e: Task[T]): T =
+ evaluator.evaluate(Strict.Agg(e)).values match {
+ case Seq() => throw new NoSuchElementException
+ case Seq(e: T) => e
+ }
+
+ private def evalOrElse[T](evaluator: Evaluator,
+ e: Task[T],
+ default: => T): T =
+ evaluator.evaluate(Strict.Agg(e)).values match {
+ case Seq() => default
+ case Seq(e: T) => e
+ }
+
+ private type ResolvedDependencies = (JavaModule, Seq[coursier.Dependency])
+}