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

coursier.cli.util.JsonReport.scala Maven / Gradle / Ivy

The newest version!
package coursier.cli.util

import java.io.File
import java.util.Objects

import coursier.Artifact
import coursier.core.{Attributes, Dependency, Resolution}
import coursier.util.Print

import scala.collection.mutable
import scala.collection.parallel.ParSeq
import argonaut._
import Argonaut._

final case class JsonPrintRequirement(fileByArtifact: Map[String, File], depToArtifacts: Map[Dependency, Vector[Artifact]])

final case class DepNode(coord: String, files: Vector[(String, String)], dependencies: Set[String])

final case class ReportNode(conflict_resolution: Map[String, String], dependencies: Vector[DepNode], version: String)

/**
  * FORMAT_VERSION_NUMBER: Version number for identifying the export file format output. This
  * version number should change when there is a change to the output format.
  *
  * Major Version 1.x.x : Increment this field when there is a major format change
  * Minor Version x.1.x : Increment this field when there is a minor change that breaks backward
  *   compatibility for an existing field or a field is removed.
  * Patch version x.x.1 : Increment this field when a minor format change that just adds information
  *   that an application can safely ignore.
  *
  * Note format changes in cli/README.md and update the Changelog section.
  */
object ReportNode {
  import argonaut.ArgonautShapeless._
  implicit val encodeJson = EncodeJson.of[ReportNode]
  implicit val decodeJson = DecodeJson.of[ReportNode]
  val version = "0.0.1"
}


object JsonReport {

  private val printer = PrettyParams.nospace.copy(preserveOrder = true)

  def apply[T](roots: IndexedSeq[T], conflictResolutionForRoots: Map[String, String])
              (children: T => Seq[T], reconciledVersionStr: T => String, requestedVersionStr: T => String, getFiles: T => Seq[(String, String)]): String = {

    val rootDeps: ParSeq[DepNode] = roots.par.map(r => {

      /**
        * Same printing mechanism as [[coursier.util.Tree#recursivePrint]]
        */
      def flattenDeps(elems: Seq[T], ancestors: Set[T], acc: mutable.Set[String]): Unit = {
        val unseenElems: Seq[T] = elems.filterNot(ancestors.contains)
        for (elem <- unseenElems) {
          val depElems = children(elem)
          acc ++= depElems.map(reconciledVersionStr(_))

          if (depElems.nonEmpty) {
            flattenDeps(children(elem), ancestors + elem, acc)
          }
        }
      }

      val acc = scala.collection.mutable.Set[String]()
      flattenDeps(Seq(r), Set(), acc)
      DepNode(reconciledVersionStr(r), getFiles(r).toVector, acc.toSet)

    })
    val report = ReportNode(conflictResolutionForRoots, rootDeps.toVector, ReportNode.version)
    printer.pretty(report.asJson)
  }

}


final case class JsonElem(dep: Dependency,
                          artifacts: Seq[(Dependency, Artifact)] = Seq(),
                          jsonPrintRequirement: Option[JsonPrintRequirement],
                          resolution: Resolution,
                          colors: Boolean,
                          printExclusions: Boolean,
                          excluded: Boolean) {

  val (red, yellow, reset) =
    if (colors)
      (Console.RED, Console.YELLOW, Console.RESET)
    else
      ("", "", "")

  // This is used to printing json output
  // Seq of (classifier, file path) tuple
  lazy val downloadedFiles: Seq[(String, String)] = {
    jsonPrintRequirement match {
      case Some(req) =>
        req.depToArtifacts.getOrElse(dep, Seq())
          .map(x => (x.classifier, req.fileByArtifact.get(x.url)))
          .filter(_._2.isDefined)
          .map(x => (x._1, x._2.get.getPath))
      case None => Seq()
    }
  }

  lazy val reconciledVersion: String = resolution.reconciledVersions
    .getOrElse(dep.module, dep.version)

  // These are used to printing json output
  val reconciledVersionStr = s"${dep.module}:$reconciledVersion"
  val requestedVersionStr = s"${dep.module}:${dep.version}"

  lazy val repr =
    if (excluded)
      resolution.reconciledVersions.get(dep.module) match {
        case None =>
          s"$yellow(excluded)$reset ${dep.module}:${dep.version}"
        case Some(version) =>
          val versionMsg =
            if (version == dep.version)
              "this version"
            else
              s"version $version"

          s"${dep.module}:${dep.version} " +
            s"$red(excluded, $versionMsg present anyway)$reset"
      }
    else {
      val versionStr =
        if (reconciledVersion == dep.version)
          dep.version
        else {
          val assumeCompatibleVersions = Print.compatibleVersions(dep.version, reconciledVersion)

          (if (assumeCompatibleVersions) yellow else red) +
            s"${dep.version} -> $reconciledVersion" +
            (if (assumeCompatibleVersions || colors) "" else " (possible incompatibility)") +
            reset
        }

      s"${dep.module}:$versionStr"
    }

  lazy val children: Seq[JsonElem] =
    if (excluded)
      Nil
    else {
      val dep0 = dep.copy(version = reconciledVersion)

      val dependencies = resolution.dependenciesOf(
        dep0,
        withReconciledVersions = false
      ).sortBy { trDep =>
        (trDep.module.organization, trDep.module.name, trDep.version)
      }

      def excluded = resolution
        .dependenciesOf(
          dep0.copy(exclusions = Set.empty),
          withReconciledVersions = false
        )
        .sortBy { trDep =>
          (trDep.module.organization, trDep.module.name, trDep.version)
        }
        .map(_.moduleVersion)
        .filterNot(dependencies.map(_.moduleVersion).toSet).map {
        case (mod, ver) =>
          JsonElem(
            Dependency(mod, ver, "", Set.empty, Attributes("", ""), false, false),
            artifacts,
            jsonPrintRequirement,
            resolution,
            colors,
            printExclusions,
            excluded = true
          )
      }

      dependencies.map(JsonElem(_, artifacts, jsonPrintRequirement, resolution, colors, printExclusions, excluded = false)) ++
        (if (printExclusions) excluded else Nil)
    }

    /**
      * Override the hashcode to explicitly exclude `children`, because children will result in recursive hash on
      * children's children, causing performance issue. Hash collision should be rare, but when that happens, the
      * default equality check should take of the recursive aspect of `children`.
      */
    override def hashCode(): Int = Objects.hash(dep, requestedVersionStr, reconciledVersion, downloadedFiles)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy