Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transitive edges #90

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/main/scala/sbtprojectgraph/Node.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@ package sbtprojectgraph
import sbt.{ ProjectRef, ResolvedProject }

/** A node in a dependency tree of elements of type `A`. */
final case class Node[A](value: A, directDeps: Set[Dependency[Node[A]]], allDeps: Set[Dependency[A]], allEdges: Set[Edge[A]])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

directDeps variable was shadowed by val directDeps definition below (the non-shadowed value is not used anywhere before)

final case class Node[A](value: A, allDeps: Set[Dependency[A]], allEdges: Set[Edge[A]])

object Node {
def create(p: ResolvedProject, projects: Map[String, ResolvedProject]): Node[ResolvedProject] = {
def create(p: ResolvedProject, projects: Map[String, ResolvedProject], includeTransitiveEdges: Boolean): Node[ResolvedProject] = {
val aggregates = p.aggregate.toSet[ProjectRef].flatMap(ref => projects.get(ref.project).map(Dependency.fromAggregate))
val classpathDeps = p.dependencies.flatMap(dep => projects.get(dep.project.project).map(Dependency.fromDependsOn))

val directDeps0: Set[Dependency[ResolvedProject]] = aggregates ++ classpathDeps
val directDeps: Set[Dependency[Node[ResolvedProject]]] = directDeps0 map (d => Dependency(create(d.target, projects), d.kind))
val transDeps: Set[Dependency[ResolvedProject]] = directDeps flatMap (_.target.allDeps)
val uniqDirectDeps: Set[Dependency[Node[ResolvedProject]]] = directDeps filterNot (d => transDeps(d.map(_.value)))
val directDeps0: Set[Dependency[ResolvedProject]] = aggregates ++ classpathDeps
val directDeps: Set[Dependency[Node[ResolvedProject]]] = directDeps0 map (d => Dependency(create(d.target, projects, includeTransitiveEdges), d.kind))
val transDeps: Set[Dependency[ResolvedProject]] = directDeps flatMap (_.target.allDeps)

val depsUsedForEdges =
if (includeTransitiveEdges) {
directDeps
} else {
val uniqDirectDeps: Set[Dependency[Node[ResolvedProject]]] = directDeps filterNot (d => transDeps(d.map(_.value)))
uniqDirectDeps
}

Node(
value = p,
directDeps = uniqDirectDeps,
allDeps = directDeps0 ++ transDeps,
allEdges = uniqDirectDeps.flatMap(_.target.allEdges) ++ uniqDirectDeps.map(d => Edge.fromDependency(p, d.map(_.value)))
allEdges = depsUsedForEdges.flatMap(_.target.allEdges) ++ depsUsedForEdges.map(d => Edge.fromDependency(p, d.map(_.value)))
)
}
}
40 changes: 29 additions & 11 deletions src/main/scala/sbtprojectgraph/SbtProjectGraphPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import sbt.internal.{ BuildStructure, LoadedBuildUnit } // sbt/sbt#3296
object SbtProjectGraphPlugin extends AutoPlugin {
override def trigger = allRequirements

object autoImport {
lazy val projectsGraphIncludeTransitiveEdges = settingKey[Boolean](
"Should the dependency graph include transitive edges, default: false."
)
}

import autoImport._

override def buildSettings: Seq[Setting[_]] = Seq(
commands ++= Seq(
projectsGraphDot,
Expand All @@ -14,25 +22,35 @@ object SbtProjectGraphPlugin extends AutoPlugin {
)
)

override def globalSettings: Seq[Def.Setting[_]] =
super.globalSettings ++ Seq(projectsGraphIncludeTransitiveEdges := false)

val projectsGraphDot = Command.command("projectsGraphDot") { s =>
val (_, state) = executeProjectsGraphDot(s)
state
projectsGraphIncludeTransitiveEdges.map(executeProjectsGraphDot(s, _))
s
}

val projectsGraphSvg = Command.command("projectsGraphSvg") { s =>
projectsGraphIncludeTransitiveEdges.map(dotTo("svg", _)(s))
s
}

val projectsGraphSvg = Command.command("projectsGraphSvg")(dotTo("svg"))
val projectsGraphPng = Command.command("projectsGraphPng")(dotTo("png"))
val projectsGraphPng = Command.command("projectsGraphPng") { s =>
projectsGraphIncludeTransitiveEdges.map(dotTo("png", _)(s))
s
}

private[this] def dotTo(outputFormat: String)(s: State) = {
val (dotFile, state) = executeProjectsGraphDot(s)
val extracted = Project extract state
private[this] def dotTo(outputFormat: String, includeTransitiveEdges: Boolean)(s: State) = {
val dotFile = executeProjectsGraphDot(s, includeTransitiveEdges)
val extracted = Project extract s
val outFile = extracted.get(target) / s"projects-graph.$outputFormat"
val command = Seq("dot", "-o" + outFile.getAbsolutePath, s"-T$outputFormat", dotFile.getAbsolutePath)
sys.process.Process(command).!
extracted get sLog info s"Wrote project graph to '$outFile'"
state
s
}

private[this] def executeProjectsGraphDot(s: State): (File, State) = {
private[this] def executeProjectsGraphDot(s: State, includeTransitiveEdges: Boolean): File = {
val extracted: Extracted = Project extract s

val currentBuildUri: URI = extracted.currentRef.build
Expand All @@ -45,7 +63,7 @@ object SbtProjectGraphPlugin extends AutoPlugin {

val projects: Seq[ResolvedProject] = projectsMap.values.toVector

val projectsNodes: Seq[Node[ResolvedProject]] = projects map (p => Node.create(p, projectsMap))
val projectsNodes: Seq[Node[ResolvedProject]] = projects map (p => Node.create(p, projectsMap, includeTransitiveEdges))

val edges: Seq[Edge[ResolvedProject]] = projectsNodes.flatMap(_.allEdges).distinct

Expand All @@ -55,6 +73,6 @@ object SbtProjectGraphPlugin extends AutoPlugin {

extracted get sLog info s"Wrote project graph to '$projectsGraphDotFile'"

(projectsGraphDotFile, s)
projectsGraphDotFile
}
}
22 changes: 22 additions & 0 deletions src/sbt-test/transitive/works/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

lazy val foo = (project in file("."))
.settings(projectsGraphIncludeTransitiveEdges := true)
.aggregate (a, b, c)

val a = project
val b = project dependsOn a
val c = project dependsOn b

TaskKey[Unit]("check") := check(target.value / "projects-graph.dot", baseDirectory.value / "projects-graph.dot")

def check(inc0: File, exp0: File) = {
val inc = IO readLines inc0
val exp = IO readLines exp0
assert(inc == exp, s"Graph mismatch:\n${unifiedDiff(exp, inc) mkString "\n"}")
}

def unifiedDiff(expected: Seq[String], obtained: Seq[String], contextSize: Int = 3): Vector[String] = {
import scala.collection.JavaConverters._
val patch = difflib.DiffUtils.diff(expected.asJava, obtained.asJava)
difflib.DiffUtils.generateUnifiedDiff("expected", "obtained", expected.asJava, patch, contextSize).asScala.toVector
}
7 changes: 7 additions & 0 deletions src/sbt-test/transitive/works/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.dwijnand" % "sbt-project-graph" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}

libraryDependencies += "com.googlecode.java-diff-utils" % "diffutils" % "1.3.0"
19 changes: 19 additions & 0 deletions src/sbt-test/transitive/works/projects-graph.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
digraph "projects-graph" {
graph[rankdir="LR"]
node [
shape="record"
]
edge [
arrowtail="none"
]
"a"[label=<a>]
"b"[label=<b>]
"c"[label=<c>]
"foo"[label=<foo>]
"b" -> "a" [style=solid]
"c" -> "b" [style=solid]
"c" -> "a" [style=solid]
"foo" -> "a" [style=dashed]
"foo" -> "b" [style=dashed]
"foo" -> "c" [style=dashed]
}
6 changes: 6 additions & 0 deletions src/sbt-test/transitive/works/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
> projectsGraphDot
> check
> projectsGraphSvg
$ exists target/projects-graph.svg
> projectsGraphPng
$ exists target/projects-graph.png