Merge branch 'sbt-0.12'

We can now support the previously 0.12 only features by explicitly including
the Graph class in 0.11 builds.

Conflicts:
	project.sbt
	project/build.properties
	project/gpg.sbt
	project/plugins.sbt
	src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala
This commit is contained in:
Johannes Rudolph 2012-05-21 17:55:20 +02:00
commit 9f4852ac64
3 changed files with 107 additions and 14 deletions

View File

@ -0,0 +1,43 @@
/* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah, Eugene Yokota
*
* Copied from sbt 0.12 source code
*/
package sbt
object Graph
{
// [info] foo
// [info] +-bar
// [info] | +-baz
// [info] |
// [info] +-quux
def toAscii[A](top: A, children: A => Seq[A], display: A => String): String = {
val maxColumn = jline.Terminal.getTerminal.getTerminalWidth - 8
val twoSpaces = " " + " " // prevent accidentally being converted into a tab
def limitLine(s: String): String =
if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".."
else s
def insertBar(s: String, at: Int): String =
s.slice(0, at) +
(s(at).toString match {
case " " => "|"
case x => x
}) +
s.slice(at + 1, s.length)
def toAsciiLines(node: A, level: Int): Vector[String] = {
val line = limitLine((twoSpaces * level) + (if (level == 0) "" else "+-") + display(node))
val cs = Vector(children(node): _*)
val childLines = cs map {toAsciiLines(_, level + 1)}
val withBar = childLines.zipWithIndex flatMap {
case (lines, pos) if pos < (cs.size - 1) => lines map {insertBar(_, 2 * (level + 1))}
case (lines, pos) =>
if (lines.last.trim != "") lines ++ Vector(twoSpaces * (level + 1))
else lines
}
line +: withBar
}
toAsciiLines(top, 0).mkString("\n")
}
}

View File

@ -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) {
def id: String = organisation+"."+name
case class Module(organisation: String, name: String, version: String) {
def id: String = organisation+":"+name+":"+version
}
def transform(ivyReportFile: String, outputFile: String) {
val doc = ConstructingParser.fromSource(io.Source.fromFile(ivyReportFile), false).document
val edges =
for (mod <- doc \ "dependencies" \ "module";
depModule = nodeFromElement(mod);
caller <- mod \ "revision" \ "caller";
callerModule = nodeFromElement(caller))
yield (callerModule, depModule)
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)
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,8 +93,10 @@ object IvyGraphMLDependencies extends App {
XML.save(outputFile, xml)
}
def nodeFromElement(element: Node): Module =
Module(element.attribute("organisation").get.text, element.attribute("name").get.text)
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)

20
src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala Normal file → Executable file
View File

@ -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 = TaskKey[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",
@ -46,7 +50,21 @@ object Plugin extends sbt.Plugin {
streams.log.info("Wrote dependency graph to '%s'" format resultFile)
resultFile
}
)
) ++ Seq(Compile, Test, Runtime, Provided, Optional).flatMap(asciiGraphSettings)
def asciiGraphSettings(config: Configuration) =
seq(
asciiGraph in config <<= asciiGraphTask(config),
printAsciiGraph in config <<= printAsciiGraphTask(config)
)
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(ivyModule: IvySbt#Module) =
ivyModule.moduleSettings match {