in DOT graph render special evicted by edges and access new elements through evicted ones

This commit is contained in:
Johannes Rudolph 2015-11-18 17:35:29 +01:00
parent 932085bdf6
commit ac0ab5189a
4 changed files with 110 additions and 31 deletions

View File

@ -31,6 +31,8 @@ trait DependencyGraphKeys {
"The header of the dot file. (e.g. to set your preferred node shapes)")
val dependencyDot = TaskKey[File]("dependency-dot",
"Creates a dot file containing the dependency-graph for a project")
val dependencyDotString = TaskKey[String]("dependency-dot-string",
"Creates a String containing the dependency-graph for a project in dot format")
val moduleGraph = TaskKey[ModuleGraph]("module-graph",
"The dependency graph for a project")
val asciiGraph = TaskKey[String]("dependency-graph-string",

View File

@ -17,12 +17,12 @@
package net.virtualvoid.sbt.graph
import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport }
import net.virtualvoid.sbt.graph.util.IOUtil
import sbt._
import Keys._
import CrossVersion._
import sbt.complete.DefaultParsers._
import sbt.complete.Parser
import org.apache.ivy.core.resolve.ResolveOptions
@ -81,17 +81,15 @@ object DependencyGraphSettings {
dependencyGraphMLFile <<= target / "dependencies-%s.graphml".format(config.toString),
dependencyGraphML <<= dependencyGraphMLTask,
dependencyDotFile <<= target / "dependencies-%s.dot".format(config.toString),
dependencyDot <<= dependencyDotTask,
dependencyDotString <<= dependencyDotStringTask,
dependencyDot <<= writeToFile(dependencyDotString, dependencyDotFile),
dependencyDotHeader := """digraph "dependency-graph" {
| graph[rankdir="LR"]
| node [
| shape="record"
| ]
| edge [
| arrowtail="none"
| ]""".stripMargin,
dependencyDotNodeLabel := { (organisation: String, name: String, version: String)
"""<%s<BR/><B>%s</B><BR/>%s>""".format(organisation, name, version)
"""%s<BR/><B>%s</B><BR/>%s""".format(organisation, name, version)
},
whatDependsOn <<= InputTask(artifactIdParser) { module
(module, streams, moduleGraph) map { (module, streams, graph)
@ -115,14 +113,19 @@ object DependencyGraphSettings {
streams.log.info("Wrote dependency graph to '%s'" format resultFile)
resultFile
}
def dependencyDotTask =
(moduleGraph, dependencyDotHeader, dependencyDotNodeLabel, dependencyDotFile, streams).map {
(graph, dotHead, nodeLabel, outFile, streams)
val resultFile = rendering.DOT.saveAsDot(graph, dotHead, nodeLabel, outFile)
streams.log.info("Wrote dependency graph to '%s'" format resultFile)
resultFile
def dependencyDotStringTask =
(moduleGraph, dependencyDotHeader, dependencyDotNodeLabel).map {
(graph, dotHead, nodeLabel) rendering.DOT.dotGraph(graph, dotHead, nodeLabel)
}
def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) =
(dataTask, fileTask, streams).map { (data, outFile, streams)
IOUtil.writeToFile(data, outFile)
streams.log.info("Wrote dependency graph to '%s'" format outFile)
outFile
}
def absoluteReportPath = (file: File) file.getAbsolutePath
def print(key: TaskKey[String]) =

View File

@ -14,31 +14,50 @@
* limitations under the License.
*/
package net.virtualvoid.sbt.graph.rendering
import java.io.File
import net.virtualvoid.sbt.graph.ModuleGraph
package net.virtualvoid.sbt.graph
package rendering
object DOT {
def saveAsDot(graph: ModuleGraph,
dotHead: String,
nodeFormation: (String, String, String) String,
outputFile: File): File = {
val EvictedStyle = "stroke-dasharray: 5,5"
def dotGraph(graph: ModuleGraph,
dotHead: String,
nodeFormation: (String, String, String) String): String = {
val nodes = {
for (n graph.nodes)
yield """ "%s"[label=%s]""".format(n.id.idString,
nodeFormation(n.id.organisation, n.id.name, n.id.version))
for (n graph.nodes) yield {
val style = if (n.isEvicted) EvictedStyle else ""
""" "%s"[labelType="html" label="%s" style="%s"]""".format(n.id.idString,
nodeFormation(n.id.organisation, n.id.name, n.id.version),
style)
}
}.mkString("\n")
def originWasEvicted(edge: Edge): Boolean = graph.module(edge._1).isEvicted
def targetWasEvicted(edge: Edge): Boolean = graph.module(edge._2).isEvicted
// add extra edges from evicted to evicted-by module
val evictedByEdges: Seq[Edge] =
graph.nodes.filter(_.isEvicted).map(m Edge(m.id, m.id.copy(version = m.evictedByVersion.get)))
// remove edges to new evicted-by module which is now replaced by a chain
// dependend -> [evicted] -> dependee
val evictionTargetEdges =
graph.edges.filter(targetWasEvicted).map {
case (from, evicted) (from, evicted.copy(version = graph.module(evicted).evictedByVersion.get))
}.toSet
val filteredEdges =
graph.edges
.filterNot(e originWasEvicted(e) || evictionTargetEdges(e)) ++ evictedByEdges
val edges = {
for (e graph.edges)
yield """ "%s" -> "%s"""".format(e._1.idString, e._2.idString)
for (e filteredEdges) yield {
val extra = if (graph.module(e._1).isEvicted)
s""" [label="Evicted By" style="$EvictedStyle"]""" else ""
""" "%s" -> "%s"%s""".format(e._1.idString, e._2.idString, extra)
}
}.mkString("\n")
val dot = "%s\n%s\n%s\n}".format(dotHead, nodes, edges)
sbt.IO.write(outputFile, dot)
outputFile
"%s\n%s\n%s\n}".format(dotHead, nodes, edges)
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2015 Johannes Rudolph
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.virtualvoid.sbt.graph.util
import java.io.{ OutputStream, InputStream, FileOutputStream, File }
import java.nio.charset.Charset
import scala.annotation.tailrec
object IOUtil {
val utf8 = Charset.forName("utf8")
def writeToFile(string: String, file: File): Unit =
sbt.IO.write(file, string, utf8)
def saveResource(resourcePath: String, to: File): Unit = {
val is = getClass.getClassLoader.getResourceAsStream(resourcePath)
require(is ne null, s"Couldn't load '$resourcePath' from classpath.")
val fos = new FileOutputStream(to)
try copy(is, fos)
finally {
is.close()
fos.close()
}
}
def copy(from: InputStream, to: OutputStream): Unit = {
val buffer = new Array[Byte](65536)
@tailrec def rec(): Unit = {
val read = from.read(buffer)
if (read > 0) {
to.write(buffer, 0, read)
rec()
} else if (read == 0)
throw new IllegalStateException("InputStream.read returned 0")
}
rec()
}
}