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

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

The newest version!
package coursier.version

/**
  * Reconciles a set of version constraints (version intervals, specific versions, …).
  *
  * To be used mainly during resolution.
  */
sealed abstract class ConstraintReconciliation extends Product with Serializable {
  def reconcile(versions: Seq[String]): Option[String]
}

object ConstraintReconciliation {

  private final val LatestIntegration = "latest.integration"
  private final val LatestRelease = "latest.release"
  private final val LatestStable = "latest.stable"

  private def splitStandard(versions: Seq[String]): (Seq[String], Seq[String]) =
    versions.distinct.partition {
      case LatestIntegration => false
      case LatestRelease     => false
      case LatestStable      => false
      case _                 => true
    }

  private def retainLatestOpt(latests: Seq[String]): Option[String] =
    if (latests.isEmpty) None
    else if (latests.lengthCompare(1) == 0) latests.headOption
    else {
      val set = latests.toSet
      val retained =
        if (set(LatestIntegration))
          LatestIntegration
        else if (set(LatestRelease))
          LatestRelease
        else {
          // at least two distinct latest.* means we shouldn't even reach this else block anyway
          assert(set(LatestStable))
          LatestStable
        }
      Some(retained)
    }


  /**
   * Keeps the intersection of intervals, retains the latest version, etc. as described in the coursier documentation
   *
   * Fails when passed version intervals that don't overlap.
   */
  case object Default extends ConstraintReconciliation {
    def reconcile(versions: Seq[String]): Option[String] =
      if (versions.isEmpty)
        None
      else if (versions.lengthCompare(1) == 0)
        Some(versions.head)
      else {
        val (standard, latests) = splitStandard(versions)
        val retainedStandard =
          if (standard.isEmpty) None
          else if (standard.lengthCompare(1) == 0) standard.headOption
          else {
            val parsedConstraints = standard.map(VersionParse.versionConstraint)
            VersionConstraint.merge(parsedConstraints: _*)
              .flatMap(_.repr)
          }
        val retainedLatestOpt = retainLatestOpt(latests)

        if (standard.isEmpty)
          retainedLatestOpt
        else if (latests.isEmpty)
          retainedStandard
        else {
          val parsedIntervals = standard.map(VersionParse.versionConstraint)
            .filter(_.preferred.isEmpty) // only keep intervals
            .filter(_.interval != VersionInterval.zero) // not interval matching any version

          if (parsedIntervals.isEmpty)
            retainedLatestOpt
          else
            VersionConstraint.merge(parsedIntervals: _*)
              .flatMap(_.repr)
              .map(itv => (itv +: retainedLatestOpt.toSeq).mkString("&"))
        }
      }
  }

  /**
   * Always succeeds
   *
   * When passed version intervals that don't overlap, the lowest intervals are discarded until the remaining intervals do overlap.
   */
  case object Relaxed extends ConstraintReconciliation {
    def reconcile(versions: Seq[String]): Option[String] =
      if (versions.isEmpty)
        None
      else if (versions.lengthCompare(1) == 0)
        Some(versions.head)
      else {
        val (standard, latests) = splitStandard(versions)
        val retainedStandard =
          if (standard.isEmpty) None
          else if (standard.lengthCompare(1) == 0) standard.headOption
          else {
            val parsedConstraints = standard.map(VersionParse.versionConstraint)
            VersionConstraint.merge(parsedConstraints: _*)
              .getOrElse(VersionConstraint.relaxedMerge(parsedConstraints: _*))
              .repr
          }
        val retainedLatestOpt = retainLatestOpt(latests)
        if (latests.isEmpty)
          retainedStandard
        else
          retainedLatestOpt
      }
  }

  /**
   * The [[ConstraintReconciliation]] to be used for this [[VersionCompatibility]]
   *
   * The `Always` version compatibility corresponds to `Relaxed` constraint reconciliation (never fail to reconcile
   * versions during resolution).
   *
   * The other version compatibilities use `Default` as constraint reconciliation (may fail to reconcile versions during
   * resolution).
   */
  def apply(compatibility: VersionCompatibility): ConstraintReconciliation =
    compatibility match {
      case VersionCompatibility.Always => Relaxed
      case _ => Default
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy