From f69915cb8c906ebe0a335e34f74a1330b8f8b4a5 Mon Sep 17 00:00:00 2001 From: Gerolf Seitz Date: Thu, 3 May 2012 18:03:59 +0200 Subject: [PATCH] Some visual improvements + Display nodes as groupId:artifact:version + Provide tasks for printing ascii representations of the dependency graphs for the various configurations: compile, test, runtime, optional, provided --- project.sbt | 2 +- project/build.properties | 1 + .../sbt/graph/IvyGraphMLDependencies.scala | 52 +++++++++++++++---- .../net/virtualvoid/sbt/graph/Plugin.scala | 33 ++++++++++-- 4 files changed, 73 insertions(+), 15 deletions(-) mode change 100644 => 100755 project.sbt create mode 100644 project/build.properties mode change 100644 => 100755 src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala mode change 100644 => 100755 src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala diff --git a/project.sbt b/project.sbt old mode 100644 new mode 100755 index f000b7925..f123069ac --- a/project.sbt +++ b/project.sbt @@ -4,7 +4,7 @@ name := "sbt-dependency-graph" organization := "net.virtual-void" -version := "0.5.2" +version := "0.5.3-SNAPSHOT" homepage := Some(url("http://github.com/jrudolph/sbt-dependency-graph")) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 000000000..b6fc683c7 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.12.0-Beta2 diff --git a/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala b/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala old mode 100644 new mode 100755 index b2f07bdea..e981339b3 --- a/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala @@ -18,24 +18,54 @@ package net.virtualvoid.sbt.graph import xml.parsing.ConstructingParser import java.io.File -import xml.{XML, Node} +import collection.mutable.HashMap +import collection.mutable.MultiMap +import collection.mutable.{Set => MSet} +import sbt.Graph +import xml.{Document, XML, Node} object IvyGraphMLDependencies extends App { case class Module(organisation: String, name: String, version: String) { - def id: String = organisation+"."+name+"-"+version + def id: String = organisation+":"+name+":"+version } - def transform(ivyReportFile: String, outputFile: String) { - val doc = ConstructingParser.fromSource(io.Source.fromFile(ivyReportFile), false).document + case class ModuleGraph(nodes: Seq[Module], edges: Seq[(Module, Module)]) + + def buildGraph(doc: Document): ModuleGraph = { val edges = for { - mod <- doc \ "dependencies" \ "module" - caller <- mod \ "revision" \ "caller" - callerModule = nodeFromElement(caller, caller.attribute("callerrev").get.text) - depModule = nodeFromElement(mod, caller.attribute("rev").get.text) - } yield (callerModule, depModule) + mod <- doc \ "dependencies" \ "module" + caller <- mod \ "revision" \ "caller" + callerModule = nodeFromElement(caller, caller.attribute("callerrev").get.text) + depModule = nodeFromElement(mod, caller.attribute("rev").get.text) + } yield (callerModule, depModule) val nodes = edges.flatMap(e => Seq(e._1, e._2)).distinct + ModuleGraph(nodes, edges) + } + + + def ascii(ivyReportFile: String): String = { + val doc = buildDoc(ivyReportFile) + val graph = buildGraph(doc) + import graph._ + val deps = { + 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)) + } + // there should only be one root node (the project itself) + val roots = nodes.filter(n => !edges.exists(_._2 == n)).sortBy(_.id) + roots.map(root => + Graph.toAscii[Module](root, node => deps.getOrElse(node, Seq.empty[Module]), _.id) + ).mkString("\n") + } + + def transform(ivyReportFile: String, outputFile: String) { + val doc = buildDoc(ivyReportFile) + val graph = buildGraph(doc) + import graph._ + val nodesXml = for (n <- nodes) yield @@ -63,9 +93,11 @@ object IvyGraphMLDependencies extends App { XML.save(outputFile, xml) } - def nodeFromElement(element: Node, version: String): Module = + private def nodeFromElement(element: Node, version: String): Module = Module(element.attribute("organisation").get.text, element.attribute("name").get.text, version) + private def buildDoc(ivyReportFile: String) = ConstructingParser.fromSource(io.Source.fromFile(ivyReportFile), false).document + def die(msg: String): Nothing = { println(msg) sys.exit(1) diff --git a/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala b/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala old mode 100644 new mode 100755 index 6d98092d2..c386a4be7 --- a/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala @@ -22,6 +22,10 @@ import Keys._ object Plugin extends sbt.Plugin { val dependencyGraphTask = TaskKey[File]("dependency-graph", "Creates a graphml file containing the dependency-graph for a project") + val asciiGraph = TaskKey[String]("ascii-graph", + "Returns a string containing the ascii representation of the dependency graph for a project") + val printAsciiGraph = TaskKey[Unit]("print-ascii-graph", + "Prints the ascii graph to the console") val ivyReportF = SettingKey[String => File]("ivy-report-function", "A function which returns the file containing the ivy report from the ivy cache for a given configuration") val ivyReport = InputKey[File]("ivy-report", @@ -40,6 +44,19 @@ object Plugin extends sbt.Plugin { report(args(0)) } }, + + asciiGraph in Compile <<= asciiGraphTask(Compile), + asciiGraph in Test <<= asciiGraphTask(Test), + asciiGraph in Runtime <<= asciiGraphTask(Runtime), + asciiGraph in Provided <<= asciiGraphTask(Provided), + asciiGraph in Optional <<= asciiGraphTask(Optional), + + printAsciiGraph in Compile <<= printAsciiGraphTask(Compile), + printAsciiGraph in Test <<= printAsciiGraphTask(Test), + printAsciiGraph in Runtime <<= printAsciiGraphTask(Runtime), + printAsciiGraph in Provided <<= printAsciiGraphTask(Provided), + printAsciiGraph in Optional <<= printAsciiGraphTask(Optional), + dependencyGraphTask <<= (ivyReportF, target, streams) map { (report, target, streams) => val resultFile = target / "dependencies.graphml" IvyGraphMLDependencies.transform(report("compile").getAbsolutePath, resultFile.getAbsolutePath) @@ -48,11 +65,19 @@ object Plugin extends sbt.Plugin { } dependsOn(deliverLocal) ) + def asciiGraphTask(conf: Configuration) = (ivyReportF in conf) map { (report) => + IvyGraphMLDependencies.ascii(report(conf.name).getAbsolutePath) + } dependsOn(deliverLocal) + + def printAsciiGraphTask(conf: Configuration) = (asciiGraph in conf, streams in conf) map { (graph, streams) => + streams.log.info(graph) + } + def crossName(moduleId: ModuleID, scalaVersion: String) = moduleId.name + ( - if (moduleId.crossVersion) - "_"+scalaVersion - else - "" + moduleId.crossVersion match { + case CrossVersion.Disabled => "" + case _ => "_"+scalaVersion + } ) } \ No newline at end of file