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

coursier.Artifacts.scala Maven / Gradle / Ivy

The newest version!
package coursier

import java.io.File
import java.lang.{Boolean => JBoolean}

import coursier.cache.{ArtifactError, Cache}
import coursier.error.FetchError
import coursier.util.{Sync, Task}

import scala.collection.mutable
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}

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

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

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

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

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

  def resolutions: Seq[Resolution] =
    params.resolutions
  def classifiers: Set[Classifier] =
    params.classifiers
  def mainArtifactsOpt: Option[Boolean] =
    params.mainArtifactsOpt
  def artifactTypesOpt: Option[Set[Type]] =
    params.artifactTypesOpt
  def cache: Cache[F] =
    params.cache
  def otherCaches: Seq[Cache[F]] =
    params.otherCaches
  def transformArtifactsOpt: Option[Seq[Artifact] => Seq[Artifact]] =
    params.transformArtifactsOpt
  def S: Sync[F] =
    params.S

  def withResolution(resolution: Resolution): Artifacts[F] =
    withParams(params.copy(resolutions = Seq(resolution)))
  def withResolutions(resolutions: Seq[Resolution]): Artifacts[F] =
    withParams(params.copy(resolutions = resolutions))
  def withClassifiers(classifiers: Set[Classifier]): Artifacts[F] =
    withParams(params.copy(classifiers = classifiers))
  def withMainArtifacts(mainArtifacts: JBoolean): Artifacts[F] =
    withParams(params.copy(mainArtifactsOpt = Option(mainArtifacts).map(x => x)))
  def withArtifactTypes(artifactTypes: Set[Type]): Artifacts[F] =
    withParams(params.copy(artifactTypesOpt = Option(artifactTypes)))
  def withCache(cache: Cache[F]): Artifacts[F] =
    withParams(params.copy(cache = cache))
  def withOtherCaches(caches: Seq[Cache[F]]): Artifacts[F] =
    withParams(params.copy(otherCaches = caches))

  def transformArtifacts(f: Seq[Artifact] => Seq[Artifact]): Artifacts[F] =
    withParams(params.copy(transformArtifactsOpt = Some(params.transformArtifactsOpt.fold(f)(_ andThen f))))
  def noTransformArtifacts(): Artifacts[F] =
    withParams(params.copy(transformArtifactsOpt = None))
  def withTransformArtifacts(fOpt: Option[Seq[Artifact] => Seq[Artifact]]): Artifacts[F] =
    withParams(params.copy(transformArtifactsOpt = fOpt))

  def io: F[Seq[(Artifact, File)]] = {

    val a = params
      .resolutions
      .flatMap { r =>
        Artifacts.artifacts0(
          r,
          params.classifiers,
          params.mainArtifactsOpt,
          params.artifactTypesOpt
        ).map(_._3)
      }
      .distinct

    Artifacts.fetchArtifacts(
      params.transformArtifacts(a),
      params.cache,
      params.otherCaches: _*
    )(S)
  }

}

object Artifacts {

  // see Resolve.apply for why cache is passed here
  def apply[F[_]](cache: Cache[F] = Cache.default)(implicit S: Sync[F]): Artifacts[F] =
    new Artifacts(
      Params(
        Nil,
        Set(),
        None,
        None,
        cache,
        Nil,
        None,
        S
      )
    )

  implicit class ArtifactsTaskOps(private val artifacts: Artifacts[Task]) extends AnyVal {

    def future()(implicit ec: ExecutionContext = artifacts.params.cache.ec): Future[Seq[(Artifact, File)]] =
      artifacts.io.future()

    def either()(implicit ec: ExecutionContext = artifacts.params.cache.ec): Either[FetchError, Seq[(Artifact, File)]] = {

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

      Await.result(f, Duration.Inf)
    }

    def run()(implicit ec: ExecutionContext = artifacts.params.cache.ec): Seq[(Artifact, File)] = {
      val f = artifacts.io.future()
      Await.result(f, Duration.Inf)
    }

  }

  private[coursier] final case class Params[F[_]](
    resolutions: Seq[Resolution],
    classifiers: Set[Classifier],
    mainArtifactsOpt: Option[Boolean],
    artifactTypesOpt: Option[Set[Type]],
    cache: Cache[F],
    otherCaches: Seq[Cache[F]],
    transformArtifactsOpt: Option[Seq[Artifact] => Seq[Artifact]],
    S: Sync[F]
  ) {
    def transformArtifacts: Seq[Artifact] => Seq[Artifact] =
      transformArtifactsOpt.getOrElse(identity[Seq[Artifact]])

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

  def defaultTypes(
    classifiers: Set[Classifier] = Set.empty,
    mainArtifactsOpt: Option[Boolean] = None
  ): Set[Type] = {

    val mainArtifacts0 = mainArtifactsOpt.getOrElse(classifiers.isEmpty)

    val fromMainArtifacts =
      if (mainArtifacts0)
        Set[Type](Type.jar, Type.testJar, Type.bundle)
      else
        Set.empty[Type]

    val fromClassifiers = classifiers.flatMap {
      case Classifier.sources => Set(Type.source)
      case Classifier.javadoc => Set(Type.doc)
      case _ => Set.empty[Type]
    }

    fromMainArtifacts ++ fromClassifiers
  }


  private[coursier] def artifacts0(
    resolution: Resolution,
    classifiers: Set[Classifier],
    mainArtifactsOpt: Option[Boolean],
    artifactTypesOpt: Option[Set[Type]]
  ): Seq[(Dependency, Attributes, Artifact)] = {

    val mainArtifacts0 = mainArtifactsOpt.getOrElse(classifiers.isEmpty)

    val artifactTypes0 =
      artifactTypesOpt
        .getOrElse(defaultTypes(classifiers, mainArtifactsOpt))

    val main =
      if (mainArtifacts0)
        resolution.dependencyArtifacts(None)
      else
        Nil

    val classifiersArtifacts =
      if (classifiers.isEmpty)
        Nil
      else
        resolution.dependencyArtifacts(Some(classifiers.toSeq))

    val artifacts = (main ++ classifiersArtifacts).map {
      case (dep, attr, artifact) =>
        (dep.copy(attributes = dep.attributes.copy(classifier = attr.classifier)), attr, artifact)
    }

    if (artifactTypes0(Type.all))
      artifacts
    else
      artifacts.filter {
        case (_, attr, _) =>
          artifactTypes0(attr.`type`)
      }
  }

  private[coursier] def fetchArtifacts[F[_]](
    artifacts: Seq[Artifact],
    cache: Cache[F],
    otherCaches: Cache[F]*
  )(implicit
     S: Sync[F]
  ): F[Seq[(Artifact, File)]] = {

    val tasks = artifacts.map { artifact =>
      val file0 = cache.file(artifact)
      S.map(file0.run)(artifact.->)
    }

    val gathered = S.gather(tasks)

    val loggerOpt = cache.loggerOpt

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

    S.bind(task) { results =>

      val ignoredErrors = new mutable.ListBuffer[(Artifact, ArtifactError)]
      val errors = new mutable.ListBuffer[(Artifact, ArtifactError)]
      val artifactToFile = new mutable.ListBuffer[(Artifact, File)]

      results.foreach {
        case (artifact, Left(err)) if artifact.optional && err.notFound =>
          ignoredErrors += artifact -> err
        case (artifact, Left(err)) =>
          errors += artifact -> err
        case (artifact, Right(f)) =>
          artifactToFile += artifact -> f
      }

      if (otherCaches.isEmpty) {
        if (errors.isEmpty)
          S.point(artifactToFile.toList)
        else
          S.fromAttempt(Left(new FetchError.DownloadingArtifacts(errors.toList)))
      } else {
        if (errors.isEmpty && ignoredErrors.isEmpty)
          S.point(artifactToFile.toList)
        else
          S.map(fetchArtifacts(errors.map(_._1).toSeq ++ ignoredErrors.map(_._1).toSeq, otherCaches.head, otherCaches.tail: _*)) { l =>
            artifactToFile.toList ++ l
          }
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy