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

coursier.Resolve.scala Maven / Gradle / Ivy

The newest version!
package coursier

import coursier.cache.{Cache, CacheLogger}
import coursier.error.ResolutionError
import coursier.error.conflict.UnsatisfiedRule
import coursier.internal.Typelevel
import coursier.params.{Mirror, MirrorConfFile, ResolutionParams}
import coursier.params.rule.{Rule, RuleResolution}
import coursier.util._

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.language.higherKinds

final class Resolve[F[_]] private[coursier] (private val params: Resolve.Params[F]) {

  override def equals(obj: Any): Boolean =
    obj match {
      case other: Resolve[_] =>
        params == other.params
    }

  override def hashCode(): Int =
    17 + params.##

  override def toString: String =
    s"Resolve($params)"


  def dependencies: Seq[Dependency] =
    params.dependencies
  def repositories: Seq[Repository] =
    params.repositories
  def mirrors: Seq[Mirror] =
    params.mirrors
  def mirrorConfFiles: Seq[MirrorConfFile] =
    params.mirrorConfFiles
  def resolutionParams: ResolutionParams =
    params.resolutionParams
  def cache: Cache[F] =
    params.cache
  def throughOpt: Option[F[Resolution] => F[Resolution]] =
    params.throughOpt
  def transformFetcherOpt: Option[ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F]] =
    params.transformFetcherOpt
  def S: Sync[F] =
    params.S

  def finalRepositories: F[Seq[Repository]] =
    S.map(allMirrors) { mirrors0 =>
      repositories
        .map { repo =>
          val it = mirrors0
            .iterator
            .flatMap(_.matches(repo).iterator)
          if (it.hasNext)
            it.next()
          else
            repo
        }
        .distinct
    }

  private def withParams(params: Resolve.Params[F]): Resolve[F] =
    new Resolve(params)


  def withDependencies(dependencies: Seq[Dependency]): Resolve[F] =
    withParams(params.copy(dependencies = dependencies))
  def addDependencies(dependencies: Dependency*): Resolve[F] =
    withParams(params.copy(dependencies = params.dependencies ++ dependencies))

  def withRepositories(repositories: Seq[Repository]): Resolve[F] =
    withParams(params.copy(repositories = repositories))
  def addRepositories(repositories: Repository*): Resolve[F] =
    withParams(params.copy(repositories = params.repositories ++ repositories))

  def noMirrors: Resolve[F] =
    withParams(params.copy(
      mirrors = Nil,
      mirrorConfFiles = Nil
    ))

  def withMirrors(mirrors: Seq[Mirror]): Resolve[F] =
    withParams(params.copy(mirrors = mirrors))
  def addMirrors(mirrors: Mirror*): Resolve[F] =
    withParams(params.copy(mirrors = params.mirrors ++ mirrors))

  def withMirrorConfFiles(mirrorConfFiles: Seq[MirrorConfFile]): Resolve[F] =
    withParams(params.copy(mirrorConfFiles = mirrorConfFiles))
  def addMirrorConfFiles(mirrorConfFiles: MirrorConfFile*): Resolve[F] =
    withParams(params.copy(mirrorConfFiles = params.mirrorConfFiles ++ mirrorConfFiles))

  def withResolutionParams(resolutionParams: ResolutionParams): Resolve[F] =
    withParams(params.copy(resolutionParams = resolutionParams))

  def withCache(cache: Cache[F]): Resolve[F] =
    withParams(params.copy(cache = cache))

  def transformResolution(f: F[Resolution] => F[Resolution]): Resolve[F] =
    withParams(params.copy(throughOpt = Some(params.throughOpt.fold(f)(_ andThen f))))
  def noTransformResolution(): Resolve[F] =
    withParams(params.copy(throughOpt = None))
  def withTransformResolution(fOpt: Option[F[Resolution] => F[Resolution]]): Resolve[F] =
    withParams(params.copy(throughOpt = fOpt))

  def transformFetcher(f: ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F]): Resolve[F] =
    withParams(params.copy(transformFetcherOpt = Some(params.transformFetcherOpt.fold(f)(_ andThen f))))
  def noTransformFetcher(): Resolve[F] =
    withParams(params.copy(transformFetcherOpt = None))
  def withTransformFetcher(fOpt: Option[ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F]]): Resolve[F] =
    withParams(params.copy(transformFetcherOpt = fOpt))

  private def allMirrors0 =
    mirrors ++ mirrorConfFiles.flatMap(_.mirrors())

  def allMirrors: F[Seq[Mirror]] =
    S.delay(allMirrors0)


  private def fetchVia: F[ResolutionProcess.Fetch[F]] = {
    val fetchs = params.cache.fetchs
    S.map(finalRepositories)(r => ResolutionProcess.fetch(r, fetchs.head, fetchs.tail: _*)(S))
  }

  private def ioWithConflicts0(fetch: ResolutionProcess.Fetch[F]): F[(Resolution, Seq[UnsatisfiedRule])] = {

    val initialRes = Resolve.initialResolution(params.dependencies, params.resolutionParams)

    def run(res: Resolution): F[Resolution] = {
      val t = Resolve.runProcess(res, fetch, params.resolutionParams.maxIterations, params.cache.loggerOpt)(S)
      params.through(t)
    }

    def validate0(res: Resolution): F[Resolution] =
      Resolve.validate(res).either match {
        case Left(errors) =>
          val err = ResolutionError.from(errors.head, errors.tail: _*)
          S.fromAttempt(Left(err))
        case Right(()) =>
          S.point(res)
      }

    def recurseOnRules(res: Resolution, rules: Seq[(Rule, RuleResolution)]): F[(Resolution, List[UnsatisfiedRule])] =
      rules match {
        case Seq() =>
          S.point((res, Nil))
        case Seq((rule, ruleRes), t @ _*) =>
          rule.enforce(res, ruleRes) match {
            case Left(c) =>
              S.fromAttempt(Left(c))
            case Right(Left(c)) =>
              S.map(recurseOnRules(res, t)) {
                case (res0, conflicts) =>
                  (res0, c :: conflicts)
              }
            case Right(Right(None)) =>
              recurseOnRules(res, t)
            case Right(Right(Some(newRes))) =>
              S.bind(S.bind(run(newRes.copy(dependencies = Set.empty)))(validate0)) { res0 =>
                // FIXME check that the rule passes after it tried to address itself
                recurseOnRules(res0, t)
              }
          }
      }

    def validateAllRules(res: Resolution, rules: Seq[(Rule, RuleResolution)]): F[Resolution] =
      rules match {
        case Seq() =>
          S.point(res)
        case Seq((rule, _), t @ _*) =>
          rule.check(res) match {
            case Some(c) =>
              S.fromAttempt(Left(c))
            case None =>
              validateAllRules(res, t)
          }
      }

    S.bind(S.bind(run(initialRes))(validate0)) { res0 =>
      S.bind(recurseOnRules(res0, params.resolutionParams.rules)) {
        case (res0, conflicts) =>
          S.map(validateAllRules(res0, params.resolutionParams.rules)) { _ =>
            (res0, conflicts)
          }
      }
    }
  }

  def ioWithConflicts: F[(Resolution, Seq[UnsatisfiedRule])] =
    S.bind(fetchVia) { f =>
      val fetchVia0 = params.transformFetcher(f)
      ioWithConflicts0(fetchVia0)
    }

  def io: F[Resolution] =
    S.map(ioWithConflicts)(_._1)

}

object Resolve extends PlatformResolve {

  private[coursier] def defaultParams[F[_]](cache: Cache[F])(implicit S: Sync[F]) =
    Params(
      Nil,
      defaultRepositories,
      defaultMirrorConfFiles,
      Nil,
      ResolutionParams(),
      cache,
      None,
      None,
      S
    )

  // Ideally, cache shouldn't be passed here, and a default one should be created from S.
  // But that would require changes in Sync or an extra typeclass (similar to Async in cats-effect)
  // to allow to use the default cache on Scala.JS with a generic F.
  def apply[F[_]](cache: Cache[F] = Cache.default)(implicit S: Sync[F]): Resolve[F] =
    new Resolve(defaultParams(cache))

  implicit class ResolveTaskOps(private val resolve: Resolve[Task]) extends AnyVal {

    def future()(implicit ec: ExecutionContext = resolve.params.cache.ec): Future[Resolution] =
      resolve.io.future()

    def either()(implicit ec: ExecutionContext = resolve.params.cache.ec): Either[ResolutionError, Resolution] = {

      val f = resolve
        .io
        .map(Right(_))
        .handle { case ex: ResolutionError => Left(ex) }
        .future()

      Await.result(f, Duration.Inf)
    }

    def run()(implicit ec: ExecutionContext = resolve.params.cache.ec): Resolution = {
      val f = future()(ec)
      Await.result(f, Duration.Inf)
    }

  }

  private[coursier] final case class Params[F[_]](
    dependencies: Seq[Dependency],
    repositories: Seq[Repository],
    mirrorConfFiles: Seq[MirrorConfFile],
    mirrors: Seq[Mirror],
    resolutionParams: ResolutionParams,
    cache: Cache[F],
    throughOpt: Option[F[Resolution] => F[Resolution]],
    transformFetcherOpt: Option[ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F]],
    S: Sync[F]
  ) {
    def through: F[Resolution] => F[Resolution] =
      throughOpt.getOrElse(identity[F[Resolution]])
    def transformFetcher: ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F] =
      transformFetcherOpt.getOrElse(identity[ResolutionProcess.Fetch[F]])

    override def toString: String =
      productIterator.mkString("ResolveParams(", ", ", ")")
  }

  private[coursier] def initialResolution(
    dependencies: Seq[Dependency],
    params: ResolutionParams = ResolutionParams()
  ): Resolution = {

    val forceScalaVersions =
      if (params.doForceScalaVersion) {
        val scalaOrg =
          if (params.typelevel) Organization("org.typelevel")
          else Organization("org.scala-lang")
        Seq(
          Module(scalaOrg, ModuleName("scala-library")) -> params.selectedScalaVersion,
          Module(scalaOrg, ModuleName("org.scala-lang:scala-reflect")) -> params.selectedScalaVersion,
          Module(scalaOrg, ModuleName("org.scala-lang:scala-compiler")) -> params.selectedScalaVersion,
          Module(scalaOrg, ModuleName("org.scala-lang:scalap")) -> params.selectedScalaVersion
        )
      } else
        Nil

    val mapDependencies = {
      val l = (if (params.typelevel) Seq(Typelevel.swap) else Nil) ++
        (if (params.doForceScalaVersion) Seq(coursier.core.Resolution.forceScalaVersion(params.selectedScalaVersion)) else Nil)

      l.reduceOption((f, g) => dep => f(g(dep)))
    }

    Resolution(
      dependencies,
      forceVersions = params.forceVersion ++ forceScalaVersions,
      filter = Some(dep => params.keepOptionalDependencies || !dep.optional),
      userActivations =
        if (params.profiles.isEmpty) None
        else Some(params.profiles.iterator.map(p => if (p.startsWith("!")) p.drop(1) -> false else p -> true).toMap),
      forceProperties = params.forcedProperties,
      mapDependencies = mapDependencies
    )
  }

  private[coursier] def runProcess[F[_]](
    initialResolution: Resolution,
    fetch: ResolutionProcess.Fetch[F],
    maxIterations: Int = 200,
    loggerOpt: Option[CacheLogger] = None
  )(implicit S: Sync[F]): F[Resolution] = {

    val task = initialResolution
      .process
      .run(fetch, maxIterations)

    loggerOpt match {
      case None =>
        task
      case Some(logger) =>
        S.bind(S.delay(logger.init())) { _ =>
          S.bind(S.attempt(task)) { a =>
            S.bind(S.delay(logger.stop())) { _ =>
              S.fromAttempt(a)
            }
          }
        }
    }
  }

  def validate(res: Resolution): ValidationNel[ResolutionError, Unit] = {

    val checkDone: ValidationNel[ResolutionError, Unit] =
      if (res.isDone)
        ValidationNel.success(())
      else
        ValidationNel.failure(new ResolutionError.MaximumIterationReached(res))

    val checkErrors: ValidationNel[ResolutionError, Unit] = res
      .errors
      .map {
        case ((module, version), errors) =>
          new ResolutionError.CantDownloadModule(res, module, version, errors)
      } match {
        case Seq() =>
          ValidationNel.success(())
        case Seq(h, t @ _*) =>
          ValidationNel.failures(h, t: _*)
      }

    val checkConflicts: ValidationNel[ResolutionError, Unit] =
      if (res.conflicts.isEmpty)
        ValidationNel.success(())
      else
        ValidationNel.failure(
          new ResolutionError.ConflictingDependencies(
            res,
            res.conflicts.map { dep =>
              dep.copy(
                version = res.projectCache.get(dep.moduleVersion).fold(dep.version)(_._2.actualVersion)
              )
            }
          )
        )

    checkDone.zip(checkErrors, checkConflicts).map {
      case ((), (), ()) =>
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy