From 6580fb59f3c80efdf39c5336d325ee84195133d9 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Tue, 23 Oct 2012 11:26:11 +0200 Subject: [PATCH] proper distinction between what is a moduleid and what is extra module info fixes #18: filter revisions per caller and evtl. display eviction info --- notes/0.7.0.markdown | 1 + .../sbt/graph/IvyGraphMLDependencies.scala | 110 +++++++++++------- .../net/virtualvoid/sbt/graph/Plugin.scala | 10 +- 3 files changed, 74 insertions(+), 47 deletions(-) diff --git a/notes/0.7.0.markdown b/notes/0.7.0.markdown index ce1209d10..a4e96c1a4 100644 --- a/notes/0.7.0.markdown +++ b/notes/0.7.0.markdown @@ -5,5 +5,6 @@ New features in this version: * The tree output from previous versions is now available with `dependency-tree`. * New task `what-depends-on` showing reverse dependency tree for a selected module (incl. tab-completion for modules) * Don't fail in cases of a missing dependency. Show errors directly in the output. + * Show info about evicted versions. * Works with sbt 0.12.1. The ivy report files were moved to a new location making an update necessary. diff --git a/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala b/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala index 329cd2bdc..b817da7db 100644 --- a/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala @@ -22,34 +22,40 @@ import collection.mutable.HashMap import collection.mutable.MultiMap import collection.mutable.{Set => MSet} import sbt.{ConsoleLogger, Graph} -import xml.{Document, XML, Node} +import xml.{NodeSeq, Document, XML, Node} import com.github.mdr.ascii.layout import layout._ import sbinary.{Format, DefaultProtocol} object IvyGraphMLDependencies extends App { - case class Module(organisation: String, name: String, version: String, error: Option[String] = None) { - def id: String = organisation+":"+name+":"+version - + case class ModuleId(organisation: String, + name: String, + version: String) { + def idString: String = organisation+":"+name+":"+version + } + case class Module(id: ModuleId, + evictedByVersion: Option[String] = None, + error: Option[String] = None) { def hadError: Boolean = error.isDefined - - override def hashCode(): Int = id.hashCode - override def equals(p1: Any): Boolean = p1 match { - case m: Module => id == m.id - case _ => false - } } - case class ModuleGraph(nodes: Seq[Module], edges: Seq[(Module, Module)]) { - lazy val dependencyMap: Map[Module, Seq[Module]] = { - val m = new HashMap[Module, MSet[Module]] with MultiMap[Module, Module] - edges.foreach { case (from, to) => m.addBinding(from, to) } - m.toMap.mapValues(_.toSeq.sortBy(_.id)) + type Edge = (ModuleId, ModuleId) + + case class ModuleGraph(nodes: Seq[Module], edges: Seq[Edge]) { + lazy val modules: Map[ModuleId, Module] = + nodes.map(n => (n.id, n)).toMap + + def module(id: ModuleId): Module = modules(id) + + lazy val dependencyMap: Map[ModuleId, Seq[Module]] = { + val m = new HashMap[ModuleId, MSet[Module]] with MultiMap[ModuleId, Module] + edges.foreach { case (from, to) => m.addBinding(from, module(to)) } + m.toMap.mapValues(_.toSeq.sortBy(_.id.idString)) } - lazy val reverseDependencyMap: Map[Module, Seq[Module]] = { - val m = new HashMap[Module, MSet[Module]] with MultiMap[Module, Module] - edges.foreach { case (from, to) => m.addBinding(to, from) } - m.toMap.mapValues(_.toSeq.sortBy(_.id)) + lazy val reverseDependencyMap: Map[ModuleId, Seq[Module]] = { + val m = new HashMap[ModuleId, MSet[Module]] with MultiMap[ModuleId, Module] + edges.foreach { case (from, to) => m.addBinding(to, module(from)) } + m.toMap.mapValues(_.toSeq.sortBy(_.id.idString)) } } @@ -57,36 +63,49 @@ object IvyGraphMLDependencies extends App { buildGraph(buildDoc(ivyReportFile)) def buildGraph(doc: Document): ModuleGraph = { - val edges = for { - mod <- doc \ "dependencies" \ "module" + def edgesForModule(id: ModuleId, revision: NodeSeq, rev: String): Seq[Edge] = + for { + caller <- revision \ "caller" if caller.attribute("rev").get.text == rev + callerModule = moduleIdFromElement(caller, caller.attribute("callerrev").get.text) + } yield (moduleIdFromElement(caller, caller.attribute("callerrev").get.text), id) + + val moduleEdges: Seq[(Module, Seq[Edge])] = for { + mod <- doc \ "dependencies" \ "module" revision <- mod \ "revision" - caller <- revision \ "caller" - callerModule = nodeFromElement(caller, caller.attribute("callerrev").get.text) - depModule = nodeFromElement(mod, revision.attribute("name").get.text, revision.attribute("error").map(_.text)) - } yield (callerModule, depModule) + rev = revision.attribute("name").get.text + moduleId = moduleIdFromElement(mod, rev) + module = Module(moduleId, + evictedByVersion = (revision \ "evicted-by").headOption.flatMap(_.attribute("rev").map(_.text)), + error = revision.attribute("error").map(_.text)) + } yield (module, edgesForModule(moduleId, revision, rev)) - val nodes = edges.flatMap(e => Seq(e._1, e._2)).distinct + val (nodes, edges) = moduleEdges.unzip - ModuleGraph(nodes, edges) + val info = (doc \ "info").head + def infoAttr(name: String): String = + info.attribute(name).getOrElse(throw new IllegalArgumentException("Missing attribute "+name)).text + val rootModule = Module(ModuleId(infoAttr("organisation"), infoAttr("module"), infoAttr("revision"))) + + ModuleGraph(rootModule +: nodes, edges.flatten) } - def reverseGraphStartingAt(graph: ModuleGraph, root: Module): ModuleGraph = { + def reverseGraphStartingAt(graph: ModuleGraph, root: ModuleId): ModuleGraph = { val deps = graph.reverseDependencyMap - def visit(module: Module, visited: Set[Module]): Seq[(Module, Module)] = + def visit(module: ModuleId, visited: Set[ModuleId]): Seq[(ModuleId, ModuleId)] = if (visited(module)) Nil else deps.get(module) match { case Some(deps) => deps.flatMap { to => - (module, to) +: visit(to, visited + module) + (module, to.id) +: visit(to.id, visited + module) } case None => Nil } val edges = visit(root, Set.empty) - val nodes = edges.foldLeft(Set.empty[Module])((set, edge) => set + edge._1 + edge._2) + val nodes = edges.foldLeft(Set.empty[ModuleId])((set, edge) => set + edge._1 + edge._2).map(graph.module) ModuleGraph(nodes.toSeq, edges) } @@ -97,21 +116,27 @@ object IvyGraphMLDependencies extends App { val deps = graph.dependencyMap // there should only be one root node (the project itself) - val roots = graph.nodes.filter(n => !graph.edges.exists(_._2 == n)).sortBy(_.id) + val roots = graph.nodes.filter(n => !graph.edges.exists(_._2 == n.id)).sortBy(_.id.idString) roots.map { root => - Graph.toAscii[Module](root, node => deps.getOrElse(node, Seq.empty[Module]), displayModule) + Graph.toAscii[Module](root, node => deps.getOrElse(node.id, Seq.empty[Module]), displayModule) }.mkString("\n") } def displayModule(module: Module): String = - red(module.id + module.error.map(" (error: "+_+")").getOrElse(""), module.hadError) + red(module.id.idString + + module.error.map(" (error: "+_+")").getOrElse("") + + module.evictedByVersion.map(_ formatted " (evicted by: %s)").getOrElse(""), module.hadError) private def buildAsciiGraph(moduleGraph: ModuleGraph): layout.Graph[String] = { def renderVertex(module: Module): String = - module.name + "\n" + module.organisation + "\n" + module.version + module.error.map("\nerror: "+_).getOrElse("") + module.id.name + "\n" + + module.id.organisation + "\n" + + module.id.version + + module.error.map("\nerror: "+_).getOrElse("") + + module.evictedByVersion.map(_ formatted "\nevicted by: %s").getOrElse("") val vertices = moduleGraph.nodes.map(renderVertex).toList - val edges = moduleGraph.edges.toList.map { case (from, to) ⇒ (renderVertex(from), renderVertex(to)) } + val edges = moduleGraph.edges.toList.map { case (from, to) ⇒ (renderVertex(moduleGraph.module(from)), renderVertex(moduleGraph.module(to))) } layout.Graph(vertices, edges) } @@ -119,15 +144,15 @@ object IvyGraphMLDependencies extends App { val nodesXml = for (n <- graph.nodes) yield - + - {n.id} + {n.id.idString} val edgesXml = for (e <- graph.edges) - yield + yield val xml = token("--force")).?.map(_.isDefined) } - import IvyGraphMLDependencies.Module + import IvyGraphMLDependencies.ModuleId - val artifactIdParser: Initialize[State => Parser[Module]] = + val artifactIdParser: Initialize[State => Parser[ModuleId]] = resolvedScoped { ctx => (state: State) => val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil) import complete.DefaultParsers._ import Compat._ - def moduleFrom(modules: Seq[Module]) = + def moduleFrom(modules: Seq[ModuleId]) = modules.map { m => (token(m.name) ~ Space ~ token(m.version)).map(_ => m) }.reduce(_ | _) - graph.nodes.groupBy(_.organisation).map { + graph.nodes.map(_.id).groupBy(_.organisation).map { case (org, modules) => Space ~ token(org) ~ Space ~> moduleFrom(modules) }.reduceOption(_ | _).getOrElse { (Space ~> token(StringBasic, "organization") ~ Space ~ token(StringBasic, "module") ~ Space ~ token(StringBasic, "version") ).map { case ((((org, _), mod), _), version) => - Module(org, mod, version) + ModuleId(org, mod, version) } } }