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

scala.coursier.Resolve.scala Maven / Gradle / Ivy

There is a newer version: 2.1.25-M3
Show newest version
package coursier

import java.util.concurrent.ConcurrentHashMap

import coursier.cache.{Cache, CacheLogger}
import coursier.core.{
  Activation,
  BomDependency,
  Configuration,
  Dependency,
  DependencySet,
  Exclusions,
  MinimizedExclusions,
  Module,
  ModuleName,
  Organization,
  Reconciliation,
  Repository,
  Resolution,
  ResolutionProcess
}
import coursier.error.ResolutionError
import coursier.error.conflict.UnsatisfiedRule
import coursier.graph.ReverseModuleTree
import coursier.internal.Typelevel
import coursier.params.{Mirror, MirrorConfFile, ResolutionParams}
import coursier.params.rule.{Rule, RuleResolution}
import coursier.util._
import coursier.util.Monad.ops._
import coursier.util.StringInterpolators._
import dataclass.{data, since}

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

// format: off
@data class Resolve[F[_]](
  cache: Cache[F],
  dependencies: Seq[Dependency] = Nil,
  repositories: Seq[Repository] = Resolve.defaultRepositories,
  mirrorConfFiles: Seq[MirrorConfFile] = Resolve.defaultMirrorConfFiles,
  mirrors: Seq[Mirror] = Nil,
  resolutionParams: ResolutionParams = ResolutionParams(),
  throughOpt: Option[F[Resolution] => F[Resolution]] = None,
  transformFetcherOpt: Option[ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F]] = None,
  @since
    initialResolution: Option[Resolution] = None,
  @since
    confFiles: Seq[Resolve.Path] = Resolve.defaultConfFiles,
  preferConfFileDefaultRepositories: Boolean = true,
  @since("2.1.12")
  @deprecated("Workaround for former uses of Resolution.mapDependencies, prefer relying on ResolutionParams", "2.1.12")
    mapDependenciesOpt: Option[Dependency => Dependency] = None,
  @since("2.1.16")
  @deprecated("Use boms instead", "2.1.18")
    bomDependencies: Seq[Dependency] = Nil,
  @since("2.1.18")
  @deprecated("Use boms instead", "2.1.19")
    bomModuleVersions: Seq[(Module, String)] = Nil,
  @since("2.1.19")
    boms: Seq[BomDependency] = Nil
)(implicit
  sync: Sync[F]
) {
  // format: on

  private def S = sync

  private def through: F[Resolution] => F[Resolution] =
    throughOpt.getOrElse(identity[F[Resolution]])
  private def transformFetcher: ResolutionProcess.Fetch[F] => ResolutionProcess.Fetch[F] =
    transformFetcherOpt.getOrElse(identity[ResolutionProcess.Fetch[F]])

  def finalDependencies: Seq[Dependency] = {

    val exclusions = MinimizedExclusions(resolutionParams.exclusions)

    dependencies
      .filter { dep =>
        exclusions(dep.module.organization, dep.module.name)
      }
      .map { dep =>
        dep.withMinimizedExclusions(
          dep.minimizedExclusions.join(exclusions)
        )
      }
  }

  def finalRepositories: F[Seq[Repository]] = {
    val repositories0 =
      if (preferConfFileDefaultRepositories) {
        val defaultFromConfOpt = confFiles
          .iterator
          .flatMap(Resolve.confFileRepositories(_).iterator)
          .take(1)
          .toList
          .headOption
        defaultFromConfOpt.getOrElse(repositories)
      }
      else
        repositories
    allMirrors.map(Mirror.replace(repositories0, _))
  }

  def addDependencies(dependencies: Dependency*): Resolve[F] =
    withDependencies(this.dependencies ++ dependencies)
  @deprecated("Use addBom or addBomConfigs instead", "2.1.18")
  def addBomDependencies(bomDependencies: Dependency*): Resolve[F] =
    withBomDependencies(this.bomDependencies ++ bomDependencies)
  def addBom(bomModule: Module, bomVersion: String): Resolve[F] =
    withBoms(this.boms :+ BomDependency(bomModule, bomVersion, Configuration.empty))
  def addBom(bomModule: Module, bomVersion: String, bomConfig: Configuration): Resolve[F] =
    withBoms(this.boms :+ BomDependency(bomModule, bomVersion, bomConfig))
  def addBom(bomDep: BomDependency): Resolve[F] =
    withBoms(this.boms :+ bomDep)
  def addBoms(bomModuleVersions: (Module, String)*): Resolve[F] =
    withBoms(
      this.boms ++
        bomModuleVersions.map(t => BomDependency(t._1, t._2, Configuration.empty))
    )
  def addBomConfigs(boms: BomDependency*): Resolve[F] =
    withBoms(this.boms ++ boms)

  def addRepositories(repositories: Repository*): Resolve[F] =
    withRepositories(this.repositories ++ repositories)

  def noMirrors: Resolve[F] =
    withMirrors(Nil).withMirrorConfFiles(Nil)

  def addMirrors(mirrors: Mirror*): Resolve[F] =
    withMirrors(this.mirrors ++ mirrors)

  def addMirrorConfFiles(mirrorConfFiles: MirrorConfFile*): Resolve[F] =
    withMirrorConfFiles(this.mirrorConfFiles ++ mirrorConfFiles)
  def addConfFiles(confFiles: Resolve.Path*): Resolve[F] =
    withConfFiles(this.confFiles ++ confFiles)

  def mapResolutionParams(f: ResolutionParams => ResolutionParams): Resolve[F] =
    withResolutionParams(f(resolutionParams))

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

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

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

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

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

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

    val initialRes = Resolve.initialResolution(
      finalDependencies,
      resolutionParams,
      initialResolution,
      mapDependenciesOpt,
      bomDependencies.map(_.asBomDependency) ++
        bomModuleVersions.map(t => BomDependency(t._1, t._2, Configuration.empty)) ++
        boms
    )

    def run(res: Resolution): F[Resolution] = {
      val t = Resolve.runProcess(res, fetch, resolutionParams.maxIterations, cache.loggerOpt)(S)
      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)) =>
              recurseOnRules(res, t).map {
                case (res0, conflicts) =>
                  (res0, c :: conflicts)
              }
            case Right(Right(None)) =>
              recurseOnRules(res, t)
            case Right(Right(Some(newRes))) =>
              run(newRes.withDependencySet(DependencySet.empty)).flatMap(validate0).flatMap {
                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)
          }
      }

    for {
      res0 <- run(initialRes)
      res1 <- validate0(res0)
      t    <- recurseOnRules(res1, resolutionParams.actualRules)
      (res2, conflicts) = t
      _ <- validateAllRules(res2, resolutionParams.actualRules)
    } yield (res2, conflicts)
  }

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

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

}

object Resolve extends PlatformResolve {

  def apply(): Resolve[Task] =
    Resolve(Cache.default)

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

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

    def either()(implicit
      ec: ExecutionContext = resolve.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.cache.ec): Resolution = {
      val f = future()(ec)
      Await.result(f, Duration.Inf)
    }

  }

  private[coursier] def initialResolution(
    dependencies: Seq[Dependency],
    params: ResolutionParams = ResolutionParams(),
    initialResolutionOpt: Option[Resolution] = None,
    mapDependenciesOpt: Option[Dependency => Dependency] = None,
    boms: Seq[BomDependency] = Nil
  ): Resolution = {
    import coursier.core.{Resolution => CoreResolution}

    val scalaOrg =
      if (params.typelevel) Organization("org.typelevel")
      else Organization("org.scala-lang")

    val forceScalaVersions =
      if (params.doForceScalaVersion)
        if (params.selectedScalaVersion.startsWith("3"))
          Seq(
            Module(
              scalaOrg,
              ModuleName("scala3-library_3"),
              Map.empty
            ) -> params.selectedScalaVersion,
            Module(
              scalaOrg,
              ModuleName("scala3-compiler_3"),
              Map.empty
            ) -> params.selectedScalaVersion
          )
        else
          Seq(
            Module(scalaOrg, ModuleName("scala-library"), Map.empty) -> params.selectedScalaVersion,
            Module(
              scalaOrg,
              ModuleName("scala-compiler"),
              Map.empty
            )                                                        -> params.selectedScalaVersion,
            Module(scalaOrg, ModuleName("scala-reflect"), Map.empty) -> params.selectedScalaVersion,
            Module(scalaOrg, ModuleName("scalap"), Map.empty)        -> params.selectedScalaVersion
          )
      else
        Nil

    val mapDependencies = {
      val l = mapDependenciesOpt.toSeq ++
        (if (params.typelevel) Seq(Typelevel.swap) else Nil) ++
        (if (params.doForceScalaVersion)
           Seq(CoreResolution.overrideScalaModule(params.selectedScalaVersion, scalaOrg))
         else Nil) ++
        (if (params.doOverrideFullSuffix)
           Seq(CoreResolution.overrideFullSuffix(params.selectedScalaVersion))
         else Nil)

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

    val reconciliation: Option[Module => Reconciliation] = {
      val actualReconciliation = params.actualReconciliation
      if (actualReconciliation.isEmpty) None
      else
        Some {
          val cache = new ConcurrentHashMap[Module, Reconciliation]
          m =>
            val reconciliation = cache.get(m)
            if (reconciliation == null) {
              val rec = actualReconciliation.find(_._1.matches(m)) match {
                case Some((_, r)) => r
                case None         => Reconciliation.Default
              }
              val prev = cache.putIfAbsent(m, rec)
              if (prev == null)
                rec
              else
                prev
            }
            else
              reconciliation
        }
    }

    val baseRes = initialResolutionOpt.getOrElse(Resolution())

    baseRes
      .withRootDependencies(dependencies)
      .withDependencySet(DependencySet.empty)
      .withForceVersions(params.forceVersion ++ forceScalaVersions)
      .withConflicts(Set.empty)
      .withFilter(Some((dep: Dependency) => params.keepOptionalDependencies || !dep.optional))
      .withReconciliation(reconciliation)
      .withOsInfo(
        params.osInfoOpt.getOrElse {
          if (params.useSystemOsInfo)
            // call from Sync[F].delay?
            Activation.Os.fromProperties(sys.props.toMap)
          else
            Activation.Os.empty
        }
      )
      .withJdkVersion(
        params.jdkVersionOpt.orElse {
          if (params.useSystemJdkVersion)
            // call from Sync[F].delay?
            sys.props.get("java.version").flatMap(coursier.core.Parse.version)
          else
            None
        }
      )
      .withUserActivations(
        if (params.profiles.isEmpty) None
        else Some(params.profiles.iterator.map(p =>
          if (p.startsWith("!")) p.drop(1) -> false else p -> true
        ).toMap)
      )
      .withMapDependencies(mapDependencies)
      .withExtraProperties(params.properties)
      .withForceProperties(params.forcedProperties)
      .withDefaultConfiguration(params.defaultConfiguration)
      .withKeepProvidedDependencies(params.keepProvidedDependencies.getOrElse(false))
      .withForceDepMgmtVersions(params.forceDepMgmtVersions.getOrElse(false))
      .withEnableDependencyOverrides(
        params.enableDependencyOverrides.getOrElse(Resolution.enableDependencyOverridesDefault)
      )
      .withBoms(boms)
  }

  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 = ResolutionProcess(initialResolution).run(fetch, maxIterations)

    loggerOpt match {
      case None         => task
      case Some(logger) => logger.using(task)
    }
  }

  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
          )
        )

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

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy