All Downloads are FREE. Search and download functionalities are using the official Maven repository.

coursier.version.VersionCompatibility.scala Maven / Gradle / Ivy

The newest version!
package coursier.version

/**
 * Represents a reconciliation strategy given a dependency conflict.
 */
sealed abstract class VersionCompatibility {
  def isCompatible(constraint: String, version: String): Boolean

  final def name: String =
    this match {
      case VersionCompatibility.Always => "always compatible"
      case VersionCompatibility.Strict => "strict"
      case VersionCompatibility.SemVerSpec => "strict semantic versioning"
      case VersionCompatibility.EarlySemVer | VersionCompatibility.SemVer =>
        "early semantic versioning"
      case VersionCompatibility.Default | VersionCompatibility.PackVer =>
        "package versioning policy"
    }

  def minimumCompatibleVersion(version: String): String
}

object VersionCompatibility {

  case object Default extends VersionCompatibility {
    def isCompatible(constraint: String, version: String): Boolean =
      PackVer.isCompatible(constraint, version)
    def minimumCompatibleVersion(version: String): String =
      PackVer.minimumCompatibleVersion(version)
  }

  case object Always extends VersionCompatibility {
    def isCompatible(constraint: String, version: String): Boolean =
      true
    def minimumCompatibleVersion(version: String): String =
      "0"
  }

  /**
    * Strict version reconciliation.
    *
    * This particular instance behaves the same as [[Default]] when used by
    * [[coursier.core.Resolution]]. Actual strict conflict manager is handled
    * by `coursier.params.rule.Strict`, which is set up by `coursier.Resolve`
    * when a strict reconciliation is added to it.
    */
  case object Strict extends VersionCompatibility {
    def isCompatible(constraint: String, version: String): Boolean =
      constraint == version || {
        val c = VersionParse.versionConstraint(constraint)
        val v = Version(version)
        if (c.interval == VersionInterval.zero)
          c.preferred.contains(v)
        else
          c.interval.contains(v)
      }
    def minimumCompatibleVersion(version: String): String =
      version
  }

  /**
    * Early Semantic Versioning version reconciliation.
    */
  @deprecated("Use EarlySemVer or SemVerSpec instead. This will be removed in the future version.", "0.3.0")
  case object SemVer extends VersionCompatibility {
    def isCompatible(constraint: String, version: String): Boolean =
      EarlySemVer.isCompatible(constraint, version)
    def minimumCompatibleVersion(version: String): String =
      EarlySemVer.minimumCompatibleVersion(version)
  }

  /**
    * Early Semantic Versioning version reconciliation.
    */
  case object EarlySemVer extends VersionCompatibility {
    private def significativePartLength(v: Version): Int =
      if (v.items.headOption.exists(_.isEmpty)) 2 else 1
    def isCompatible(constraint: String, version: String): Boolean =
      constraint == version || {
        val c = VersionParse.versionConstraint(constraint)
        val v = Version(version)
        if (c.interval == VersionInterval.zero)
          c.preferred.exists { wanted =>
            val toCompare = significativePartLength(v)
            wanted.items.forall(_.isNumber) &&
            wanted.items.take(toCompare) == v.items.take(toCompare) && {
              import Ordering.Implicits._
              wanted.items.drop(toCompare) <= v.items.drop(toCompare)
            }
          }
        else
          c.interval.contains(v)
      }
    def minimumCompatibleVersion(version: String): String = {
      val v = Version(version)
      val toCompare = significativePartLength(v)
      val candidateOpt = Some(v.items.take(toCompare))
        .filter(_.forall(_.isNumber))
        .map(_.collect { case n: Version.Numeric => n })
        .map(items => items.map(_.repr).mkString("."))
        .filter(s => Version(s).compareTo(v) <= 0)
      candidateOpt.getOrElse(version)
    }
  }

  /**
    * Semantic versioning version reconciliation, closer to the semantic versioning spec.
    *
    * Unlike `SemVer`, assumes 0.x versions are not compatible with each other.
    */
  case object SemVerSpec extends VersionCompatibility {
    def isCompatible(constraint: String, version: String): Boolean =
      constraint == version || {
        val c = VersionParse.versionConstraint(constraint)
        val v = Version(version)
        if (c.interval == VersionInterval.zero)
          c.preferred.exists { wanted =>
            wanted.items.forall(_.isNumber) &&
            wanted.items.take(1) == v.items.take(1) &&
            v.items.take(1).exists(!_.isEmpty) && {
              import Ordering.Implicits._
              wanted.items.drop(1) <= v.items.drop(1)
            }
          }
        else
          c.interval.contains(v)
      }
    def minimumCompatibleVersion(version: String): String = {
      val v = Version(version)
      val candidateOpt = Some(v.items.take(1))
        .filter(items => items.nonEmpty && items.forall(_.isNumber) && items.forall(!_.isEmpty))
        .map(_.collect { case n: Version.Numeric => n })
        .map(items => items.map(_.repr).mkString("."))
        .filter(s => Version(s).compareTo(v) <= 0)
      candidateOpt.getOrElse(version)
    }
  }

  case object PackVer extends VersionCompatibility {
    def isCompatible(constraint: String, version: String): Boolean =
      constraint == version || {
        val c = VersionParse.versionConstraint(constraint)
        val v = Version(version)
        if (c.interval == VersionInterval.zero)
          c.preferred.exists(_.items.take(2) == v.items.take(2))
        else
          c.interval.contains(v)
      }
    def minimumCompatibleVersion(version: String): String = {
      val v = Version(version)
      val candidateOpt = Some(v.items.take(2))
        .filter(_.forall(_.isNumber))
        .map(_.collect { case n: Version.Numeric => n })
        .map(items => items.map(_.repr).mkString("."))
        .filter(s => Version(s).compareTo(v) <= 0)
      candidateOpt.getOrElse(version)
    }
  }

  def apply(input: String): Option[VersionCompatibility] =
    input match {
      case "default" => Some(Default)
      case "always" => Some(Always)
      case "strict" => Some(Strict)
      case "early-semver" => Some(EarlySemVer)
      case "semver-spec" => Some(SemVerSpec)
      case "pvp" => Some(PackVer)
      case "semver" => sys.error(s"""'semver' is ambiguous.
                                    |Based on the Semantic Versioning 2.0.0, 0.y.z updates are all initial development and thus
                                    |0.6.0 and 0.6.1 would NOT maintain any compatibility, but in Scala ecosystem it is
                                    |common to start adopting binary compatibility even in 0.y.z releases.
                                    |
                                    |Specify 'early-semver' for the early variant.
                                    |Specify 'semver-spec' for the spec-correct SemVer.""".stripMargin)
      case _ => None
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy