mirror of https://github.com/sbt/sbt.git
in DOT graph render special evicted by edges and access new elements through evicted ones
This commit is contained in:
parent
932085bdf6
commit
ac0ab5189a
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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]) =
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue