├── project ├── build.properties ├── pgp.sbt ├── plugins.sbt └── ScalariformSupport.scala ├── src ├── sbt-test │ └── sbt-dependency-graph │ │ ├── cachedResolution │ │ ├── test │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ ├── ignoreScalaLibrary │ │ ├── test │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ ├── intervalRangedVersions │ │ ├── test │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ ├── showMissingUpdates │ │ ├── test │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ ├── testDotFileGeneration │ │ ├── project │ │ │ └── plugins.sbt │ │ ├── test │ │ └── build.sbt │ │ ├── whatDependsOn │ │ ├── test │ │ ├── project │ │ │ └── plugins.sbt │ │ └── build.sbt │ │ ├── plugins.sbt │ │ ├── toFileSubTask │ │ ├── project │ │ │ └── plugins.sbt │ │ ├── expected │ │ │ ├── list.txt │ │ │ ├── tree.txt │ │ │ ├── licenses.txt │ │ │ └── stats.txt │ │ ├── test │ │ └── build.sbt │ │ └── whatDependsOn-without-previous-initialization │ │ ├── test │ │ ├── project │ │ └── plugins.sbt │ │ └── build.sbt ├── main │ ├── scala-sbt-0.13 │ │ ├── net │ │ │ └── virtualvoid │ │ │ │ └── sbt │ │ │ │ └── graph │ │ │ │ ├── ModuleGraphProtocolCompat.scala │ │ │ │ └── rendering │ │ │ │ └── AsciiGraph.scala │ │ └── sbt │ │ │ └── dependencygraph │ │ │ └── DependencyGraphSbtCompat.scala │ ├── scala-sbt-1.0 │ │ ├── sbt │ │ │ └── dependencygraph │ │ │ │ └── DependencyGraphSbtCompat.scala │ │ └── net │ │ │ └── virtualvoid │ │ │ └── sbt │ │ │ └── graph │ │ │ ├── rendering │ │ │ └── AsciiGraph.scala │ │ │ └── ModuleGraphProtocolCompat.scala │ ├── scala │ │ ├── net │ │ │ └── virtualvoid │ │ │ │ └── sbt │ │ │ │ └── graph │ │ │ │ ├── rendering │ │ │ │ ├── LicenseInfo.scala │ │ │ │ ├── FlatList.scala │ │ │ │ ├── DagreHTML.scala │ │ │ │ ├── AsciiTree.scala │ │ │ │ ├── TreeView.scala │ │ │ │ ├── GraphML.scala │ │ │ │ ├── Statistics.scala │ │ │ │ └── DOT.scala │ │ │ │ ├── package.scala │ │ │ │ ├── util │ │ │ │ ├── ConsoleUtils.scala │ │ │ │ ├── IOUtil.scala │ │ │ │ └── AsciiTreeLayout.scala │ │ │ │ ├── DependencyGraphPlugin.scala │ │ │ │ ├── Main.scala │ │ │ │ ├── backend │ │ │ │ ├── SbtUpdateReport.scala │ │ │ │ └── IvyReport.scala │ │ │ │ ├── GraphTransformations.scala │ │ │ │ ├── model.scala │ │ │ │ ├── DependencyGraphKeys.scala │ │ │ │ └── DependencyGraphSettings.scala │ │ └── sbt │ │ │ └── dependencygraph │ │ │ └── SbtAccess.scala │ └── resources │ │ ├── tree.html │ │ └── graph.html └── test │ └── scala │ └── net │ └── virtualvoid │ └── sbt │ └── graph │ └── util │ └── AsciiTreeLayoutSpecs.scala ├── .gitignore ├── PUBLISHING ├── project.sbt ├── .travis.yml ├── publish.sbt ├── NOTICE ├── README.md ├── CHANGELOG.md └── LICENSE /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.7 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/cachedResolution/test: -------------------------------------------------------------------------------- 1 | > check -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/test: -------------------------------------------------------------------------------- 1 | > check -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/intervalRangedVersions/test: -------------------------------------------------------------------------------- 1 | > check -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/showMissingUpdates/test: -------------------------------------------------------------------------------- 1 | > check -------------------------------------------------------------------------------- /project/pgp.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | ../../plugins.sbt -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/intervalRangedVersions/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | ../../plugins.sbt -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/showMissingUpdates/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | ../../plugins.sbt -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/testDotFileGeneration/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | ../../plugins.sbt -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | project/boot/ 3 | target/ 4 | lib_managed/ 5 | src_managed/ 6 | test-output/ 7 | *.iml -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/testDotFileGeneration/test: -------------------------------------------------------------------------------- 1 | > project test-dot-file-generation 2 | > check 3 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/whatDependsOn/test: -------------------------------------------------------------------------------- 1 | # to initialize parser with deps 2 | > compile:moduleGraph 3 | > check -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.sbt.graph 2 | 3 | trait ModuleGraphProtocolCompat 4 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/cachedResolution/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/whatDependsOn/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/test: -------------------------------------------------------------------------------- 1 | # same as whatDependsOn test but without the initialization to prime the parser 2 | > check -------------------------------------------------------------------------------- /src/main/scala-sbt-1.0/sbt/dependencygraph/DependencyGraphSbtCompat.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package dependencygraph 3 | 4 | object DependencyGraphSbtCompat { 5 | object Implicits 6 | } 7 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % sys.props("project.version")) 2 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/list.txt: -------------------------------------------------------------------------------- 1 | com.codahale:jerkson_2.9.1:0.5.0 2 | org.codehaus.jackson:jackson-core-asl:1.9.11 3 | org.codehaus.jackson:jackson-mapper-asl:1.9.11 4 | org.example:blubber_2.12:0.1 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies += "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value 2 | 3 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") 4 | addSbtPlugin("com.dwijnand" % "sbt-dynver" % "2.0.0") 5 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/test: -------------------------------------------------------------------------------- 1 | > dependencyTree::toFile target/tree.txt 2 | > dependencyList::toFile target/list.txt 3 | > dependencyStats::toFile target/stats.txt 4 | > dependencyLicenseInfo::toFile target/licenses.txt 5 | > check 6 | -------------------------------------------------------------------------------- /PUBLISHING: -------------------------------------------------------------------------------- 1 | Before publishing: 2 | 3 | * create entry in CHANGELOG 4 | * update version in README 5 | 6 | * run `;^ clean; ^ test:compile; ^ test; ^ scripted; ^publishSigned` 7 | 8 | * test staged version in sonatype 9 | * release from sonatype 10 | 11 | After publishing: 12 | * push master and tags 13 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/tree.txt: -------------------------------------------------------------------------------- 1 | org.example:blubber_2.12:0.1 [S] 2 | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 3 | +-org.codehaus.jackson:jackson-core-asl:1.9.11 4 | +-org.codehaus.jackson:jackson-mapper-asl:1.9.11 5 | +-org.codehaus.jackson:jackson-core-asl:1.9.11 6 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/licenses.txt: -------------------------------------------------------------------------------- 1 | No license specified 2 | org.example:blubber_2.12:0.1 3 | 4 | The Apache Software License, Version 2.0 5 | org.codehaus.jackson:jackson-mapper-asl:1.9.11 6 | org.codehaus.jackson:jackson-core-asl:1.9.11 7 | 8 | The MIT License 9 | com.codahale:jerkson_2.9.1:0.5.0 -------------------------------------------------------------------------------- /project.sbt: -------------------------------------------------------------------------------- 1 | sbtPlugin := true 2 | 3 | name := "sbt-dependency-graph" 4 | 5 | organization := "net.virtual-void" 6 | 7 | homepage := Some(url("http://github.com/jrudolph/sbt-dependency-graph")) 8 | 9 | licenses in GlobalScope += "Apache License 2.0" -> url("https://github.com/jrudolph/sbt-dependency-graph/raw/master/LICENSE") 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: scala 3 | jdk: oraclejdk8 4 | dist: trusty 5 | script: 6 | - sbt ";^test ;^scripted" 7 | 8 | before_cache: 9 | # Cleanup the cached directories to avoid unnecessary cache updates 10 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete 11 | - find $HOME/.sbt -name "*.lock" -print -delete 12 | 13 | cache: 14 | directories: 15 | - $HOME/.ivy2/cache 16 | - $HOME/.sbt 17 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/LicenseInfo.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.sbt.graph.rendering 2 | 3 | import net.virtualvoid.sbt.graph.ModuleGraph 4 | 5 | object LicenseInfo { 6 | def render(graph: ModuleGraph): String = 7 | graph.nodes.filter(_.isUsed).groupBy(_.license).toSeq.sortBy(_._1).map { 8 | case (license, modules) ⇒ 9 | license.getOrElse("No license specified") + "\n" + 10 | modules.map(_.id.idString formatted "\t %s").mkString("\n") 11 | }.mkString("\n\n") 12 | } 13 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/expected/stats.txt: -------------------------------------------------------------------------------- 1 | TotSize JarSize #TDe #Dep Module 2 | 1.754 MB ------- MB 3 1 org.example:blubber_2.12:0.1 3 | 1.754 MB 0.741 MB 2 2 com.codahale:jerkson_2.9.1:0.5.0 4 | 1.013 MB 0.780 MB 1 1 org.codehaus.jackson:jackson-mapper-asl:1.9.11 5 | 0.232 MB 0.232 MB 0 0 org.codehaus.jackson:jackson-core-asl:1.9.11 6 | 7 | Columns are 8 | - Jar-Size including dependencies 9 | - Jar-Size 10 | - Number of transitive dependencies 11 | - Number of direct dependencies 12 | - ModuleID -------------------------------------------------------------------------------- /src/main/scala-sbt-0.13/sbt/dependencygraph/DependencyGraphSbtCompat.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package dependencygraph 3 | 4 | import scala.language.implicitConversions 5 | 6 | object DependencyGraphSbtCompat { 7 | object Implicits { 8 | implicit def convertConfig(config: sbt.Configuration): String = config.toString 9 | 10 | implicit class RichUpdateConfiguration(val updateConfig: UpdateConfiguration) extends AnyVal { 11 | def withMissingOk(missingOk: Boolean): UpdateConfiguration = 12 | updateConfig.copy(missingOk = missingOk) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /project/ScalariformSupport.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | import com.typesafe.sbt.SbtScalariform 4 | import com.typesafe.sbt.SbtScalariform.ScalariformKeys 5 | 6 | object ScalariformSupport { 7 | lazy val formatSettings = SbtScalariform.scalariformSettings ++ Seq( 8 | ScalariformKeys.preferences in Compile := formattingPreferences, 9 | ScalariformKeys.preferences in Test := formattingPreferences 10 | ) 11 | 12 | import scalariform.formatter.preferences._ 13 | def formattingPreferences = 14 | FormattingPreferences() 15 | .setPreference(RewriteArrowSymbols, true) 16 | .setPreference(AlignParameters, true) 17 | .setPreference(AlignSingleLineCaseStatements, true) 18 | .setPreference(DoubleIndentClassDeclaration, true) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/cachedResolution/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.9" 2 | 3 | libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.28" 4 | updateOptions := updateOptions.value.withCachedResolution(true) 5 | 6 | TaskKey[Unit]("check") := { 7 | val report = (ivyReport in Test).value 8 | val graph = (asciiTree in Test).value 9 | 10 | def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n") 11 | val expectedGraph = 12 | """default:cachedresolution_2.12:0.1.0-SNAPSHOT 13 | | +-org.slf4j:slf4j-api:1.7.28 14 | | """.stripMargin 15 | require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph))) 16 | () 17 | } 18 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/showMissingUpdates/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.9.2" 2 | 3 | libraryDependencies += 4 | "at.blub" % "blib" % "1.2.3" % "test" 5 | 6 | TaskKey[Unit]("check") := { 7 | val report = (ivyReport in Test).value 8 | val graph = (asciiTree in Test).value 9 | 10 | def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n") 11 | val expectedGraph = 12 | """default:default-91180e_2.9.2:0.1-SNAPSHOT 13 | | +-%sat.blub:blib:1.2.3 (error: not found)%s 14 | | """.stripMargin.format(scala.Console.RED, scala.Console.RESET) 15 | require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph))) 16 | () 17 | } 18 | -------------------------------------------------------------------------------- /publish.sbt: -------------------------------------------------------------------------------- 1 | publishTo := { 2 | val nexus = "https://oss.sonatype.org/" 3 | Some { 4 | if (version.value.trim.contains("+")) "snapshots" at nexus + "content/repositories/snapshots" 5 | else "releases" at nexus + "service/local/staging/deploy/maven2" 6 | } 7 | } 8 | 9 | publishMavenStyle := true 10 | 11 | publishArtifact in Test := false 12 | 13 | pomIncludeRepository := { _ => false } 14 | 15 | scmInfo := Some( 16 | ScmInfo( 17 | browseUrl = url("https://github.com/jrudolph/sbt-dependency-graph"), 18 | connection = "scm:git:git@github.com:jrudolph/sbt-dependency-graph.git" 19 | ) 20 | ) 21 | 22 | developers := List( 23 | Developer( 24 | "jrudolph", 25 | "Johannes Rudolph", 26 | "johannes.rudolph@gmail.com", 27 | url("https://virtual-void.net") 28 | ) 29 | ) 30 | 31 | useGpg := true 32 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/toFileSubTask/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.12.6" 2 | 3 | organization := "org.example" 4 | 5 | name := "blubber" 6 | 7 | version := "0.1" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.codahale" % "jerkson_2.9.1" % "0.5.0" 11 | ) 12 | 13 | TaskKey[Unit]("check") := { 14 | val candidates = "tree list stats licenses".split(' ').map(_.trim) 15 | candidates.foreach { c => 16 | val expected = new File(s"expected/$c.txt") 17 | val actual = new File(s"target/$c.txt") 18 | 19 | import sys.process._ 20 | val exit = s"diff -U3 ${expected.getPath} ${actual.getPath}".! 21 | require(exit == 0, s"Diff was non-zero for ${actual.getName}") 22 | } 23 | 24 | //require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph))) 25 | () 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | object AsciiGraph { 21 | def asciiGraphSetttings = Seq.empty[sbt.Def.Setting[_]] 22 | } -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt 18 | 19 | package object graph { 20 | type Edge = (ModuleId, ModuleId) 21 | def Edge(from: ModuleId, to: ModuleId): Edge = from -> to 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/util/ConsoleUtils.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph.util 18 | 19 | import sbt.ConsoleLogger 20 | 21 | object ConsoleUtils { 22 | def red(str: String, doRed: Boolean): String = 23 | if (ConsoleLogger.formatEnabled && doRed) 24 | Console.RED + str + Console.RESET 25 | else 26 | str 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/FlatList.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | object FlatList { 21 | def render(display: Module ⇒ String)(graph: ModuleGraph): String = 22 | graph.modules.values.toSeq 23 | .distinct 24 | .filterNot(_.isEvicted) 25 | .sortBy(m ⇒ (m.id.organisation, m.id.name)) 26 | .map(display) 27 | .mkString("\n") 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphPlugin.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011, 2012 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | 19 | import sbt._ 20 | import Keys._ 21 | 22 | object DependencyGraphPlugin extends AutoPlugin { 23 | object autoImport extends DependencyGraphKeys 24 | 25 | override def projectSettings: Seq[Def.Setting[_]] = DependencyGraphSettings.graphSettings 26 | 27 | override def trigger: PluginTrigger = AllRequirements 28 | } 29 | -------------------------------------------------------------------------------- /src/main/scala/sbt/dependencygraph/SbtAccess.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package sbt 18 | package dependencygraph 19 | 20 | import Def._ 21 | 22 | /** Accessors to private[sbt] symbols. */ 23 | object SbtAccess { 24 | val unmanagedScalaInstanceOnly = Defaults.unmanagedScalaInstanceOnly 25 | 26 | def getTerminalWidth: Int = sbt.internal.util.JLine.usingTerminal(_.getWidth) 27 | 28 | def inTask[T](t: Scoped, i: Initialize[T]): Initialize[T] = _root_.sbt.inTask(t, i) 29 | } 30 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/ignoreScalaLibrary/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.9.2" 2 | 3 | libraryDependencies ++= Seq( 4 | "org.slf4j" % "slf4j-api" % "1.7.2", 5 | "ch.qos.logback" % "logback-classic" % "1.0.7" 6 | ) 7 | 8 | TaskKey[Unit]("check") := { 9 | val report = (ivyReport in Test).value 10 | val graph = (asciiTree in Test).value 11 | def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n") 12 | val expectedGraph = 13 | """default:default-e95e05_2.9.2:0.1-SNAPSHOT [S] 14 | | +-ch.qos.logback:logback-classic:1.0.7 15 | | | +-ch.qos.logback:logback-core:1.0.7 16 | | | +-org.slf4j:slf4j-api:1.6.6 (evicted by: 1.7.2) 17 | | | +-org.slf4j:slf4j-api:1.7.2 18 | | | 19 | | +-org.slf4j:slf4j-api:1.7.2 20 | | """.stripMargin 21 | IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n")) 22 | IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n")) 23 | require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph))) 24 | () 25 | } 26 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/intervalRangedVersions/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.9.1" 2 | 3 | resolvers += "typesafe maven" at "https://repo.typesafe.com/typesafe/maven-releases/" 4 | 5 | libraryDependencies ++= Seq( 6 | "com.codahale" % "jerkson_2.9.1" % "0.5.0" 7 | ) 8 | 9 | TaskKey[Unit]("check") := { 10 | val report = (ivyReport in Test).value 11 | val graph = (asciiTree in Test).value 12 | 13 | def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n") 14 | val expectedGraph = 15 | """default:default-dbc48d_2.9.2:0.1-SNAPSHOT [S] 16 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 17 | | +-org.codehaus.jackson:jackson-core-asl:1.9.11 18 | | +-org.codehaus.jackson:jackson-mapper-asl:1.9.11 19 | | +-org.codehaus.jackson:jackson-core-asl:1.9.11 20 | | """.stripMargin 21 | IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n")) 22 | IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n")) 23 | require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph))) 24 | () 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala-sbt-1.0/net/virtualvoid/sbt/graph/ModuleGraphProtocolCompat.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.sbt.graph 2 | 3 | import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, File } 4 | import java.util.Base64 5 | 6 | import sbinary.{ Format, JavaInput, JavaOutput } 7 | import sjsonnew.{ Builder, Unbuilder } 8 | 9 | trait ModuleGraphProtocolCompat { 10 | implicit def sjsonNewAndShinyTransformAndTranspileAdapterFactoryModuleImplementation[T](implicit format: Format[T]): sjsonnew.JsonFormat[T] = 11 | new sjsonnew.JsonFormat[T] { 12 | // note, how this is simpler to write than to learn any sjonnew protocol syntax 13 | def write[J](obj: T, builder: Builder[J]): Unit = { 14 | val baos = new ByteArrayOutputStream() 15 | format.writes(new JavaOutput(baos), obj) 16 | val str = Base64.getEncoder.encodeToString(baos.toByteArray) 17 | builder.writeString(str) 18 | } 19 | def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T = { 20 | val str = unbuilder.readString(jsOpt.get) 21 | val bais = new ByteArrayInputStream(Base64.getDecoder.decode(str)) 22 | format.reads(new JavaInput(bais)) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/Main.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | 19 | import java.io.File 20 | 21 | import net.virtualvoid.sbt.graph.backend.IvyReport 22 | 23 | object Main extends App { 24 | def die(msg: String): Nothing = { 25 | println(msg) 26 | sys.exit(1) 27 | } 28 | def usage: String = 29 | "Usage: " 30 | 31 | val reportFile = args.lift(0).filter(f ⇒ new File(f).exists).getOrElse(die(usage)) 32 | val outputFile = args.lift(1).getOrElse(die(usage)) 33 | val graph = IvyReport.fromReportFile(reportFile) 34 | rendering.GraphML.saveAsGraphML(graph, outputFile) 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dependencyBrowseTree 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 24 |

Dependencies

25 | Search: 26 |
27 | 28 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/DagreHTML.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | import java.io.File 21 | import java.net.{ URLEncoder, URI } 22 | 23 | import net.virtualvoid.sbt.graph.util.IOUtil 24 | 25 | object DagreHTML { 26 | def createLink(dotGraph: String, targetDirectory: File): URI = { 27 | targetDirectory.mkdirs() 28 | val graphHTML = new File(targetDirectory, "graph.html") 29 | IOUtil.saveResource("graph.html", graphHTML) 30 | IOUtil.writeToFile(dotGraph, new File(targetDirectory, "dependencies.dot")) 31 | 32 | val graphString = 33 | URLEncoder.encode(dotGraph, "utf8") 34 | .replaceAllLiterally("+", "%20") 35 | 36 | IOUtil.writeToFile(s"""data = "$graphString";""", new File(targetDirectory, "dependencies.dot.js")) 37 | 38 | new URI(graphHTML.toURI.toString) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/AsciiTree.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | import util.AsciiTreeLayout 21 | import util.ConsoleUtils._ 22 | 23 | object AsciiTree { 24 | def asciiTree(graph: ModuleGraph): String = { 25 | val deps = graph.dependencyMap 26 | 27 | // there should only be one root node (the project itself) 28 | val roots = graph.roots 29 | roots.map { root ⇒ 30 | AsciiTreeLayout.toAscii[Module](root, node ⇒ deps.getOrElse(node.id, Seq.empty[Module]), displayModule) 31 | }.mkString("\n") 32 | } 33 | 34 | def displayModule(module: Module): String = 35 | red(module.id.idString + 36 | module.extraInfo + 37 | module.error.map(" (error: " + _ + ")").getOrElse("") + 38 | module.evictedByVersion.map(_ formatted " (evicted by: %s)").getOrElse(""), module.hadError) 39 | } 40 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AsciiTreeLayout.scala is copied from sbt and is distributed under the following license: 2 | 3 | Copyright (c) 2008, 2009, 2010 Steven Blundy, Josh Cough, Mark Harrah, Stuart Roebuck, Tony Sloane, Vesa Vilhonen, Jason Zaugg 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 3. The name of the author may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/TreeView.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.sbt.graph.rendering 2 | 3 | import java.io.File 4 | import java.net.URI 5 | 6 | import net.virtualvoid.sbt.graph.util.IOUtil 7 | import net.virtualvoid.sbt.graph.{ Module, ModuleGraph } 8 | 9 | import scala.util.parsing.json.{ JSONArray, JSONObject } 10 | 11 | object TreeView { 12 | def createJson(graph: ModuleGraph): String = { 13 | val trees = graph.roots 14 | .map(module ⇒ processSubtree(graph, module)) 15 | .toList 16 | JSONArray(trees).toString 17 | } 18 | 19 | def createLink(graphJson: String, targetDirectory: File): URI = { 20 | targetDirectory.mkdirs() 21 | val graphHTML = new File(targetDirectory, "tree.html") 22 | IOUtil.saveResource("tree.html", graphHTML) 23 | IOUtil.writeToFile(graphJson, new File(targetDirectory, "tree.json")) 24 | IOUtil.writeToFile(s"tree_data = $graphJson;", new File(targetDirectory, "tree.data.js")) 25 | new URI(graphHTML.toURI.toString) 26 | } 27 | 28 | private def processSubtree(graph: ModuleGraph, module: Module): JSONObject = { 29 | val children = graph.dependencyMap 30 | .getOrElse(module.id, List()) 31 | .map(module ⇒ processSubtree(graph, module)) 32 | .toList 33 | moduleAsJson(module, children) 34 | } 35 | 36 | private def moduleAsJson(module: Module, children: List[JSONObject]): JSONObject = { 37 | val eviction = module.evictedByVersion.map(version ⇒ s" (evicted by $version)").getOrElse("") 38 | val error = module.error.map(err ⇒ s" (errors: $err)").getOrElse("") 39 | val text = module.id.idString + eviction + error 40 | JSONObject(Map("text" -> text, "children" -> JSONArray(children))) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/util/IOUtil.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph.util 18 | 19 | import java.io.{ OutputStream, InputStream, FileOutputStream, File } 20 | import java.nio.charset.Charset 21 | 22 | import scala.annotation.tailrec 23 | 24 | object IOUtil { 25 | val utf8 = Charset.forName("utf8") 26 | 27 | def writeToFile(string: String, file: File): Unit = 28 | sbt.IO.write(file, string, utf8) 29 | 30 | def saveResource(resourcePath: String, to: File): Unit = { 31 | val is = getClass.getClassLoader.getResourceAsStream(resourcePath) 32 | require(is ne null, s"Couldn't load '$resourcePath' from classpath.") 33 | 34 | val fos = new FileOutputStream(to) 35 | try copy(is, fos) 36 | finally { 37 | is.close() 38 | fos.close() 39 | } 40 | } 41 | 42 | def copy(from: InputStream, to: OutputStream): Unit = { 43 | val buffer = new Array[Byte](65536) 44 | 45 | @tailrec def rec(): Unit = { 46 | val read = from.read(buffer) 47 | if (read > 0) { 48 | to.write(buffer, 0, read) 49 | rec() 50 | } else if (read == 0) 51 | throw new IllegalStateException("InputStream.read returned 0") 52 | } 53 | rec() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/GraphML.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph.rendering 18 | 19 | import net.virtualvoid.sbt.graph.ModuleGraph 20 | 21 | import scala.xml.XML 22 | 23 | object GraphML { 24 | def saveAsGraphML(graph: ModuleGraph, outputFile: String): Unit = { 25 | val nodesXml = 26 | for (n ← graph.nodes) 27 | yield 28 | 29 | { n.id.idString } 30 | 31 | 32 | 33 | val edgesXml = 34 | for (e ← graph.edges) 35 | yield 36 | 37 | val xml = 38 | 39 | 40 | 41 | { nodesXml } 42 | { edgesXml } 43 | 44 | 45 | 46 | XML.save(outputFile, xml) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/whatDependsOn/build.sbt: -------------------------------------------------------------------------------- 1 | version := "0.1.0-SNAPSHOT" 2 | 3 | scalaVersion := "2.9.1" 4 | 5 | resolvers += "typesafe maven" at "https://repo.typesafe.com/typesafe/maven-releases/" 6 | 7 | libraryDependencies ++= Seq( 8 | "com.codahale" % "jerkson_2.9.1" % "0.5.0", 9 | "org.codehaus.jackson" % "jackson-mapper-asl" % "1.9.10" // as another version of asl 10 | ) 11 | 12 | val check = TaskKey[Unit]("check") 13 | 14 | check := { 15 | def sanitize(str: String): String = str.split('\n').map(_.trim).mkString("\n") 16 | def checkOutput(output: String, expected: String): Unit = 17 | require(sanitize(expected) == sanitize(output), s"Tree should have been [\n${sanitize(expected)}\n] but was [\n${sanitize(output)}\n]") 18 | 19 | val withVersion = 20 | (whatDependsOn in Compile) 21 | .toTask(" org.codehaus.jackson jackson-core-asl 1.9.11") 22 | .value 23 | val expectedGraphWithVersion = 24 | """org.codehaus.jackson:jackson-core-asl:1.9.11 25 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 26 | | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 27 | | | 28 | | +-org.codehaus.jackson:jackson-mapper-asl:1.9.11 29 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 30 | | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 31 | | | 32 | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 33 | | """.stripMargin 34 | 35 | checkOutput(withVersion, expectedGraphWithVersion) 36 | 37 | val withoutVersion = 38 | (whatDependsOn in Compile) 39 | .toTask(" org.codehaus.jackson jackson-mapper-asl") 40 | .value 41 | val expectedGraphWithoutVersion = 42 | """org.codehaus.jackson:jackson-mapper-asl:1.9.11 43 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 44 | | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 45 | | | 46 | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 47 | | 48 | |org.codehaus.jackson:jackson-mapper-asl:1.9.10 (evicted by: 1.9.11) 49 | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 50 | | """.stripMargin 51 | checkOutput(withoutVersion, expectedGraphWithoutVersion) 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayout.scala: -------------------------------------------------------------------------------- 1 | /* sbt -- Simple Build Tool 2 | * Copyright 2011 Mark Harrah, Eugene Yokota 3 | * 4 | * Copied from sbt 0.12 source code 5 | */ 6 | package net.virtualvoid.sbt.graph.util 7 | 8 | import sbt.dependencygraph.SbtAccess 9 | 10 | object AsciiTreeLayout { 11 | // [info] foo 12 | // [info] +-bar 13 | // [info] | +-baz 14 | // [info] | 15 | // [info] +-quux 16 | def toAscii[A]( 17 | top: A, 18 | children: A ⇒ Seq[A], 19 | display: A ⇒ String, 20 | maxColumn: Int = defaultColumnSize): String = { 21 | val twoSpaces = " " + " " // prevent accidentally being converted into a tab 22 | def limitLine(s: String): String = 23 | if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".." 24 | else s 25 | def insertBar(s: String, at: Int): String = 26 | if (at < s.length) 27 | s.slice(0, at) + 28 | (s(at).toString match { 29 | case " " ⇒ "|" 30 | case x ⇒ x 31 | }) + 32 | s.slice(at + 1, s.length) 33 | else s 34 | def toAsciiLines(node: A, level: Int, parents: Set[A]): Vector[String] = 35 | if (parents contains node) // cycle 36 | Vector(limitLine((twoSpaces * level) + "#-" + display(node) + " (cycle)")) 37 | else { 38 | val line = limitLine((twoSpaces * level) + (if (level == 0) "" else "+-") + display(node)) 39 | val cs = Vector(children(node): _*) 40 | val childLines = cs map { 41 | toAsciiLines(_, level + 1, parents + node) 42 | } 43 | val withBar = childLines.zipWithIndex flatMap { 44 | case (lines, pos) if pos < (cs.size - 1) ⇒ lines map { 45 | insertBar(_, 2 * (level + 1)) 46 | } 47 | case (lines, pos) ⇒ 48 | if (lines.last.trim != "") lines ++ Vector(twoSpaces * (level + 1)) 49 | else lines 50 | } 51 | line +: withBar 52 | } 53 | 54 | toAsciiLines(top, 0, Set.empty).mkString("\n") 55 | } 56 | 57 | def defaultColumnSize: Int = { 58 | val termWidth = SbtAccess.getTerminalWidth 59 | if (termWidth > 20) termWidth - 8 60 | else 80 // ignore termWidth 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/whatDependsOn-without-previous-initialization/build.sbt: -------------------------------------------------------------------------------- 1 | version := "0.1.0-SNAPSHOT" 2 | 3 | organization := "default" 4 | 5 | name := "whatDependsOn" 6 | 7 | scalaVersion := "2.9.1" 8 | 9 | resolvers += "typesafe maven" at "https://repo.typesafe.com/typesafe/maven-releases/" 10 | 11 | libraryDependencies ++= Seq( 12 | "com.codahale" % "jerkson_2.9.1" % "0.5.0", 13 | "org.codehaus.jackson" % "jackson-mapper-asl" % "1.9.10" // as another version of asl 14 | ) 15 | 16 | val check = TaskKey[Unit]("check") 17 | 18 | check := { 19 | def sanitize(str: String): String = str.split('\n').map(_.trim).mkString("\n") 20 | def checkOutput(output: String, expected: String): Unit = 21 | require(sanitize(expected) == sanitize(output), s"Tree should have been [\n${sanitize(expected)}\n] but was [\n${sanitize(output)}\n]") 22 | 23 | val withVersion = 24 | (whatDependsOn in Compile) 25 | .toTask(" org.codehaus.jackson jackson-core-asl 1.9.11") 26 | .value 27 | val expectedGraphWithVersion = 28 | """org.codehaus.jackson:jackson-core-asl:1.9.11 29 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 30 | | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 31 | | | 32 | | +-org.codehaus.jackson:jackson-mapper-asl:1.9.11 33 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 34 | | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 35 | | | 36 | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 37 | | """.stripMargin 38 | 39 | checkOutput(withVersion, expectedGraphWithVersion) 40 | 41 | val withoutVersion = 42 | (whatDependsOn in Compile) 43 | .toTask(" org.codehaus.jackson jackson-mapper-asl") 44 | .value 45 | val expectedGraphWithoutVersion = 46 | """org.codehaus.jackson:jackson-mapper-asl:1.9.11 47 | | +-com.codahale:jerkson_2.9.1:0.5.0 [S] 48 | | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 49 | | | 50 | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 51 | | 52 | |org.codehaus.jackson:jackson-mapper-asl:1.9.10 (evicted by: 1.9.11) 53 | | +-default:whatdependson_2.9.1:0.1.0-SNAPSHOT [S] 54 | | """.stripMargin 55 | checkOutput(withoutVersion, expectedGraphWithoutVersion) 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/backend/SbtUpdateReport.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package backend 19 | 20 | import scala.language.implicitConversions 21 | import scala.language.reflectiveCalls 22 | 23 | import sbt._ 24 | 25 | object SbtUpdateReport { 26 | type OrganizationArtifactReport = { 27 | def modules: Seq[ModuleReport] 28 | } 29 | 30 | def fromConfigurationReport(report: ConfigurationReport, rootInfo: sbt.ModuleID): ModuleGraph = { 31 | implicit def id(sbtId: sbt.ModuleID): ModuleId = ModuleId(sbtId.organization, sbtId.name, sbtId.revision) 32 | 33 | def moduleEdges(orgArt: OrganizationArtifactReport): Seq[(Module, Seq[Edge])] = { 34 | val chosenVersion = orgArt.modules.find(!_.evicted).map(_.module.revision) 35 | orgArt.modules.map(moduleEdge(chosenVersion)) 36 | } 37 | 38 | def moduleEdge(chosenVersion: Option[String])(report: ModuleReport): (Module, Seq[Edge]) = { 39 | val evictedByVersion = if (report.evicted) chosenVersion else None 40 | val jarFile = report.artifacts.find(_._1.`type` == "jar").orElse(report.artifacts.find(_._1.extension == "jar")).map(_._2) 41 | ( 42 | Module( 43 | id = report.module, 44 | license = report.licenses.headOption.map(_._1), 45 | evictedByVersion = evictedByVersion, 46 | jarFile = jarFile, 47 | error = report.problem), 48 | report.callers.map(caller ⇒ Edge(caller.caller, report.module))) 49 | } 50 | 51 | val (nodes, edges) = report.details.flatMap(moduleEdges).unzip 52 | val root = Module(rootInfo) 53 | 54 | ModuleGraph(root +: nodes, edges.flatten) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/GraphTransformations.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011, 2012 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | 19 | object GraphTransformations { 20 | def reverseGraphStartingAt(graph: ModuleGraph, root: ModuleId): ModuleGraph = { 21 | val deps = graph.reverseDependencyMap 22 | 23 | def visit(module: ModuleId, visited: Set[ModuleId]): Seq[(ModuleId, ModuleId)] = 24 | if (visited(module)) 25 | Nil 26 | else 27 | deps.get(module) match { 28 | case Some(deps) ⇒ 29 | deps.flatMap { to ⇒ 30 | (module, to.id) +: visit(to.id, visited + module) 31 | } 32 | case None ⇒ Nil 33 | } 34 | 35 | val edges = visit(root, Set.empty) 36 | val nodes = edges.foldLeft(Set.empty[ModuleId])((set, edge) ⇒ set + edge._1 + edge._2).map(graph.module) 37 | ModuleGraph(nodes.toSeq, edges) 38 | } 39 | 40 | def ignoreScalaLibrary(scalaVersion: String, graph: ModuleGraph): ModuleGraph = { 41 | def isScalaLibrary(m: Module) = isScalaLibraryId(m.id) 42 | def isScalaLibraryId(id: ModuleId) = id.organisation == "org.scala-lang" && id.name == "scala-library" 43 | 44 | def dependsOnScalaLibrary(m: Module): Boolean = 45 | graph.dependencyMap(m.id).exists(isScalaLibrary) 46 | 47 | def addScalaLibraryAnnotation(m: Module): Module = { 48 | if (dependsOnScalaLibrary(m)) 49 | m.copy(extraInfo = m.extraInfo + " [S]") 50 | else 51 | m 52 | } 53 | 54 | val newNodes = graph.nodes.map(addScalaLibraryAnnotation).filterNot(isScalaLibrary) 55 | val newEdges = graph.edges.filterNot(e ⇒ isScalaLibraryId(e._2)) 56 | ModuleGraph(newNodes, newEdges) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/scala-sbt-0.13/net/virtualvoid/sbt/graph/rendering/AsciiGraph.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | import com.github.mdr.ascii.layout._ 21 | import net.virtualvoid.sbt.graph.DependencyGraphKeys._ 22 | import sbt.Keys._ 23 | 24 | object AsciiGraph { 25 | def asciiGraph(graph: ModuleGraph): String = 26 | Layouter.renderGraph(buildAsciiGraph(graph)) 27 | 28 | private def buildAsciiGraph(moduleGraph: ModuleGraph): Graph[String] = { 29 | def renderVertex(module: Module): String = 30 | module.id.name + module.extraInfo + "\n" + 31 | module.id.organisation + "\n" + 32 | module.id.version + 33 | module.error.map("\nerror: " + _).getOrElse("") + 34 | module.evictedByVersion.map(_ formatted "\nevicted by: %s").getOrElse("") 35 | 36 | val vertices = moduleGraph.nodes.map(renderVertex).toList 37 | val edges = moduleGraph.edges.toList.map { case (from, to) ⇒ (renderVertex(moduleGraph.module(from)), renderVertex(moduleGraph.module(to))) } 38 | Graph(vertices, edges) 39 | } 40 | 41 | def asciiGraphSetttings = Seq[sbt.Def.Setting[_]]( 42 | DependencyGraphKeys.asciiGraph := asciiGraph(moduleGraph.value), 43 | dependencyGraph := { 44 | val force = DependencyGraphSettings.shouldForceParser.parsed 45 | val log = streams.value.log 46 | if (force || moduleGraph.value.nodes.size < 15) { 47 | log.info(rendering.AsciiGraph.asciiGraph(moduleGraph.value)) 48 | log.info("\n\n") 49 | log.info("Note: The old tree layout is still available by using `dependency-tree`") 50 | } 51 | 52 | log.info(rendering.AsciiTree.asciiTree(moduleGraph.value)) 53 | 54 | if (!force) { 55 | log.info("\n") 56 | log.info("Note: The graph was estimated to be too big to display (> 15 nodes). Use `sbt 'dependency-graph --force'` (with the single quotes) to force graph display.") 57 | } 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/backend/IvyReport.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package backend 19 | 20 | import scala.xml.{ NodeSeq, Document, Node } 21 | import scala.xml.parsing.ConstructingParser 22 | 23 | object IvyReport { 24 | def fromReportFile(ivyReportFile: String): ModuleGraph = 25 | fromReportXML(loadXML(ivyReportFile)) 26 | 27 | def fromReportXML(doc: Document): ModuleGraph = { 28 | def edgesForModule(id: ModuleId, revision: NodeSeq): Seq[Edge] = 29 | for { 30 | caller ← revision \ "caller" 31 | callerModule = moduleIdFromElement(caller, caller.attribute("callerrev").get.text) 32 | } yield (moduleIdFromElement(caller, caller.attribute("callerrev").get.text), id) 33 | 34 | val moduleEdges: Seq[(Module, Seq[Edge])] = for { 35 | mod ← doc \ "dependencies" \ "module" 36 | revision ← mod \ "revision" 37 | rev = revision.attribute("name").get.text 38 | moduleId = moduleIdFromElement(mod, rev) 39 | module = Module( 40 | moduleId, 41 | (revision \ "license").headOption.flatMap(_.attribute("name")).map(_.text), 42 | evictedByVersion = (revision \ "evicted-by").headOption.flatMap(_.attribute("rev").map(_.text)), 43 | error = revision.attribute("error").map(_.text)) 44 | } yield (module, edgesForModule(moduleId, revision)) 45 | 46 | val (nodes, edges) = moduleEdges.unzip 47 | 48 | val info = (doc \ "info").head 49 | def infoAttr(name: String): String = 50 | info.attribute(name).getOrElse(throw new IllegalArgumentException("Missing attribute " + name)).text 51 | val rootModule = Module(ModuleId(infoAttr("organisation"), infoAttr("module"), infoAttr("revision"))) 52 | 53 | ModuleGraph(rootModule +: nodes, edges.flatten) 54 | } 55 | 56 | private def moduleIdFromElement(element: Node, version: String): ModuleId = 57 | ModuleId(element.attribute("organisation").get.text, element.attribute("name").get.text, version) 58 | 59 | private def loadXML(ivyReportFile: String) = 60 | ConstructingParser.fromSource(io.Source.fromFile(ivyReportFile), preserveWS = false).document() 61 | } 62 | -------------------------------------------------------------------------------- /src/test/scala/net/virtualvoid/sbt/graph/util/AsciiTreeLayoutSpecs.scala: -------------------------------------------------------------------------------- 1 | package net.virtualvoid.sbt.graph.util 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class AsciiTreeLayoutSpecs extends Specification { 6 | sealed trait Tree 7 | case class Branch(left: Tree, right: Tree) extends Tree 8 | case class Leaf(i: Int) extends Tree 9 | 10 | def children(t: Tree): Seq[Tree] = t match { 11 | case Branch(left, right) ⇒ Seq(left, right) 12 | case _: Leaf ⇒ Nil 13 | } 14 | def display(t: Tree): String = t match { 15 | case Branch(left, right) ⇒ "Branch" 16 | case Leaf(value) ⇒ value.toString * value 17 | } 18 | 19 | "Graph" should { 20 | "layout simple graphs" in { 21 | val simple = Branch(Branch(Leaf(1), Leaf(2)), Leaf(3)) 22 | AsciiTreeLayout.toAscii(simple, children, display, 20) === 23 | """Branch 24 | | +-Branch 25 | | | +-1 26 | | | +-22 27 | | |\u0020 28 | | +-333 29 | | """.stripMargin 30 | } 31 | "add separator lines where applicable" in { 32 | val simple = Branch(Branch(Leaf(1), Branch(Leaf(2), Leaf(3))), Leaf(4)) 33 | AsciiTreeLayout.toAscii(simple, children, display, 20) === 34 | """Branch 35 | | +-Branch 36 | | | +-1 37 | | | +-Branch 38 | | | +-22 39 | | | +-333 40 | | |\u0020\u0020\u0020 41 | | +-4444 42 | | """.stripMargin 43 | } 44 | "layout deep graphs" in { 45 | val simple = Branch(Branch(Branch(Branch(Branch(Branch(Leaf(1), Leaf(1)), Leaf(1)), Leaf(1)), Leaf(2)), Leaf(3)), Leaf(4)) 46 | AsciiTreeLayout.toAscii(simple, children, display, 10) === 47 | """Branch 48 | | +-Branch 49 | | | +-Br.. 50 | | | | +-.. 51 | | | | | .. 52 | | | | | .. 53 | | | | | .. 54 | | | | | .. 55 | | | | | | |\u0020 56 | | | | | .. 57 | | | | | |\u0020 58 | | | | | .. 59 | | | | |\u0020 60 | | | | +-22 61 | | | |\u0020 62 | | | +-333 63 | | |\u0020 64 | | +-4444 65 | | """.stripMargin 66 | } 67 | "cut off cycles" in { 68 | AsciiTreeLayout.toAscii[Int](1, Map( 69 | 1 -> Seq(2, 3, 4), 70 | 2 -> Seq(4, 5), 71 | 3 -> Seq(), 72 | 4 -> Seq(3), 73 | 5 -> Seq(1, 4, 6, 7), 74 | 6 -> Seq(), 75 | 7 -> Seq()), _.toString).trim === 76 | """1 77 | | +-2 78 | | | +-4 79 | | | | +-3 80 | | | |\u0020 81 | | | +-5 82 | | | #-1 (cycle) 83 | | | +-4 84 | | | | +-3 85 | | | |\u0020 86 | | | +-6 87 | | | +-7 88 | | |\u0020\u0020\u0020 89 | | +-3 90 | | +-4 91 | | +-3""".stripMargin.trim 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/model.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | 19 | import java.io.File 20 | 21 | import sbinary.Format 22 | 23 | import scala.collection.mutable.{ HashMap, MultiMap, Set } 24 | 25 | case class ModuleId( 26 | organisation: String, 27 | name: String, 28 | version: String) { 29 | def idString: String = organisation + ":" + name + ":" + version 30 | } 31 | case class Module( 32 | id: ModuleId, 33 | license: Option[String] = None, 34 | extraInfo: String = "", 35 | evictedByVersion: Option[String] = None, 36 | jarFile: Option[File] = None, 37 | error: Option[String] = None) { 38 | def hadError: Boolean = error.isDefined 39 | def isUsed: Boolean = !isEvicted 40 | def isEvicted: Boolean = evictedByVersion.isDefined 41 | } 42 | 43 | object ModuleGraph { 44 | val empty = ModuleGraph(Seq.empty, Seq.empty) 45 | } 46 | 47 | case class ModuleGraph(nodes: Seq[Module], edges: Seq[Edge]) { 48 | lazy val modules: Map[ModuleId, Module] = 49 | nodes.map(n ⇒ (n.id, n)).toMap 50 | 51 | def module(id: ModuleId): Module = modules(id) 52 | 53 | lazy val dependencyMap: Map[ModuleId, Seq[Module]] = 54 | createMap(identity) 55 | 56 | lazy val reverseDependencyMap: Map[ModuleId, Seq[Module]] = 57 | createMap { case (a, b) ⇒ (b, a) } 58 | 59 | def createMap(bindingFor: ((ModuleId, ModuleId)) ⇒ (ModuleId, ModuleId)): Map[ModuleId, Seq[Module]] = { 60 | val m = new HashMap[ModuleId, Set[Module]] with MultiMap[ModuleId, Module] 61 | edges.foreach { entry ⇒ 62 | val (f, t) = bindingFor(entry) 63 | m.addBinding(f, module(t)) 64 | } 65 | m.toMap.mapValues(_.toSeq.sortBy(_.id.idString)).withDefaultValue(Nil) 66 | } 67 | 68 | def roots: Seq[Module] = 69 | nodes.filter(n ⇒ !edges.exists(_._2 == n.id)).sortBy(_.id.idString) 70 | } 71 | 72 | object ModuleGraphProtocol extends ModuleGraphProtocolCompat { 73 | import sbinary.DefaultProtocol._ 74 | 75 | implicit def seqFormat[T: Format]: Format[Seq[T]] = wrap[Seq[T], List[T]](_.toList, _.toSeq) 76 | implicit val ModuleIdFormat: Format[ModuleId] = asProduct3(ModuleId)(ModuleId.unapply(_).get) 77 | implicit val ModuleFormat: Format[Module] = asProduct6(Module)(Module.unapply(_).get) 78 | implicit val ModuleGraphFormat: Format[ModuleGraph] = asProduct2(ModuleGraph.apply _)(ModuleGraph.unapply(_).get) 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/Statistics.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | object Statistics { 21 | def renderModuleStatsList(graph: ModuleGraph): String = { 22 | case class ModuleStats( 23 | id: ModuleId, 24 | numDirectDependencies: Int, 25 | numTransitiveDependencies: Int, 26 | selfSize: Option[Long], 27 | transitiveSize: Long, 28 | transitiveDependencyStats: Map[ModuleId, ModuleStats]) { 29 | def transitiveStatsWithSelf: Map[ModuleId, ModuleStats] = transitiveDependencyStats + (id -> this) 30 | } 31 | 32 | def statsFor(moduleId: ModuleId): ModuleStats = { 33 | val directDependencies = graph.dependencyMap(moduleId).filterNot(_.isEvicted).map(_.id) 34 | val dependencyStats = directDependencies.map(statsFor).flatMap(_.transitiveStatsWithSelf).toMap 35 | val selfSize = graph.module(moduleId).jarFile.filter(_.exists).map(_.length) 36 | val numDirectDependencies = directDependencies.size 37 | val numTransitiveDependencies = dependencyStats.size 38 | val transitiveSize = selfSize.getOrElse(0L) + dependencyStats.map(_._2.selfSize.getOrElse(0L)).sum 39 | 40 | ModuleStats(moduleId, numDirectDependencies, numTransitiveDependencies, selfSize, transitiveSize, dependencyStats) 41 | } 42 | 43 | def format(stats: ModuleStats): String = { 44 | import stats._ 45 | def mb(bytes: Long): Double = bytes.toDouble / 1000000 46 | val selfSize = 47 | stats.selfSize match { 48 | case Some(size) ⇒ f"${mb(size)}%7.3f" 49 | case None ⇒ "-------" 50 | } 51 | f"${mb(transitiveSize)}%7.3f MB $selfSize MB $numTransitiveDependencies%4d $numDirectDependencies%4d ${id.idString}%s" 52 | } 53 | 54 | val allStats = 55 | graph.roots.flatMap(r ⇒ statsFor(r.id).transitiveStatsWithSelf).toMap.values.toSeq 56 | .sortBy(s ⇒ (-s.transitiveSize, -s.numTransitiveDependencies)) 57 | 58 | val header = " TotSize JarSize #TDe #Dep Module\n" 59 | 60 | header + 61 | allStats.map(format).mkString("\n") + 62 | """ 63 | | 64 | |Columns are 65 | | - Jar-Size including dependencies 66 | | - Jar-Size 67 | | - Number of transitive dependencies 68 | | - Number of direct dependencies 69 | | - ModuleID""".stripMargin 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/rendering/DOT.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | package rendering 19 | 20 | object DOT { 21 | val EvictedStyle = "stroke-dasharray: 5,5" 22 | 23 | def dotGraph( 24 | graph: ModuleGraph, 25 | dotHead: String, 26 | nodeFormation: (String, String, String) ⇒ String, 27 | labelRendering: HTMLLabelRendering): String = { 28 | val nodes = { 29 | for (n ← graph.nodes) yield { 30 | val style = if (n.isEvicted) EvictedStyle else "" 31 | val label = nodeFormation(n.id.organisation, n.id.name, n.id.version) 32 | """ "%s"[%s style="%s"]""".format( 33 | n.id.idString, 34 | labelRendering.renderLabel(label), 35 | style) 36 | } 37 | }.mkString("\n") 38 | 39 | def originWasEvicted(edge: Edge): Boolean = graph.module(edge._1).isEvicted 40 | def targetWasEvicted(edge: Edge): Boolean = graph.module(edge._2).isEvicted 41 | 42 | // add extra edges from evicted to evicted-by module 43 | val evictedByEdges: Seq[Edge] = 44 | graph.nodes.filter(_.isEvicted).map(m ⇒ Edge(m.id, m.id.copy(version = m.evictedByVersion.get))) 45 | 46 | // remove edges to new evicted-by module which is now replaced by a chain 47 | // dependend -> [evicted] -> dependee 48 | val evictionTargetEdges = 49 | graph.edges.filter(targetWasEvicted).map { 50 | case (from, evicted) ⇒ (from, evicted.copy(version = graph.module(evicted).evictedByVersion.get)) 51 | }.toSet 52 | 53 | val filteredEdges = 54 | graph.edges 55 | .filterNot(e ⇒ originWasEvicted(e) || evictionTargetEdges(e)) ++ evictedByEdges 56 | 57 | val edges = { 58 | for (e ← filteredEdges) yield { 59 | val extra = if (graph.module(e._1).isEvicted) 60 | s""" [label="Evicted By" style="$EvictedStyle"]""" else "" 61 | """ "%s" -> "%s"%s""".format(e._1.idString, e._2.idString, extra) 62 | } 63 | }.mkString("\n") 64 | 65 | "%s\n%s\n%s\n}".format(dotHead, nodes, edges) 66 | } 67 | 68 | sealed trait HTMLLabelRendering { 69 | def renderLabel(labelText: String): String 70 | } 71 | /** 72 | * Render HTML labels in Angle brackets as defined at http://graphviz.org/content/node-shapes#html 73 | */ 74 | case object AngleBrackets extends HTMLLabelRendering { 75 | def renderLabel(labelText: String): String = s"label=<$labelText>" 76 | } 77 | 78 | /** 79 | * Render HTML labels with `labelType="html"` and label content in double quotes as supported by 80 | * dagre-d3 81 | */ 82 | case object LabelTypeHtml extends HTMLLabelRendering { 83 | def renderLabel(labelText: String): String = s"""labelType="html" label="$labelText"""" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/sbt-test/sbt-dependency-graph/testDotFileGeneration/build.sbt: -------------------------------------------------------------------------------- 1 | import collection.mutable.ListBuffer 2 | 3 | import net.virtualvoid.sbt.graph.DependencyGraphKeys.dependencyDot 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | def defaultSettings = 8 | Seq( 9 | scalaVersion := "2.9.2", 10 | version := "0.1-SNAPSHOT" 11 | ) 12 | 13 | lazy val justATransiviteDependencyEndpointProject = 14 | Project("just-a-transitive-dependency-endpoint", file("a")) 15 | .settings(defaultSettings: _*) 16 | 17 | lazy val justATransitiveDependencyProject = 18 | Project("just-a-transitive-dependency", file("b")) 19 | .settings(defaultSettings: _*) 20 | .dependsOn(justATransiviteDependencyEndpointProject) 21 | 22 | lazy val justADependencyProject = 23 | Project("just-a-dependency", file("c")) 24 | .settings(defaultSettings: _*) 25 | 26 | lazy val test_project = 27 | Project("test-dot-file-generation", file("d")) 28 | .settings(defaultSettings: _*) 29 | .settings( 30 | TaskKey[Unit]("check") := { 31 | val dotFile = (dependencyDot in Compile).value 32 | val expectedGraph = 33 | """digraph "dependency-graph" { 34 | | graph[rankdir="LR"] 35 | | edge [ 36 | | arrowtail="none" 37 | | ] 38 | | "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT"[label=test-dot-file-generation_2.9.2
0.1-SNAPSHOT> style=""] 39 | | "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT"[label=just-a-transitive-dependency_2.9.2
0.1-SNAPSHOT> style=""] 40 | | "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT"[label=just-a-transitive-dependency-endpoint_2.9.2
0.1-SNAPSHOT> style=""] 41 | | "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT"[label=just-a-dependency_2.9.2
0.1-SNAPSHOT> style=""] 42 | | "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT" 43 | | "just-a-transitive-dependency:just-a-transitive-dependency_2.9.2:0.1-SNAPSHOT" -> "just-a-transitive-dependency-endpoint:just-a-transitive-dependency-endpoint_2.9.2:0.1-SNAPSHOT" 44 | | "test-dot-file-generation:test-dot-file-generation_2.9.2:0.1-SNAPSHOT" -> "just-a-dependency:just-a-dependency_2.9.2:0.1-SNAPSHOT" 45 | |} 46 | """.stripMargin 47 | 48 | val graph : String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString 49 | val errors = compareByLine(graph, expectedGraph) 50 | require(errors.isEmpty , errors.mkString("\n")) 51 | () 52 | } 53 | ) 54 | .dependsOn(justADependencyProject, justATransitiveDependencyProject) 55 | 56 | def compareByLine(got : String, expected : String) : Seq[String] = { 57 | val errors = ListBuffer[String]() 58 | got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach { case((got_line : String, expected_line : String), i : Int) => 59 | if(got_line != expected_line) { 60 | errors.append( 61 | """not matching lines at line %s 62 | |expected: %s 63 | |got: %s 64 | |""".stripMargin.format(i,expected_line, got_line)) 65 | } 66 | } 67 | errors 68 | } -------------------------------------------------------------------------------- /src/main/resources/graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | 30 | 31 | Dependency Graph 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 115 | 116 | -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphKeys.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | 19 | import sbt._ 20 | 21 | trait DependencyGraphKeys { 22 | val asString = TaskKey[String]("asString", "Provides the string value for the task it is scoped for") 23 | val printToConsole = TaskKey[Unit]("printToConsole", "Prints the tasks value to the console") 24 | val toFile = InputKey[File]("toFile", "Writes the task value to the given file") 25 | 26 | val dependencyGraphMLFile = SettingKey[File]( 27 | "dependency-graph-ml-file", 28 | "The location the graphml file should be generated at") 29 | val dependencyGraphML = TaskKey[File]( 30 | "dependency-graph-ml", 31 | "Creates a graphml file containing the dependency-graph for a project") 32 | val dependencyDotFile = SettingKey[File]( 33 | "dependency-dot-file", 34 | "The location the dot file should be generated at") 35 | val dependencyDotNodeLabel = SettingKey[(String, String, String) ⇒ String]( 36 | "dependency-dot-node-label", 37 | "Returns a formated string of a dependency. Takes organisation, name and version as parameters") 38 | val dependencyDotHeader = SettingKey[String]( 39 | "dependency-dot-header", 40 | "The header of the dot file. (e.g. to set your preferred node shapes)") 41 | val dependencyDot = TaskKey[File]( 42 | "dependency-dot", 43 | "Creates a dot file containing the dependency-graph for a project") 44 | val dependencyDotString = TaskKey[String]( 45 | "dependency-dot-string", 46 | "Creates a String containing the dependency-graph for a project in dot format") 47 | val dependencyBrowseGraphTarget = SettingKey[File]( 48 | "dependency-browse-graph-target", 49 | "The location dependency browse graph files should be put.") 50 | val dependencyBrowseGraphHTML = TaskKey[URI]( 51 | "dependency-browse-graph-html", 52 | "Creates an HTML page that can be used to view the graph.") 53 | val dependencyBrowseGraph = TaskKey[URI]( 54 | "dependency-browse-graph", 55 | "Opens an HTML page that can be used to view the graph.") 56 | val dependencyBrowseTreeTarget = SettingKey[File]( 57 | "dependency-browse-tree-target", 58 | "The location dependency browse tree files should be put.") 59 | val dependencyBrowseTreeHTML = TaskKey[URI]( 60 | "dependency-browse-tree-html", 61 | "Creates an HTML page that can be used to view the dependency tree") 62 | val dependencyBrowseTree = TaskKey[URI]( 63 | "dependency-browse-tree", 64 | "Opens an HTML page that can be used to view the dependency tree") 65 | val moduleGraph = TaskKey[ModuleGraph]( 66 | "module-graph", 67 | "The dependency graph for a project") 68 | val moduleGraphIvyReport = TaskKey[ModuleGraph]( 69 | "module-graph-ivy-report", 70 | "The dependency graph for a project as generated from an Ivy Report XML") 71 | val moduleGraphSbt = TaskKey[ModuleGraph]( 72 | "module-graph-sbt", 73 | "The dependency graph for a project as generated from SBT data structures.") 74 | val asciiGraph = TaskKey[String]( 75 | "dependency-graph-string", 76 | "Returns a string containing the ascii representation of the dependency graph for a project") 77 | val dependencyGraph = InputKey[Unit]( 78 | "dependency-graph", 79 | "Prints the ascii graph to the console") 80 | val asciiTree = TaskKey[String]( 81 | "dependency-tree-string", 82 | "Returns a string containing an ascii tree representation of the dependency graph for a project") 83 | val dependencyTree = TaskKey[Unit]( 84 | "dependency-tree", 85 | "Prints an ascii tree of all the dependencies to the console") 86 | val dependencyList = TaskKey[Unit]( 87 | "dependency-list", 88 | "Prints a list of all dependencies to the console") 89 | val dependencyStats = TaskKey[Unit]( 90 | "dependency-stats", 91 | "Prints statistics for all dependencies to the console") 92 | val ivyReportFunction = TaskKey[String ⇒ File]( 93 | "ivy-report-function", 94 | "A function which returns the file containing the ivy report from the ivy cache for a given configuration") 95 | val ivyReport = TaskKey[File]( 96 | "ivy-report", 97 | "A task which returns the location of the ivy report file for a given configuration (default `compile`).") 98 | val filterScalaLibrary = SettingKey[Boolean]( 99 | "filter-scala-library", 100 | "Specifies if scala dependency should be filtered in dependency-* output") 101 | 102 | val licenseInfo = TaskKey[Unit]( 103 | "dependency-license-info", 104 | "Aggregates and shows information about the licenses of dependencies") 105 | 106 | // internal 107 | private[graph] val ignoreMissingUpdate = TaskKey[UpdateReport]("dependencyUpdate", "sbt-dependency-graph version of update") 108 | private[graph] val moduleGraphStore = TaskKey[ModuleGraph]("module-graph-store", "The stored module-graph from the last run") 109 | val whatDependsOn = InputKey[String]("what-depends-on", "Shows information about what depends on the given module") 110 | private[graph] val crossProjectId = SettingKey[ModuleID]("dependency-graph-cross-project-id") 111 | } 112 | 113 | object DependencyGraphKeys extends DependencyGraphKeys 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sbt-dependency-graph 2 | 3 | [![Join the chat at https://gitter.im/jrudolph/sbt-dependency-graph](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jrudolph/sbt-dependency-graph?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Visualize your project's dependencies. 6 | 7 | **DEPRECATED: This plugin has been replaced by built-in sbt plugin in sbt 1.4+, please replace your dependency on this plugin with the following statement `addDependencyTreePlugin`** 8 | 9 | **Note: Under sbt >= 1.3.x some features might currently not work as expected or not at all (like `dependencyLicenses`).** 10 | 11 | ## Usage Instructions 12 | 13 | sbt-dependency-graph is an informational tool rather than one that changes your build, so you will more than likely wish to 14 | install it as a [global plugin] so that you can use it in any SBT project without the need to explicitly add it to each one. To do 15 | this, add the plugin dependency to `~/.sbt/0.13/plugins/plugins.sbt` for sbt 0.13 or `~/.sbt/1.0/plugins/plugins.sbt` for sbt 1.0: 16 | 17 | For sbt 1.4+ use: 18 | 19 | ```scala 20 | addDependencyTreePlugin 21 | ``` 22 | 23 | For sbt < 1.3 use: 24 | 25 | ```scala 26 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1") 27 | ``` 28 | 29 | To add the plugin only to a single project, put this line into `project/plugins.sbt` of your project, instead. 30 | 31 | The plugin currently supports sbt versions >= 0.13.10 and sbt 1.0.x. For versions supporting older versions of sbt see 32 | the notes of version [0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tree/v0.8.2#compatibility-notes). 33 | 34 | ## Main Tasks 35 | 36 | * `dependencyTree`: Shows an ASCII tree representation of the project's dependencies 37 | * `dependencyBrowseGraph`: Opens a browser window with a visualization of the dependency graph (courtesy of graphlib-dot + dagre-d3). 38 | * `dependencyBrowseTree`: Opens a browser window with a visualization of the dependency tree (courtesy of jstree). 39 | * `dependencyList`: Shows a flat list of all transitive dependencies on the sbt console (sorted by organization and name) 40 | * `whatDependsOn ?`: Find out what depends on an artifact. Shows a reverse dependency 41 | tree for the selected module. The `` argument is optional. 42 | * `dependencyLicenseInfo`: show dependencies grouped by declared license 43 | * `dependencyStats`: Shows a table with each module a row with (transitive) Jar sizes and number of dependencies 44 | * `dependencyGraphMl`: Generates a `.graphml` file with the project's dependencies to `target/dependencies-.graphml`. 45 | Use e.g. [yEd](http://www.yworks.com/en/products_yed_about.html) to format the graph to your needs. 46 | * `dependencyDot`: Generates a .dot file with the project's dependencies to `target/dependencies-.dot`. 47 | Use [graphviz](http://www.graphviz.org/) to render it to your preferred graphic format. 48 | * `dependencyGraph`: Shows an ASCII graph of the project's dependencies on the sbt console (only supported on sbt 0.13) 49 | * `ivyReport`: Lets ivy generate the resolution report for you project. Use 50 | `show ivyReport` for the filename of the generated report 51 | 52 | The following tasks also support the `toFile` subtask to save the contents to a file: 53 | 54 | * `dependencyTree` 55 | * `dependencyList` 56 | * `dependencyStats` 57 | * `dependencyLicenseInfo` 58 | 59 | The `toFile` subtask has the following syntax: 60 | 61 | ``` 62 | :::toFile [-f|--force] 63 | ``` 64 | 65 | Use `-f` to force overwriting an existing file. 66 | 67 | E.g. `test:dependencyStats::toFile target/depstats.txt` will write the output of the `dependencyStats` in the `test` 68 | configuration to the file `target/depstats.txt` but would not overwrite an existing file. 69 | 70 | All tasks can be scoped to a configuration to get the report for a specific configuration. `test:dependencyGraph`, 71 | for example, prints the dependencies in the `test` configuration. If you don't specify any configuration, `compile` is 72 | assumed as usual. 73 | 74 | Note: If you want to run tasks with parameters from outside the sbt shell, make sure to put the whole task invocation in 75 | quotes, e.g. `sbt "whatDependsOn "`. 76 | 77 | ## Configuration settings 78 | 79 | * `filterScalaLibrary`: Defines if the scala library should be excluded from the output of the dependency-* functions. 80 | If `true`, instead of showing the dependency `"[S]"` is appended to the artifact name. Set to `false` if 81 | you want the scala-library dependency to appear in the output. (default: true) 82 | * `dependencyGraphMLFile`: a setting which allows configuring the output path of `dependency-graph-ml`. 83 | * `dependencyDotFile`: a setting which allows configuring the output path of `dependency-dot`. 84 | * `dependencyDotHeader`: a setting to customize the header of the dot file (e.g. to set your preferred node shapes). 85 | * `dependencyDotNodeLabel`: defines the format of a node label 86 | (default set to `[organisation]
[name]
[version]`) 87 | 88 | E.g. in `build.sbt` you can change configuration settings like this: 89 | 90 | ```scala 91 | filterScalaLibrary := false // include scala library in output 92 | 93 | dependencyDotFile := file("dependencies.dot") //render dot file to `./dependencies.dot` 94 | ``` 95 | 96 | ## Known issues 97 | 98 | * [#19]: There's an unfixed bug with graph generation for particular layouts. Workaround: 99 | Use `dependency-tree` instead of `dependency-graph`. 100 | 101 | ## License 102 | 103 | Published under the [Apache License 2.0](http://en.wikipedia.org/wiki/Apache_license). 104 | 105 | [global plugin]: http://www.scala-sbt.org/0.13/tutorial/Using-Plugins.html#Global+plugins 106 | [global build configuration]: http://www.scala-sbt.org/0.13/docs/Global-Settings.html 107 | [#19]: https://github.com/jrudolph/sbt-dependency-graph/issues/19 108 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | * [#184](https://github.com/jrudolph/sbt-dependency-graph/pull/184): Fix regression in 0.10.0-RC1 for recent sbt versions when 5 | `cachedResolution` (with coursier turned off). Thanks [@bjaglin](https://github.com/bjaglin) for the report and the fix. 6 | 7 | ## Version 0.10.0-RC1 (2019-07-24) 8 | * [#136](https://github.com/jrudolph/sbt-dependency-graph/pull/136): Added `dependencyBrowseTree` to open a searchable dependency tree in the browser. 9 | Thanks, [@pcejrowski](https://github.com/pcejrowski) for contributing this feature. 10 | * [#163](https://github.com/jrudolph/sbt-dependency-graph/pull/163): Remove some usage of internal sbt APIs, this allows to run sbt-dependency-graph with sbt 1.3.0 11 | but results are not entirely correct. 12 | * [#165](https://github.com/jrudolph/sbt-dependency-graph/pull/165): For common operations introduce `asString`, `printToConsole`, and `toFile` subtasks 13 | 14 | ## Version 0.9.2 (2018-08-26) 15 | * [#159](https://github.com/jrudolph/sbt-dependency-graph/pull/159): Fixed regression in `whatDependsOn` where task parser failed when no other sbt-dependency-graph task was called before 16 | 17 | ## Version 0.9.1 (2018-07-17) 18 | 19 | * [#110](https://github.com/jrudolph/sbt-dependency-graph/issues/110): `whatDependsOn` can now be called without specifying a version. Thanks, @chikei for the initial implementation. 20 | * [#150](https://github.com/jrudolph/sbt-dependency-graph/issues/150): `ivyReport` now reports correct path again even for older sbt versions (< 0.13.16) 21 | 22 | ## Version 0.9.0 (2017-10-25) 23 | 24 | This version (finally!) adds support for sbt 1.0. *sbt-dependency-graph* depends on a lot of internals from sbt to do its 25 | work which is why it was quite an effort to do the migration. Thanks [@MasseGuillaume](https://github.com/MasseGuillaume) from Scala Center, 26 | [@2m](https://github.com/2m), and [@xuwei-k](https://github.com/xuwei-k) for helping out with the effort. 27 | 28 | The plugin is cross-built for sbt 0.13 (and will continued to be for while). The `dependencyGraph` task is currently not 29 | supported on sbt 1.0. Use `dependencyBrowseGraph`, instead. 30 | 31 | ## Version 0.8.2 (2016-02-01) 32 | 33 | This is a maintenance release [fixing](https://github.com/jrudolph/sbt-dependency-graph/issues/89) `dependencyBrowseGraph` 34 | in the latest Chrome versions. Thanks [@chtefi](https://github.com/chtefi)! 35 | 36 | ## Version 0.8.1 (2016-01-08) 37 | 38 | This is a maintenance release fixing a regression in 0.8.0 and adding two small features. 39 | 40 | All changes: 41 | 42 | * [#84](https://github.com/jrudolph/sbt-dependency-graph/issues/84): Fix regression of DOT label rendering introduced in 0.8.0. 43 | * [#83](https://github.com/jrudolph/sbt-dependency-graph/issues/83): Added new task `dependencyStats` which prints a 44 | simple table of jar sizes for all your dependencies. Handy if you want to know why your assembled jar gets so big. 45 | * [#85](https://github.com/jrudolph/sbt-dependency-graph/issues/85): Added new task `dependencyList` which prints a 46 | flat, deduplicated list of all the transitive dependencies. 47 | 48 | ## Version 0.8.0 (2015-11-26) 49 | 50 | sbt-dependency-graph is finally an AutoPlugin and can now show the dependency graph in the browser directly. 51 | 52 | ### New features 53 | 54 | - (experimental) open dependency graph directly in the browser with `dependencyBrowseGraph` ([#29](https://github.com/jrudolph/sbt-dependency-graph/issues/29)) 55 | ![dependencyBrowseGraph in action](https://gist.githubusercontent.com/jrudolph/941754bcf67a0fafe495/raw/7d80d766feb7af6ba2a69494e1f3ceb1fd40d4da/Screenshot%2520from%25202015-11-26%252014:18:19.png) 56 | 57 | - this plugin is finally an sbt AutoPlugin and it is automatically enabled 58 | ([#51](https://github.com/jrudolph/sbt-dependency-graph/issues/51)) 59 | 60 | **Note: To update from 0.7.x remove the `net.virtualvoid.sbt.graph.Plugin.graphSettings` line from your configurations.** 61 | 62 | ### Other changes 63 | 64 | - a new backend was implemented which accesses the in-memory dependency data structures of sbt directly. The plugin doesn't 65 | require accessing the ivy report XML any more (the old backend can still be wired in for comparisons if needed) which 66 | should have solved the race condition and the dreaded `FileNotFoundException` ([#39](https://github.com/jrudolph/sbt-dependency-graph/issues/39)) 67 | in multi-module projects. The new backend is only used for sbt >= 0.13.6. 68 | - code was restructured which touched a lot of the classes but didn't change the function or syntax of settings and tasks. 69 | - fixed [#77](https://github.com/jrudolph/sbt-dependency-graph/issues/77) 70 | 71 | 72 | ## Version 0.7.5 (2015-03-30) 73 | 74 | This is a maintenance release adding support for sbt 0.13.8. 75 | 76 | All changes: 77 | 78 | * [#67](https://github.com/jrudolph/sbt-dependency-graph/issues/67): Added support for sbt 0.13.8. Thanks 79 | [@eed3si9n](https://github.com/eed3si9n) for the fix. 80 | * [#37](https://github.com/jrudolph/sbt-dependency-graph/issues/37): Don't fail with StringIndexOutOfBoundsException 81 | for deep trees. 82 | * [#44](https://github.com/jrudolph/sbt-dependency-graph/issues/44): Only match scala lib by org/name. 83 | Thanks [@2beaucoup](https://github.com/2beaucoup) for the fix. 84 | 85 | ## Version 0.7.4 (2013-06-26) 86 | 87 | This is a maintenance release fixing an exception when generating graphs without a terminal [#32](https://github.com/jrudolph/sbt-dependency-graph/issues/32). 88 | 89 | ## Version 0.7.3 (2013-04-28) 90 | 91 | This is a maintenance release. Following issues have been fixed: 92 | 93 | * [#27](https://github.com/jrudolph/sbt-dependency-graph/issues/27): A dependency configured with 94 | a version range was not properly associated with its dependant. 95 | * [#30](https://github.com/jrudolph/sbt-dependency-graph/issues/30) & [#31](https://github.com/jrudolph/sbt-dependency-graph/issues/31): 96 | Make it work again with sbt 0.12.3. The path of the dependency resolution file changed in sbt 0.12.3. 97 | Thanks [ebowman](https://github.com/ebowman) for the fix. 98 | 99 | ## Version 0.7.2 (2013-03-02) 100 | 101 | This is a maintenance release. Following issues have been fixed: 102 | 103 | * [#27](https://github.com/jrudolph/sbt-dependency-graph/issues/27): A dependency configured with 104 | a version range was not properly associated with its dependant. 105 | 106 | 107 | ## Version 0.7.1 108 | 109 | New features in this version: 110 | 111 | * `dependency-license-info`: show dependencies grouped by declared license 112 | * `dependency-dot`: create dot file from dependency graph. Contributed by 113 | [berleon](https://github.com/berleon). 114 | 115 | ## Version 0.7.0 (2012-10-24) 116 | 117 | New features in this version: 118 | 119 | * `dependency-graph` now renders a real graph. Thanks go to [Matt Russell](https://github.com/mdr/) for 120 | this added awesomeness. 121 | * The tree output from previous versions is now available with `dependency-tree`. 122 | * New task `what-depends-on` showing reverse dependency tree for a selected module (incl. tab-completion for modules) 123 | * Don't fail in cases of a missing dependency. Show errors directly in the output. 124 | * Show info about evicted versions. 125 | * By default, exclude scala-library dependency and append `[S]` to the artifact name instead. Set 126 | `filter-scala-library` to `false` to disable this feature. 127 | * Works with sbt 0.12.1. The ivy report files were moved to a new location making an update necessary. 128 | 129 | 130 | ## Version 0.6.0 (2012-05-23) 131 | 132 | New features in this version: 133 | 134 | * `dependency-graph` task now prints the dependency graph to the console 135 | (contributed by @gseitz) 136 | * `dependency-graph-ml` contains now the old functionality of `dependency-graph` 137 | which generates a `.graphml` file. Nodes now contain the dependency version as well (contributed by @gseitz). 138 | * The output filename of `dependency-graph-ml` has been changed to include the configuration name. It is now 139 | configurable using the `dependency-graph-ml-file` setting. 140 | * The common `scalaVersion in update` idiom to support Scala 2.9.1 libraries in a 141 | Scala 2.9.2 broke the plugin in 0.5.2, because it wouldn't find the ivy report xml file 142 | any more. This was fixed. 143 | * All tasks are scoped by configuration. 144 | 145 | ## Version 0.5.2 (2012-02-13) 146 | 147 | ## Version 0.5.1 (2011-11-18) 148 | 149 | ## Version 0.5 (2011-11-15) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Johannes Rudolph 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package net.virtualvoid.sbt.graph 18 | 19 | import scala.language.reflectiveCalls 20 | import sbt._ 21 | import Keys._ 22 | import sbt.complete.Parser 23 | import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport } 24 | import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML, TreeView } 25 | import net.virtualvoid.sbt.graph.util.IOUtil 26 | import internal.librarymanagement._ 27 | import librarymanagement._ 28 | import sbt.dependencygraph.SbtAccess 29 | import sbt.dependencygraph.DependencyGraphSbtCompat.Implicits._ 30 | import sbt.complete.Parsers 31 | 32 | object DependencyGraphSettings { 33 | import DependencyGraphKeys._ 34 | import ModuleGraphProtocol._ 35 | 36 | def graphSettings = baseSettings ++ reportSettings 37 | 38 | def baseSettings = Seq( 39 | ivyReportFunction := ivyReportFunctionTask.value, 40 | 41 | // disable the cached resolution engine (exposing a scoped `ivyModule` used directly by `updateTask`), as it 42 | // generates artificial module descriptors which are internal to sbt, making it hard to reconstruct the 43 | // dependency tree 44 | updateOptions in ignoreMissingUpdate := updateOptions.value.withCachedResolution(false), 45 | ivyConfiguration in ignoreMissingUpdate := 46 | // inTask will make sure the new definition will pick up `updateOptions in ignoreMissingUpdate` 47 | SbtAccess.inTask(ignoreMissingUpdate, Classpaths.mkIvyConfiguration).value, 48 | ivyModule in ignoreMissingUpdate := { 49 | // concatenating & inlining ivySbt & ivyModule default task implementations, as `SbtAccess.inTask` does 50 | // NOT correctly force the scope when applied to `TaskKey.toTask` instances (as opposed to raw 51 | // implementations like `Classpaths.mkIvyConfiguration` or `Classpaths.updateTask`) 52 | val is = new IvySbt((ivyConfiguration in ignoreMissingUpdate).value) 53 | new is.Module(moduleSettings.value) 54 | }, 55 | 56 | // don't fail on missing dependencies 57 | updateConfiguration in ignoreMissingUpdate := updateConfiguration.value.withMissingOk(true), 58 | 59 | ignoreMissingUpdate := 60 | // inTask will make sure the new definition will pick up `ivyModule/updateConfiguration in ignoreMissingUpdate` 61 | SbtAccess.inTask(ignoreMissingUpdate, Classpaths.updateTask).value, 62 | 63 | filterScalaLibrary in Global := true) 64 | 65 | def reportSettings = 66 | Seq(Compile, Test, IntegrationTest, Runtime, Provided, Optional).flatMap(ivyReportForConfig) 67 | 68 | val renderingAlternatives: Seq[(TaskKey[Unit], ModuleGraph ⇒ String)] = 69 | Seq( 70 | dependencyTree -> rendering.AsciiTree.asciiTree _, 71 | dependencyList -> rendering.FlatList.render(_.id.idString), 72 | dependencyStats -> rendering.Statistics.renderModuleStatsList _, 73 | licenseInfo -> rendering.LicenseInfo.render _) 74 | 75 | def ivyReportForConfig(config: Configuration) = inConfig(config)( 76 | Seq( 77 | ivyReport := { Def.task { ivyReportFunction.value.apply(config.toString) } dependsOn (ignoreMissingUpdate) }.value, 78 | crossProjectId := sbt.CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(projectID.value), 79 | moduleGraphSbt := 80 | ignoreMissingUpdate.value.configuration(configuration.value).map(report ⇒ SbtUpdateReport.fromConfigurationReport(report, crossProjectId.value)).getOrElse(ModuleGraph.empty), 81 | moduleGraphIvyReport := IvyReport.fromReportFile(absoluteReportPath(ivyReport.value)), 82 | moduleGraph := { 83 | sbtVersion.value match { 84 | case Version(0, 13, x, _) if x >= 6 ⇒ moduleGraphSbt.value 85 | case Version(1, _, _, _) ⇒ moduleGraphSbt.value 86 | } 87 | }, 88 | moduleGraph := { 89 | // FIXME: remove busywork 90 | val scalaVersion = Keys.scalaVersion.value 91 | val moduleGraph = DependencyGraphKeys.moduleGraph.value 92 | 93 | if (filterScalaLibrary.value) GraphTransformations.ignoreScalaLibrary(scalaVersion, moduleGraph) 94 | else moduleGraph 95 | }, 96 | moduleGraphStore := (moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph).value, 97 | 98 | // browse 99 | dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" }, 100 | dependencyBrowseGraphHTML := browseGraphHTMLTask.value, 101 | dependencyBrowseGraph := openBrowser(dependencyBrowseGraphHTML).value, 102 | 103 | dependencyBrowseTreeTarget := { target.value / "browse-dependency-tree" }, 104 | dependencyBrowseTreeHTML := browseTreeHTMLTask.value, 105 | dependencyBrowseTree := openBrowser(dependencyBrowseTreeHTML).value, 106 | 107 | // dot support 108 | dependencyDotFile := { target.value / "dependencies-%s.dot".format(config.toString) }, 109 | dependencyDotString := rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.AngleBrackets), 110 | dependencyDot := writeToFile(dependencyDotString, dependencyDotFile).value, 111 | dependencyDotHeader := 112 | """|digraph "dependency-graph" { 113 | | graph[rankdir="LR"] 114 | | edge [ 115 | | arrowtail="none" 116 | | ]""".stripMargin, 117 | dependencyDotNodeLabel := { (organisation: String, name: String, version: String) ⇒ 118 | """%s
%s
%s""".format(organisation, name, version) 119 | }, 120 | 121 | // GraphML support 122 | dependencyGraphMLFile := { target.value / "dependencies-%s.graphml".format(config.toString) }, 123 | dependencyGraphML := dependencyGraphMLTask.value, 124 | 125 | whatDependsOn := { 126 | val ArtifactPattern(org, name, versionFilter) = artifactPatternParser.parsed 127 | val graph = moduleGraph.value 128 | val modules = 129 | versionFilter match { 130 | case Some(version) ⇒ ModuleId(org, name, version) :: Nil 131 | case None ⇒ graph.nodes.filter(m ⇒ m.id.organisation == org && m.id.name == name).map(_.id) 132 | } 133 | val output = 134 | modules 135 | .map { module ⇒ 136 | rendering.AsciiTree.asciiTree(GraphTransformations.reverseGraphStartingAt(graph, module)) 137 | } 138 | .mkString("\n") 139 | 140 | streams.value.log.info(output) 141 | output 142 | }, 143 | // deprecated settings 144 | asciiTree := (asString in dependencyTree).value) ++ 145 | renderingAlternatives.flatMap((renderingTaskSettings _).tupled) ++ 146 | AsciiGraph.asciiGraphSetttings) 147 | 148 | def renderingTaskSettings(key: TaskKey[Unit], renderer: ModuleGraph ⇒ String): Seq[Setting[_]] = 149 | Seq( 150 | asString in key := renderer(moduleGraph.value), 151 | printToConsole in key := streams.value.log.info((asString in key).value), 152 | toFile in key := { 153 | val (targetFile, force) = targetFileAndForceParser.parsed 154 | writeToFile(key.key.label, (asString in key).value, targetFile, force, streams.value) 155 | }, 156 | key := (printToConsole in key).value) 157 | 158 | def ivyReportFunctionTask = Def.task { 159 | val ivyConfig = Keys.ivyConfiguration.value.asInstanceOf[InlineIvyConfiguration] 160 | val projectID = Keys.projectID.value 161 | val ivyModule = Keys.ivyModule.value 162 | 163 | (config: String) ⇒ { 164 | val org = projectID.organization 165 | val name = crossName(ivyModule) 166 | new File(ivyConfig.resolutionCacheDir.get, s"reports/$org-$name-$config.xml") 167 | } 168 | } 169 | 170 | def dependencyGraphMLTask = 171 | Def.task { 172 | val resultFile = dependencyGraphMLFile.value 173 | rendering.GraphML.saveAsGraphML(moduleGraph.value, resultFile.getAbsolutePath) 174 | streams.value.log.info("Wrote dependency graph to '%s'" format resultFile) 175 | resultFile 176 | } 177 | 178 | def browseGraphHTMLTask = 179 | Def.task { 180 | val dotGraph = rendering.DOT.dotGraph(moduleGraph.value, dependencyDotHeader.value, dependencyDotNodeLabel.value, rendering.DOT.LabelTypeHtml) 181 | val link = DagreHTML.createLink(dotGraph, target.value) 182 | streams.value.log.info(s"HTML graph written to $link") 183 | link 184 | } 185 | 186 | def browseTreeHTMLTask = 187 | Def.task { 188 | val renderedTree = TreeView.createJson(moduleGraph.value) 189 | val link = TreeView.createLink(renderedTree, target.value) 190 | streams.value.log.info(s"HTML tree written to $link") 191 | link 192 | } 193 | 194 | def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) = 195 | Def.task { 196 | val outFile = fileTask.value 197 | IOUtil.writeToFile(dataTask.value, outFile) 198 | 199 | streams.value.log.info("Wrote dependency graph to '%s'" format outFile) 200 | outFile 201 | } 202 | 203 | def writeToFile(what: String, data: String, targetFile: File, force: Boolean, streams: TaskStreams): File = 204 | if (targetFile.exists && !force) 205 | throw new RuntimeException(s"Target file for $what already exists at ${targetFile.getAbsolutePath}. Use '-f' to override") 206 | else { 207 | IOUtil.writeToFile(data, targetFile) 208 | 209 | streams.log.info(s"Wrote $what to '$targetFile'") 210 | targetFile 211 | } 212 | 213 | def absoluteReportPath = (file: File) ⇒ file.getAbsolutePath 214 | 215 | def openBrowser(uriKey: TaskKey[URI]) = 216 | Def.task { 217 | val uri = uriKey.value 218 | streams.value.log.info("Opening in browser...") 219 | java.awt.Desktop.getDesktop.browse(uri) 220 | uri 221 | } 222 | 223 | case class ArtifactPattern( 224 | organisation: String, 225 | name: String, 226 | version: Option[String]) 227 | 228 | import sbt.complete.DefaultParsers._ 229 | val artifactPatternParser: Def.Initialize[State ⇒ Parser[ArtifactPattern]] = 230 | resolvedScoped { ctx ⇒ (state: State) ⇒ 231 | val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil) 232 | 233 | graph.nodes 234 | .map(_.id) 235 | .groupBy(m ⇒ (m.organisation, m.name)) 236 | .map { 237 | case ((org, name), modules) ⇒ 238 | val versionParsers: Seq[Parser[Option[String]]] = 239 | modules.map { id ⇒ 240 | token(Space ~> id.version).? 241 | } 242 | 243 | (Space ~> token(org) ~ token(Space ~> name) ~ oneOf(versionParsers)).map { 244 | case ((org, name), version) ⇒ ArtifactPattern(org, name, version) 245 | } 246 | } 247 | .reduceOption(_ | _).getOrElse { 248 | // If the moduleGraphStore couldn't be loaded because no dependency tree command was run before, we should still provide a parser for the command. 249 | ((Space ~> token(StringBasic, "")) ~ (Space ~> token(StringBasic, "")) ~ (Space ~> token(StringBasic, "")).?).map { 250 | case ((org, mod), version) ⇒ 251 | ArtifactPattern(org, mod, version) 252 | } 253 | } 254 | } 255 | val shouldForceParser: Parser[Boolean] = (Space ~> (Parser.literal("-f") | "--force")).?.map(_.isDefined) 256 | 257 | val targetFileAndForceParser: Parser[(File, Boolean)] = 258 | Parsers.fileParser(new File(".")) ~ shouldForceParser 259 | 260 | // This is to support 0.13.8's InlineConfigurationWithExcludes while not forcing 0.13.8 261 | type HasModule = { 262 | val module: ModuleID 263 | } 264 | def crossName(ivyModule: IvySbt#Module) = 265 | ivyModule.moduleSettings match { 266 | case ic: InlineConfiguration ⇒ ic.module.name 267 | case hm: HasModule @unchecked if hm.getClass.getName == "sbt.InlineConfigurationWithExcludes" ⇒ hm.module.name 268 | case _ ⇒ 269 | throw new IllegalStateException("sbt-dependency-graph plugin currently only supports InlineConfiguration of ivy settings (the default in sbt)") 270 | } 271 | 272 | val VersionPattern = """(\d+)\.(\d+)\.(\d+)(?:-(.*))?""".r 273 | object Version { 274 | def unapply(str: String): Option[(Int, Int, Int, Option[String])] = str match { 275 | case VersionPattern(major, minor, fix, appendix) ⇒ Some((major.toInt, minor.toInt, fix.toInt, Option(appendix))) 276 | case _ ⇒ None 277 | } 278 | } 279 | } 280 | --------------------------------------------------------------------------------