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

scala.coursier.core.Reconciliation.scala Maven / Gradle / Ivy

The newest version!
package coursier.core

/** Represents a reconciliation strategy given a dependency conflict.
  */
sealed abstract class Reconciliation {

  /** Reconcile multiple version candidate.
    *
    * Returns `None` in case of conflict.
    */
  def apply(versions: Seq[String]): Option[String]
}

object Reconciliation {
  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)
    }

  case object Default extends Reconciliation {
    def apply(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(Parse.versionConstraint)
            VersionConstraint.merge(parsedConstraints: _*)
              .flatMap(_.repr)
          }
        val retainedLatestOpt = retainLatestOpt(latests)

        if (standard.isEmpty)
          retainedLatestOpt
        else if (latests.isEmpty)
          retainedStandard
        else {
          val parsedIntervals = standard.map(Parse.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("&"))
        }
      }
  }

  case object Relaxed extends Reconciliation {
    def apply(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(Parse.versionConstraint)
            VersionConstraint.merge(parsedConstraints: _*)
              .getOrElse(VersionConstraint.relaxedMerge(parsedConstraints: _*))
              .repr
          }
        val retainedLatestOpt = retainLatestOpt(latests)
        if (latests.isEmpty)
          retainedStandard
        else
          retainedLatestOpt
      }
  }

  /** 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 Reconciliation {
    def apply(versions: Seq[String]): Option[String] =
      Default(versions)
  }

  /** Semantic versioning version reconciliation.
    *
    * This particular instance behaves the same as [[Default]] when used by
    * [[coursier.core.Resolution]]. Actual semantic versioning checks are handled by
    * `coursier.params.rule.Strict` with field `semVer = true`, which is set up by
    * `coursier.Resolve` when a SemVer reconciliation is added to it.
    */
  case object SemVer extends Reconciliation {
    def apply(versions: Seq[String]): Option[String] =
      Default(versions)
  }

  def apply(input: String): Option[Reconciliation] =
    input match {
      case "default" => Some(Default)
      case "relaxed" => Some(Relaxed)
      case "strict"  => Some(Strict)
      case "semver"  => Some(SemVer)
      case _         => None
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy