mirror of https://github.com/sbt/sbt.git
refactor: Unify to dependency-tree
**Problem**
While sbt-dependency-graph is useful, not just for the basic ASCII graph,
but for DOT file generation etc, it adds a large number of settings and
tasks for combination of formats and actions to the point that
we actually disable most of them by default.
**Solution*
I've had an idea for a while that dependencyTree can be implemented
as a inputTask that accepts its own subcommands and options,
and this implements that.
For example, to open the browser that hosts a DOT file, now you can write
dependencyTree dot --browse
This commit is contained in:
parent
09b9a97437
commit
61fe604519
|
|
@ -157,7 +157,7 @@ jobs:
|
||||||
if: ${{ matrix.jobtype == 3 }}
|
if: ${{ matrix.jobtype == 3 }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# ./sbt -v "dependencyTreeProj/publishLocal; scripted dependency-graph/*"
|
./sbt -v --client "scripted dependency-graph/*"
|
||||||
./sbt -v --client "scripted dependency-management/* project-load/* project-matrix/* java/* run/*"
|
./sbt -v --client "scripted dependency-management/* project-load/* project-matrix/* java/* run/*"
|
||||||
# ./sbt -v --client "scripted plugins/*"
|
# ./sbt -v --client "scripted plugins/*"
|
||||||
# ./sbt -v --client "scripted nio/*"
|
# ./sbt -v --client "scripted nio/*"
|
||||||
|
|
|
||||||
14
build.sbt
14
build.sbt
|
|
@ -628,19 +628,6 @@ lazy val scriptedSbtProj = (project in file("scripted-sbt"))
|
||||||
.dependsOn(lmCore)
|
.dependsOn(lmCore)
|
||||||
.configure(addSbtIO, addSbtCompilerInterface)
|
.configure(addSbtIO, addSbtCompilerInterface)
|
||||||
|
|
||||||
lazy val dependencyTreeProj = (project in file("dependency-tree"))
|
|
||||||
.dependsOn(sbtProj)
|
|
||||||
.settings(
|
|
||||||
sbtPlugin := true,
|
|
||||||
baseSettings,
|
|
||||||
name := "sbt-dependency-tree",
|
|
||||||
pluginCrossBuild / sbtVersion := version.value,
|
|
||||||
publishMavenStyle := true,
|
|
||||||
sbtPluginPublishLegacyMavenStyle := false,
|
|
||||||
// mimaSettings,
|
|
||||||
mimaPreviousArtifacts := Set.empty,
|
|
||||||
)
|
|
||||||
|
|
||||||
lazy val remoteCacheProj = (project in file("sbt-remote-cache"))
|
lazy val remoteCacheProj = (project in file("sbt-remote-cache"))
|
||||||
.dependsOn(sbtProj)
|
.dependsOn(sbtProj)
|
||||||
.settings(
|
.settings(
|
||||||
|
|
@ -1212,7 +1199,6 @@ def allProjects =
|
||||||
stdTaskProj,
|
stdTaskProj,
|
||||||
runProj,
|
runProj,
|
||||||
scriptedSbtProj,
|
scriptedSbtProj,
|
||||||
dependencyTreeProj,
|
|
||||||
protocolProj,
|
protocolProj,
|
||||||
actionsProj,
|
actionsProj,
|
||||||
commandProj,
|
commandProj,
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* sbt
|
|
||||||
* Copyright 2023, Scala center
|
|
||||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
|
||||||
* Copyright 2008 - 2010, Mark Harrah
|
|
||||||
* Licensed under Apache License 2.0 (see LICENSE)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sbt
|
|
||||||
package plugins
|
|
||||||
|
|
||||||
import scala.annotation.nowarn
|
|
||||||
|
|
||||||
object DependencyTreePlugin extends AutoPlugin {
|
|
||||||
object autoImport extends DependencyTreeKeys
|
|
||||||
override def trigger = AllRequirements
|
|
||||||
override def requires = MiniDependencyTreePlugin
|
|
||||||
|
|
||||||
@nowarn
|
|
||||||
val configurations = Vector(Compile, Test, Runtime, Provided, Optional)
|
|
||||||
|
|
||||||
// MiniDependencyTreePlugin provides baseBasicReportingSettings for Compile and Test
|
|
||||||
override lazy val projectSettings: Seq[Def.Setting[?]] =
|
|
||||||
configurations.diff(Vector(Compile, Test)).flatMap { config =>
|
|
||||||
inConfig(config)(DependencyTreeSettings.baseBasicReportingSettings)
|
|
||||||
} ++
|
|
||||||
configurations.flatMap { config =>
|
|
||||||
inConfig(config)(DependencyTreeSettings.baseFullReportingSettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4442,16 +4442,6 @@ trait BuildExtra extends BuildCommon with DefExtra {
|
||||||
Seq(compose(onLoad, add), compose(onUnload, remove))
|
Seq(compose(onLoad, add), compose(onUnload, remove))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds Dependency tree plugin.
|
|
||||||
*/
|
|
||||||
def addDependencyTreePlugin: Setting[Seq[ModuleID]] =
|
|
||||||
libraryDependencies += sbtPluginExtra(
|
|
||||||
ModuleID("org.scala-sbt", "sbt-dependency-tree", sbtVersion.value),
|
|
||||||
sbtBinaryVersion.value,
|
|
||||||
scalaBinaryVersion.value
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds Maven resolver plugin.
|
* Adds Maven resolver plugin.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ object PluginDiscovery:
|
||||||
"sbt.plugins.SemanticdbPlugin" -> sbt.plugins.SemanticdbPlugin,
|
"sbt.plugins.SemanticdbPlugin" -> sbt.plugins.SemanticdbPlugin,
|
||||||
"sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin,
|
"sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin,
|
||||||
"sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin,
|
"sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin,
|
||||||
"sbt.plugins.MiniDependencyTreePlugin" -> sbt.plugins.MiniDependencyTreePlugin,
|
"sbt.plugins.DependencyTreePlugin" -> sbt.plugins.DependencyTreePlugin,
|
||||||
)
|
)
|
||||||
val detectedAutoPlugins = discover[AutoPlugin](AutoPlugins)
|
val detectedAutoPlugins = discover[AutoPlugin](AutoPlugins)
|
||||||
val allAutoPlugins = (defaultAutoPlugins ++ detectedAutoPlugins.modules) map { (name, value) =>
|
val allAutoPlugins = (defaultAutoPlugins ++ detectedAutoPlugins.modules) map { (name, value) =>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ import sbt.io.IO
|
||||||
|
|
||||||
object DagreHTML {
|
object DagreHTML {
|
||||||
def createLink(dotGraph: String, targetDirectory: File): URI = {
|
def createLink(dotGraph: String, targetDirectory: File): URI = {
|
||||||
|
val graphHTML = createFile(dotGraph, targetDirectory)
|
||||||
|
new URI(graphHTML.toURI.toString)
|
||||||
|
}
|
||||||
|
|
||||||
|
def createFile(dotGraph: String, targetDirectory: File): File = {
|
||||||
targetDirectory.mkdirs()
|
targetDirectory.mkdirs()
|
||||||
val graphHTML = new File(targetDirectory, "graph.html")
|
val graphHTML = new File(targetDirectory, "graph.html")
|
||||||
TreeView.saveResource("graph.html", graphHTML)
|
TreeView.saveResource("graph.html", graphHTML)
|
||||||
|
|
@ -33,7 +38,6 @@ object DagreHTML {
|
||||||
s"""data = "$graphString";""",
|
s"""data = "$graphString";""",
|
||||||
IO.utf8
|
IO.utf8
|
||||||
)
|
)
|
||||||
|
graphHTML
|
||||||
new URI(graphHTML.toURI.toString)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,25 @@ package internal
|
||||||
package graph
|
package graph
|
||||||
package rendering
|
package rendering
|
||||||
|
|
||||||
import scala.xml.XML
|
import java.io.StringWriter
|
||||||
|
import scala.xml.{ Elem, XML }
|
||||||
|
|
||||||
object GraphML {
|
object GraphML:
|
||||||
def saveAsGraphML(graph: ModuleGraph, outputFile: String): Unit = {
|
def graphML(graph: ModuleGraph): Elem =
|
||||||
val nodesXml =
|
val nodesXml =
|
||||||
for (n <- graph.nodes)
|
for n <- graph.nodes
|
||||||
yield <node id={n.id.idString}><data key="d0">
|
yield <node id={n.id.idString}>
|
||||||
<y:ShapeNode>
|
<data key="d0">
|
||||||
<y:NodeLabel>{n.id.idString}</y:NodeLabel>
|
<y:ShapeNode>
|
||||||
</y:ShapeNode>
|
<y:NodeLabel>{n.id.idString}</y:NodeLabel>
|
||||||
</data></node>
|
</y:ShapeNode>
|
||||||
|
</data></node>
|
||||||
|
|
||||||
val edgesXml =
|
val edgesXml =
|
||||||
for (e <- graph.edges)
|
for e <- graph.edges
|
||||||
yield <edge source={e._1.idString} target={e._2.idString}/>
|
yield <edge source={e._1.idString} target={e._2.idString}/>
|
||||||
|
|
||||||
val xml =
|
val r =
|
||||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
||||||
<key for="node" id="d0" yfiles.type="nodegraphics"/>
|
<key for="node" id="d0" yfiles.type="nodegraphics"/>
|
||||||
<graph id="Graph" edgedefault="undirected">
|
<graph id="Graph" edgedefault="undirected">
|
||||||
|
|
@ -35,7 +37,13 @@ object GraphML {
|
||||||
{edgesXml}
|
{edgesXml}
|
||||||
</graph>
|
</graph>
|
||||||
</graphml>
|
</graphml>
|
||||||
|
r
|
||||||
|
|
||||||
XML.save(outputFile, xml)
|
def graphMLAsString(graph: ModuleGraph): String =
|
||||||
}
|
val w = StringWriter()
|
||||||
}
|
XML.write(w, graphML(graph), "UTF-8", false, None.orNull)
|
||||||
|
w.toString()
|
||||||
|
|
||||||
|
def saveAsGraphML(graph: ModuleGraph, outputFile: String): Unit =
|
||||||
|
XML.save(outputFile, graphML(graph))
|
||||||
|
end GraphML
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,17 @@ object TreeView {
|
||||||
}
|
}
|
||||||
|
|
||||||
def createLink(graphJson: String, targetDirectory: File): URI = {
|
def createLink(graphJson: String, targetDirectory: File): URI = {
|
||||||
|
val graphHTML = createFile(graphJson, targetDirectory)
|
||||||
|
new URI(graphHTML.toURI.toString)
|
||||||
|
}
|
||||||
|
|
||||||
|
def createFile(graphJson: String, targetDirectory: File): File = {
|
||||||
targetDirectory.mkdirs()
|
targetDirectory.mkdirs()
|
||||||
val graphHTML = new File(targetDirectory, "tree.html")
|
val graphHTML = new File(targetDirectory, "tree.html")
|
||||||
saveResource("tree.html", graphHTML)
|
saveResource("tree.html", graphHTML)
|
||||||
IO.write(new File(targetDirectory, "tree.json"), graphJson, IO.utf8)
|
IO.write(new File(targetDirectory, "tree.json"), graphJson, IO.utf8)
|
||||||
IO.write(new File(targetDirectory, "tree.data.js"), s"tree_data = $graphJson;", IO.utf8)
|
IO.write(new File(targetDirectory, "tree.data.js"), s"tree_data = $graphJson;", IO.utf8)
|
||||||
new URI(graphHTML.toURI.toString)
|
graphHTML
|
||||||
}
|
}
|
||||||
|
|
||||||
private[rendering] def processSubtree(
|
private[rendering] def processSubtree(
|
||||||
|
|
|
||||||
|
|
@ -9,39 +9,15 @@
|
||||||
package sbt
|
package sbt
|
||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URI
|
|
||||||
import sbt.internal.graph.*
|
import sbt.internal.graph.*
|
||||||
import sbt.Def.*
|
import sbt.Def.*
|
||||||
import sbt.librarymanagement.{ ModuleID, UpdateReport }
|
import sbt.librarymanagement.{ ModuleID, UpdateReport }
|
||||||
|
|
||||||
trait MiniDependencyTreeKeys {
|
abstract class DependencyTreeKeys:
|
||||||
|
val dependencyTree = inputKey[String]("Displays dependencies in ascii tree and other formats")
|
||||||
val dependencyTreeIncludeScalaLibrary = settingKey[Boolean](
|
val dependencyTreeIncludeScalaLibrary = settingKey[Boolean](
|
||||||
"Specifies if scala dependency should be included in dependencyTree output"
|
"Specifies if scala dependency should be included in dependencyTree output"
|
||||||
)
|
)
|
||||||
val dependencyTree = taskKey[Unit]("Prints an ascii tree of all the dependencies to the console")
|
|
||||||
val asString = taskKey[String]("Provides the string value for the task it is scoped for")
|
|
||||||
// val printToConsole = TaskKey[Unit]("printToConsole", "Prints the tasks value to the console")
|
|
||||||
val toFile = inputKey[File]("Writes the task value to the given file")
|
|
||||||
|
|
||||||
// internal
|
|
||||||
private[sbt] val dependencyTreeIgnoreMissingUpdate =
|
|
||||||
taskKey[UpdateReport]("update used for dependencyTree task")
|
|
||||||
private[sbt] val dependencyTreeModuleGraphStore =
|
|
||||||
taskKey[ModuleGraph]("The stored module-graph from the last run")
|
|
||||||
val whatDependsOn = inputKey[String]("Shows information about what depends on the given module")
|
|
||||||
private[sbt] val dependencyTreeCrossProjectId = settingKey[ModuleID]("")
|
|
||||||
}
|
|
||||||
|
|
||||||
object MiniDependencyTreeKeys extends MiniDependencyTreeKeys
|
|
||||||
|
|
||||||
abstract class DependencyTreeKeys {
|
|
||||||
val dependencyGraphMLFile =
|
|
||||||
settingKey[File]("The location the graphml file should be generated at")
|
|
||||||
val dependencyGraphML =
|
|
||||||
taskKey[File]("Creates a graphml file containing the dependency-graph for a project")
|
|
||||||
val dependencyDotFile =
|
|
||||||
settingKey[File]("The location the dot file should be generated at")
|
|
||||||
val dependencyDotNodeColors = settingKey[Boolean](
|
val dependencyDotNodeColors = settingKey[Boolean](
|
||||||
"The boxes of nodes are painted with colors. Otherwise they're black."
|
"The boxes of nodes are painted with colors. Otherwise they're black."
|
||||||
)
|
)
|
||||||
|
|
@ -51,41 +27,18 @@ abstract class DependencyTreeKeys {
|
||||||
val dependencyDotHeader = settingKey[String](
|
val dependencyDotHeader = settingKey[String](
|
||||||
"The header of the dot file. (e.g. to set your preferred node shapes)"
|
"The header of the dot file. (e.g. to set your preferred node shapes)"
|
||||||
)
|
)
|
||||||
val dependencyDot = taskKey[File](
|
|
||||||
"Creates a dot file containing the dependency-graph for a project"
|
// internal
|
||||||
)
|
private[sbt] val dependencyTreeIgnoreMissingUpdate =
|
||||||
val dependencyDotString = taskKey[String](
|
taskKey[UpdateReport]("update used for dependencyTree task")
|
||||||
"Creates a String containing the dependency-graph for a project in dot format"
|
private[sbt] val dependencyTreeModuleGraphStore =
|
||||||
)
|
taskKey[ModuleGraph]("The stored module-graph from the last run")
|
||||||
val dependencyBrowseGraphTarget = settingKey[File](
|
val whatDependsOn = inputKey[String]("Shows information about what depends on the given module")
|
||||||
"The location dependency browse graph files should be put."
|
private[sbt] val dependencyTreeCrossProjectId = settingKey[ModuleID]("")
|
||||||
)
|
|
||||||
val dependencyBrowseGraphHTML = taskKey[URI](
|
|
||||||
"Creates an HTML page that can be used to view the graph."
|
|
||||||
)
|
|
||||||
val dependencyBrowseGraph = taskKey[URI](
|
|
||||||
"Opens an HTML page that can be used to view the graph."
|
|
||||||
)
|
|
||||||
val dependencyBrowseTreeTarget = settingKey[File](
|
|
||||||
"The location dependency browse tree files should be put."
|
|
||||||
)
|
|
||||||
val dependencyBrowseTreeHTML = taskKey[URI](
|
|
||||||
"Creates an HTML page that can be used to view the dependency tree"
|
|
||||||
)
|
|
||||||
val dependencyBrowseTree = taskKey[URI](
|
|
||||||
"Opens an HTML page that can be used to view the dependency tree"
|
|
||||||
)
|
|
||||||
// 0 was added to avoid conflict with sbt-dependency-tree
|
// 0 was added to avoid conflict with sbt-dependency-tree
|
||||||
private[sbt] val dependencyTreeModuleGraph0 =
|
private[sbt] val dependencyTreeModuleGraph0 =
|
||||||
taskKey[ModuleGraph]("The dependency graph for a project")
|
taskKey[ModuleGraph]("The dependency graph for a project")
|
||||||
|
end DependencyTreeKeys
|
||||||
val dependencyList =
|
|
||||||
taskKey[Unit]("Prints a list of all dependencies to the console")
|
|
||||||
val dependencyStats =
|
|
||||||
taskKey[Unit]("Prints statistics for all dependencies to the console")
|
|
||||||
val dependencyLicenseInfo = taskKey[Unit](
|
|
||||||
"Aggregates and shows information about the licenses of dependencies"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object DependencyTreeKeys extends DependencyTreeKeys
|
object DependencyTreeKeys extends DependencyTreeKeys
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2023, Scala center
|
||||||
|
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sbt
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import sbt.PluginTrigger.AllRequirements
|
||||||
|
import sbt.ProjectExtra.*
|
||||||
|
import sbt.librarymanagement.Configurations.{ Compile, Test }
|
||||||
|
|
||||||
|
object DependencyTreePlugin extends AutoPlugin {
|
||||||
|
object autoImport extends DependencyTreeKeys
|
||||||
|
|
||||||
|
private val defaultDependencyDotHeader =
|
||||||
|
"""|digraph "dependency-graph" {
|
||||||
|
| graph[rankdir="LR"; splines=polyline]
|
||||||
|
| edge [
|
||||||
|
| arrowtail="none"
|
||||||
|
| ]""".stripMargin
|
||||||
|
|
||||||
|
private val defaultDependencyDotNodeLabel =
|
||||||
|
(organization: String, name: String, version: String) =>
|
||||||
|
s"""${organization}<BR/><B>${name}</B><BR/>${version}"""
|
||||||
|
|
||||||
|
import autoImport.*
|
||||||
|
override def trigger: PluginTrigger = AllRequirements
|
||||||
|
override def globalSettings: Seq[Def.Setting[?]] = Seq(
|
||||||
|
dependencyTreeIncludeScalaLibrary :== false,
|
||||||
|
dependencyDotNodeColors :== true,
|
||||||
|
dependencyDotHeader := defaultDependencyDotHeader,
|
||||||
|
dependencyDotNodeLabel := defaultDependencyDotNodeLabel,
|
||||||
|
)
|
||||||
|
override lazy val projectSettings: Seq[Def.Setting[?]] =
|
||||||
|
DependencyTreeSettings.coreSettings ++
|
||||||
|
inConfig(Compile)(DependencyTreeSettings.baseSettings) ++
|
||||||
|
inConfig(Test)(DependencyTreeSettings.baseSettings)
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ package sbt
|
||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
import sbt.Def.*
|
import sbt.Def.*
|
||||||
import sbt.Keys.*
|
import sbt.Keys.*
|
||||||
|
|
@ -19,15 +20,88 @@ import sbt.internal.graph.backend.SbtUpdateReport
|
||||||
import sbt.internal.graph.rendering.{ DagreHTML, TreeView }
|
import sbt.internal.graph.rendering.{ DagreHTML, TreeView }
|
||||||
import sbt.internal.librarymanagement.*
|
import sbt.internal.librarymanagement.*
|
||||||
import sbt.internal.util.complete.{ Parser, Parsers }
|
import sbt.internal.util.complete.{ Parser, Parsers }
|
||||||
|
import sbt.internal.util.complete.DefaultParsers.*
|
||||||
import sbt.io.IO
|
import sbt.io.IO
|
||||||
import sbt.io.syntax.*
|
import sbt.io.syntax.*
|
||||||
import sbt.librarymanagement.*
|
import sbt.librarymanagement.*
|
||||||
|
import sbt.util.Logger
|
||||||
|
import scala.Console
|
||||||
|
|
||||||
object DependencyTreeSettings {
|
private[sbt] object DependencyTreeSettings:
|
||||||
import sjsonnew.BasicJsonProtocol.*
|
import sjsonnew.BasicJsonProtocol.*
|
||||||
import MiniDependencyTreeKeys.*
|
|
||||||
import DependencyTreeKeys.*
|
import DependencyTreeKeys.*
|
||||||
|
|
||||||
|
enum Arg:
|
||||||
|
case Help
|
||||||
|
case Quiet
|
||||||
|
case Format(format: Fmt)
|
||||||
|
case Out(out: String)
|
||||||
|
case Browse
|
||||||
|
|
||||||
|
enum Fmt:
|
||||||
|
case Tree
|
||||||
|
case List
|
||||||
|
case Stats
|
||||||
|
case Json
|
||||||
|
case Graph
|
||||||
|
case HtmlGraph
|
||||||
|
case Html
|
||||||
|
case Xml
|
||||||
|
|
||||||
|
// Parser for the supported formats
|
||||||
|
lazy val FmtParser: Parser[Fmt] =
|
||||||
|
(("tree" ^^^ Fmt.Tree)
|
||||||
|
| ("list" ^^^ Fmt.List)
|
||||||
|
| ("stats" ^^^ Fmt.Stats)
|
||||||
|
| ("json" ^^^ Fmt.Json)
|
||||||
|
| ("dot" ^^^ Fmt.Graph)
|
||||||
|
| ("graph" ^^^ Fmt.Graph)
|
||||||
|
| ("html-graph" ^^^ Fmt.HtmlGraph)
|
||||||
|
| ("html" ^^^ Fmt.Html)
|
||||||
|
| ("xml" ^^^ Fmt.Xml))
|
||||||
|
|
||||||
|
lazy val ArgParser: Parser[Arg] =
|
||||||
|
Space ~> (("help" ^^^ Arg.Help)
|
||||||
|
| ("--help" ^^^ Arg.Help)
|
||||||
|
| FmtParser.map(fmt => Arg.Format(fmt)))
|
||||||
|
|
||||||
|
lazy val ArgOptionParser: Parser[Arg] =
|
||||||
|
Space ~> (("--quiet" ^^^ Arg.Quiet)
|
||||||
|
| ("--browse" ^^^ Arg.Browse)
|
||||||
|
| ("--out" ~> Space ~> StringBasic)
|
||||||
|
.map(Arg.Out(_))
|
||||||
|
.examples("--out /tmp/deps.txt"))
|
||||||
|
|
||||||
|
// You can have zero-or-one format and options afterwards
|
||||||
|
lazy val ArgsParser: Parser[Seq[Arg]] =
|
||||||
|
(ArgParser.? ~ ArgOptionParser.*).map:
|
||||||
|
case (a, opts) => a.toList ::: opts.toList
|
||||||
|
|
||||||
|
def usageText: String =
|
||||||
|
s"""dependencyTree task displays the dependency graph.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
dependencyTree [subcommand] [options]
|
||||||
|
|
||||||
|
SUBCOMMAND
|
||||||
|
tree Prints ascii tree (default)
|
||||||
|
list Prints list of all dependencies
|
||||||
|
graph Prints GraphViz DOT file
|
||||||
|
dot Same as graph
|
||||||
|
html Creates HTML page
|
||||||
|
html-graph Creates HTML page with GraphViz DOT file
|
||||||
|
json Prints JSON
|
||||||
|
xml Prints GraphML
|
||||||
|
stats Prints statistics for all dependencies
|
||||||
|
help Prints this help
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
--quiet Returns the output as task value, replacing asString
|
||||||
|
--out <file> Writes the output to the specified file;
|
||||||
|
The file extension will influence the default subcommand
|
||||||
|
--browse Opens the browser when combined with graph or html subcommand
|
||||||
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core settings needed for any graphing tasks.
|
* Core settings needed for any graphing tasks.
|
||||||
*/
|
*/
|
||||||
|
|
@ -59,10 +133,10 @@ object DependencyTreeSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MiniDependencyTreePlugin includes these settings for Compile and Test scopes
|
* DependencyTreePlugin includes these settings for Compile and Test scopes
|
||||||
* to provide dependencyTree task.
|
* to provide dependencyTree task.
|
||||||
*/
|
*/
|
||||||
lazy val baseBasicReportingSettings: Seq[Def.Setting[?]] =
|
lazy val baseSettings: Seq[Def.Setting[?]] =
|
||||||
Seq(
|
Seq(
|
||||||
dependencyTreeCrossProjectId := CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(
|
dependencyTreeCrossProjectId := CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(
|
||||||
projectID.value
|
projectID.value
|
||||||
|
|
@ -82,57 +156,81 @@ object DependencyTreeSettings {
|
||||||
.storeAs(dependencyTreeModuleGraphStore)
|
.storeAs(dependencyTreeModuleGraphStore)
|
||||||
.triggeredBy(dependencyTreeModuleGraph0)
|
.triggeredBy(dependencyTreeModuleGraph0)
|
||||||
.value,
|
.value,
|
||||||
) ++ {
|
dependencyTree := (Def.inputTaskDyn {
|
||||||
renderingTaskSettings(dependencyTree) :+ {
|
val s = streams.value
|
||||||
dependencyTree / asString := {
|
val args = ArgsParser.parsed.toList
|
||||||
rendering.AsciiTree.asciiTree(dependencyTreeModuleGraph0.value, asciiGraphWidth.value)
|
val isHelp = args.contains(Arg.Help)
|
||||||
}
|
val isQuiet = args.contains(Arg.Quiet)
|
||||||
}
|
val isBrowse = args.contains(Arg.Browse)
|
||||||
}
|
if isHelp then Def.task { s.log.info(usageText); "" }
|
||||||
|
else
|
||||||
/**
|
val formatOpt = (args
|
||||||
* This is the maximum strength settings for DependencyTreePlugin.
|
.collect { case Arg.Format(fmt) => fmt })
|
||||||
*/
|
.reverse
|
||||||
lazy val baseFullReportingSettings: Seq[Def.Setting[?]] =
|
.headOption
|
||||||
Seq(
|
val outFileNameOpt = (args
|
||||||
// browse
|
.collect { case Arg.Out(out) => out })
|
||||||
dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" },
|
.reverse
|
||||||
dependencyBrowseGraphHTML := browseGraphHTMLTask.value,
|
.headOption
|
||||||
dependencyBrowseGraph := openBrowser(dependencyBrowseGraphHTML).value,
|
val outFileOpt = outFileNameOpt.map(new File(_))
|
||||||
dependencyBrowseTreeTarget := { target.value / "browse-dependency-tree" },
|
val format = (formatOpt, outFileNameOpt) match
|
||||||
dependencyBrowseTreeHTML := browseTreeHTMLTask.value,
|
case (None, Some(out)) if out.endsWith(".dot") => Fmt.Graph
|
||||||
dependencyBrowseTree := openBrowser(dependencyBrowseTreeHTML).value,
|
case (None, Some(out)) if out.endsWith(".html") => Fmt.Html
|
||||||
// dot support
|
case (None, Some(out)) if out.endsWith(".xml") => Fmt.Xml
|
||||||
dependencyDotFile := {
|
case (None, Some(out)) if out.endsWith(".json") => Fmt.Json
|
||||||
val config = configuration.value
|
case (Some(Fmt.Graph), Some(out)) if out.endsWith(".html") => Fmt.HtmlGraph
|
||||||
target.value / s"dependencies-${config.toString}.dot"
|
case (Some(Fmt.Graph), _) if isBrowse => Fmt.HtmlGraph
|
||||||
},
|
case (Some(fmt), _) => fmt
|
||||||
dependencyDot / asString := Def.uncached(
|
case _ => Fmt.Tree
|
||||||
rendering.DOT.dotGraph(
|
val config = configuration.value.name
|
||||||
dependencyTreeModuleGraph0.value,
|
val targetDir = target.value / config / format.toString.toLowerCase(Locale.ENGLISH)
|
||||||
dependencyDotHeader.value,
|
format match
|
||||||
dependencyDotNodeLabel.value,
|
case Fmt.Tree | Fmt.List | Fmt.Stats =>
|
||||||
rendering.DOT.HTMLLabelRendering.AngleBrackets,
|
Def.task {
|
||||||
dependencyDotNodeColors.value
|
val graph = dependencyTreeModuleGraph0.value
|
||||||
)
|
val output = format match
|
||||||
),
|
case Fmt.List => rendering.FlatList.render(_.id.idString)(graph)
|
||||||
dependencyDot := writeToFile(dependencyDot / asString, dependencyDotFile).value,
|
case Fmt.Stats => rendering.Statistics.renderModuleStatsList(graph)
|
||||||
dependencyDotHeader :=
|
case _ => rendering.AsciiTree.asciiTree(graph, asciiGraphWidth.value)
|
||||||
"""|digraph "dependency-graph" {
|
handleOutput(output, outFileOpt, isQuiet, s.log)
|
||||||
| graph[rankdir="LR"; splines=polyline]
|
}
|
||||||
| edge [
|
case Fmt.Json =>
|
||||||
| arrowtail="none"
|
Def.task {
|
||||||
| ]""".stripMargin,
|
val graph = dependencyTreeModuleGraph0.value
|
||||||
dependencyDotNodeColors := true,
|
val output = TreeView.createJson(graph)
|
||||||
dependencyDotNodeLabel := { (organization: String, name: String, version: String) =>
|
handleOutput(output, outFileOpt, isQuiet, s.log)
|
||||||
s"""${organization}<BR/><B>${name}</B><BR/>${version}"""
|
}
|
||||||
},
|
case Fmt.Xml =>
|
||||||
// GraphML support
|
Def.task {
|
||||||
dependencyGraphMLFile := {
|
val graph = dependencyTreeModuleGraph0.value
|
||||||
val config = configuration.value
|
val output = rendering.GraphML.graphMLAsString(graph)
|
||||||
target.value / s"dependencies-${config.toString}.graphml"
|
handleOutput(output, outFileOpt, isQuiet, s.log)
|
||||||
},
|
}
|
||||||
dependencyGraphML := dependencyGraphMLTask.value,
|
case Fmt.Html =>
|
||||||
|
Def.task {
|
||||||
|
val graph = dependencyTreeModuleGraph0.value
|
||||||
|
val renderedTree = TreeView.createJson(graph)
|
||||||
|
val outputFile = TreeView.createFile(renderedTree, targetDir)
|
||||||
|
if isBrowse then openBrowser(outputFile.toURI)
|
||||||
|
outputFile.getAbsolutePath
|
||||||
|
}
|
||||||
|
case Fmt.Graph | Fmt.HtmlGraph =>
|
||||||
|
Def.task {
|
||||||
|
val graph = dependencyTreeModuleGraph0.value
|
||||||
|
val output = rendering.DOT.dotGraph(
|
||||||
|
graph,
|
||||||
|
dependencyDotHeader.value,
|
||||||
|
dependencyDotNodeLabel.value,
|
||||||
|
rendering.DOT.HTMLLabelRendering.AngleBrackets,
|
||||||
|
dependencyDotNodeColors.value
|
||||||
|
)
|
||||||
|
if format == Fmt.Graph then handleOutput(output, outFileOpt, isQuiet, s.log)
|
||||||
|
else
|
||||||
|
val outputFile = DagreHTML.createFile(output, targetDir)
|
||||||
|
if isBrowse then openBrowser(outputFile.toURI)
|
||||||
|
outputFile.getAbsolutePath
|
||||||
|
}
|
||||||
|
}).evaluated,
|
||||||
whatDependsOn := {
|
whatDependsOn := {
|
||||||
val ArtifactPattern(org, name, versionFilter) = artifactPatternParser.parsed
|
val ArtifactPattern(org, name, versionFilter) = artifactPatternParser.parsed
|
||||||
val graph = dependencyTreeModuleGraph0.value
|
val graph = dependencyTreeModuleGraph0.value
|
||||||
|
|
@ -155,112 +253,32 @@ object DependencyTreeSettings {
|
||||||
}
|
}
|
||||||
output
|
output
|
||||||
},
|
},
|
||||||
) ++
|
|
||||||
renderingAlternatives.flatMap { (key, renderer) => renderingTaskSettings(key, renderer) }
|
|
||||||
|
|
||||||
def renderingAlternatives: Seq[(TaskKey[Unit], ModuleGraph => String)] =
|
|
||||||
Seq(
|
|
||||||
dependencyList -> rendering.FlatList.render(_.id.idString),
|
|
||||||
dependencyStats -> rendering.Statistics.renderModuleStatsList,
|
|
||||||
dependencyLicenseInfo -> rendering.LicenseInfo.render
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def renderingTaskSettings(key: TaskKey[Unit], renderer: ModuleGraph => String): Seq[Setting[?]] =
|
private def handleOutput(
|
||||||
renderingTaskSettings(key) :+ {
|
content: String,
|
||||||
key / asString := renderer(dependencyTreeModuleGraph0.value)
|
outputFileOpt: Option[File],
|
||||||
}
|
isQuiet: Boolean,
|
||||||
|
log: Logger,
|
||||||
|
): String =
|
||||||
|
outputFileOpt match
|
||||||
|
case Some(output) =>
|
||||||
|
IO.write(output, content, IO.utf8)
|
||||||
|
if !isQuiet then log.info(s"wrote dependencies to $output")
|
||||||
|
output.toString
|
||||||
|
case None =>
|
||||||
|
if isQuiet then content
|
||||||
|
else
|
||||||
|
Console.out.println(content); ""
|
||||||
|
|
||||||
def renderingTaskSettings(key: TaskKey[Unit]): Seq[Setting[?]] =
|
def openBrowser(uri: URI): Unit =
|
||||||
Seq(
|
val desktop = java.awt.Desktop.getDesktop
|
||||||
key := {
|
desktop.synchronized {
|
||||||
val s = streams.value
|
desktop.browse(uri)
|
||||||
val str = (key / asString).value
|
|
||||||
synchronized {
|
|
||||||
s.log.info(str)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(key / toFile) := {
|
|
||||||
val (targetFile, force) = targetFileAndForceParser.parsed
|
|
||||||
writeToFile(key.key.label, (key / asString).value, targetFile, force, streams.value)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def dependencyGraphMLTask =
|
|
||||||
Def.task {
|
|
||||||
val resultFile = dependencyGraphMLFile.value
|
|
||||||
val graph = dependencyTreeModuleGraph0.value
|
|
||||||
rendering.GraphML.saveAsGraphML(graph, resultFile.getAbsolutePath)
|
|
||||||
streams.value.log.info(s"Wrote dependency graph to '${resultFile}'")
|
|
||||||
resultFile
|
|
||||||
}
|
|
||||||
|
|
||||||
def browseGraphHTMLTask =
|
|
||||||
Def.task {
|
|
||||||
val graph = dependencyTreeModuleGraph0.value
|
|
||||||
val dotGraph = rendering.DOT.dotGraph(
|
|
||||||
graph,
|
|
||||||
dependencyDotHeader.value,
|
|
||||||
dependencyDotNodeLabel.value,
|
|
||||||
rendering.DOT.HTMLLabelRendering.AngleBrackets,
|
|
||||||
dependencyDotNodeColors.value
|
|
||||||
)
|
|
||||||
val link = DagreHTML.createLink(dotGraph, dependencyBrowseGraphTarget.value)
|
|
||||||
streams.value.log.info(s"HTML graph written to $link")
|
|
||||||
link
|
|
||||||
}
|
|
||||||
|
|
||||||
def browseTreeHTMLTask =
|
|
||||||
Def.task {
|
|
||||||
val graph = dependencyTreeModuleGraph0.value
|
|
||||||
val renderedTree = TreeView.createJson(graph)
|
|
||||||
val link = TreeView.createLink(renderedTree, dependencyBrowseTreeTarget.value)
|
|
||||||
streams.value.log.info(s"HTML tree written to $link")
|
|
||||||
link
|
|
||||||
}
|
|
||||||
|
|
||||||
def writeToFile(dataTask: TaskKey[String], fileTask: SettingKey[File]) =
|
|
||||||
Def.task {
|
|
||||||
val outFile = fileTask.value
|
|
||||||
IO.write(outFile, dataTask.value, IO.utf8)
|
|
||||||
|
|
||||||
streams.value.log.info(s"Wrote dependency graph to '${outFile}'")
|
|
||||||
outFile
|
|
||||||
}
|
|
||||||
|
|
||||||
def writeToFile(
|
|
||||||
what: String,
|
|
||||||
data: String,
|
|
||||||
targetFile: File,
|
|
||||||
force: Boolean,
|
|
||||||
streams: TaskStreams
|
|
||||||
): File =
|
|
||||||
if (targetFile.exists && !force)
|
|
||||||
throw new RuntimeException(
|
|
||||||
s"Target file for $what already exists at ${targetFile.getAbsolutePath}. Use '-f' to override"
|
|
||||||
)
|
|
||||||
else {
|
|
||||||
IO.write(targetFile, data, IO.utf8)
|
|
||||||
|
|
||||||
streams.log.info(s"Wrote $what to '$targetFile'")
|
|
||||||
targetFile
|
|
||||||
}
|
|
||||||
|
|
||||||
def absoluteReportPath = (file: File) => file.getAbsolutePath
|
|
||||||
|
|
||||||
def openBrowser(uriKey: TaskKey[URI]) =
|
|
||||||
Def.task {
|
|
||||||
val uri = uriKey.value
|
|
||||||
streams.value.log.info(s"Opening ${uri} in browser...")
|
|
||||||
val desktop = java.awt.Desktop.getDesktop
|
|
||||||
desktop.synchronized {
|
|
||||||
desktop.browse(uri)
|
|
||||||
}
|
|
||||||
uri
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ArtifactPattern(organization: String, name: String, version: Option[String])
|
case class ArtifactPattern(organization: String, name: String, version: Option[String])
|
||||||
|
|
||||||
import sbt.internal.util.complete.DefaultParsers.*
|
|
||||||
val artifactPatternParser: Def.Initialize[State => Parser[ArtifactPattern]] =
|
val artifactPatternParser: Def.Initialize[State => Parser[ArtifactPattern]] =
|
||||||
Keys.resolvedScoped { ctx => (state: State) =>
|
Keys.resolvedScoped { ctx => (state: State) =>
|
||||||
val graph =
|
val graph =
|
||||||
|
|
@ -320,4 +338,4 @@ object DependencyTreeSettings {
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
end DependencyTreeSettings
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* sbt
|
|
||||||
* Copyright 2023, Scala center
|
|
||||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
|
||||||
* Copyright 2008 - 2010, Mark Harrah
|
|
||||||
* Licensed under Apache License 2.0 (see LICENSE)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package sbt
|
|
||||||
package plugins
|
|
||||||
|
|
||||||
import sbt.PluginTrigger.AllRequirements
|
|
||||||
import sbt.ProjectExtra.*
|
|
||||||
import sbt.librarymanagement.Configurations.{ Compile, Test }
|
|
||||||
|
|
||||||
object MiniDependencyTreePlugin extends AutoPlugin {
|
|
||||||
object autoImport extends MiniDependencyTreeKeys
|
|
||||||
|
|
||||||
import autoImport.*
|
|
||||||
override def trigger: PluginTrigger = AllRequirements
|
|
||||||
override def globalSettings: Seq[Def.Setting[?]] = Seq(
|
|
||||||
dependencyTreeIncludeScalaLibrary := false
|
|
||||||
)
|
|
||||||
override lazy val projectSettings: Seq[Def.Setting[?]] =
|
|
||||||
DependencyTreeSettings.coreSettings ++
|
|
||||||
inConfig(Compile)(DependencyTreeSettings.baseBasicReportingSettings) ++
|
|
||||||
inConfig(Test)(DependencyTreeSettings.baseBasicReportingSettings)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2023, Scala center
|
||||||
|
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sbt
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import sbt.internal.util.complete.Parser
|
||||||
|
import DependencyTreeSettings.{ Arg, ArgsParser, Fmt, FmtParser }
|
||||||
|
|
||||||
|
object DependencyTreeTest extends verify.BasicTestSuite:
|
||||||
|
test("Parse args") {
|
||||||
|
assert(parseArgs(List("help")) == List(Arg.Help))
|
||||||
|
assert(parseArgs(List("--help")) == List(Arg.Help))
|
||||||
|
assert(parseArgs(List("--quiet")) == List(Arg.Quiet))
|
||||||
|
assert(parseArgs(List("tree")) == List(Arg.Format(Fmt.Tree)))
|
||||||
|
assert(parseArgs(List("--out", "/tmp/deps.txt")) == List(Arg.Out("/tmp/deps.txt")))
|
||||||
|
assert(parseArgs(List("--browse")) == List(Arg.Browse))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Parse format") {
|
||||||
|
assert(parseFormat("tree") == Fmt.Tree)
|
||||||
|
assert(parseFormat("list") == Fmt.List)
|
||||||
|
assert(parseFormat("stats") == Fmt.Stats)
|
||||||
|
assert(parseFormat("json") == Fmt.Json)
|
||||||
|
assert(parseFormat("html") == Fmt.Html)
|
||||||
|
assert(parseFormat("graph") == Fmt.Graph)
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseArgs(args: List[String]): Seq[Arg] =
|
||||||
|
Parser.parse(" " + args.mkString(" "), ArgsParser) match
|
||||||
|
case Right(args) => args
|
||||||
|
case Left(err) => sys.error(err)
|
||||||
|
|
||||||
|
def parseFormat(fmt: String): Fmt =
|
||||||
|
Parser.parse(fmt, FmtParser) match
|
||||||
|
case Right(x) => x
|
||||||
|
case Left(err) => sys.error(err)
|
||||||
|
end DependencyTreeTest
|
||||||
|
|
@ -5,11 +5,13 @@ name := "asciiGraphWidthSpecs"
|
||||||
|
|
||||||
lazy val whenIsDefault = (project in file("when-is-default"))
|
lazy val whenIsDefault = (project in file("when-is-default"))
|
||||||
.settings(
|
.settings(
|
||||||
|
name := "whenisdefault",
|
||||||
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.1.0",
|
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.1.0",
|
||||||
check := checkTask.value
|
check := checkTask.value
|
||||||
)
|
)
|
||||||
lazy val whenIs20 = (project in file("when-is-20"))
|
lazy val whenIs20 = (project in file("when-is-20"))
|
||||||
.settings(
|
.settings(
|
||||||
|
name := "whenis20",
|
||||||
asciiGraphWidth := 20,
|
asciiGraphWidth := 20,
|
||||||
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.1.0",
|
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.1.0",
|
||||||
check := checkTask.value
|
check := checkTask.value
|
||||||
|
|
@ -19,6 +21,11 @@ lazy val check = taskKey[Unit]("check")
|
||||||
lazy val checkTask = Def.task {
|
lazy val checkTask = Def.task {
|
||||||
val context = thisProject.value
|
val context = thisProject.value
|
||||||
val expected = IO.read(file(s"${context.base}/expected.txt"))
|
val expected = IO.read(file(s"${context.base}/expected.txt"))
|
||||||
val actual = (Compile / dependencyTree / asString).value
|
val actual = (Compile / dependencyTree).toTask(" --quiet").value
|
||||||
require(actual == expected, s"${context.id} is failed.")
|
require(actual == expected, s"""${context.id} failed
|
||||||
|
|
||||||
|
actual: $actual
|
||||||
|
|
||||||
|
expected: $expected
|
||||||
|
""")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
addDependencyTreePlugin
|
// addDependencyTreePlugin
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ updateOptions := updateOptions.value.withCachedResolution(true)
|
||||||
|
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val report = (Test / updateFull).value
|
val report = (Test / updateFull).value
|
||||||
val graph = (Test / dependencyTree / asString).value
|
val graph = (Test / dependencyTree).toTask(" --quiet").value
|
||||||
|
|
||||||
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
|
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
|
||||||
val expectedGraph =
|
val expectedGraph =
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
ThisBuild / scalaVersion := "2.12.20"
|
ThisBuild / scalaVersion := "2.12.20"
|
||||||
|
|
||||||
|
name := "foo"
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.slf4j" % "slf4j-api" % "1.7.2",
|
"org.slf4j" % "slf4j-api" % "1.7.2",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.0.7"
|
"ch.qos.logback" % "logback-classic" % "1.0.7"
|
||||||
|
|
@ -8,8 +9,11 @@ csrMavenDependencyOverride := false
|
||||||
|
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val report = updateFull.value
|
val report = updateFull.value
|
||||||
val graph = (Test / dependencyTree / asString).value
|
val graph = (Test / dependencyTree).toTask(" --quiet").value
|
||||||
def sanitize(str: String): String = str.split('\n').drop(1).map(_.trim).mkString("\n")
|
def sanitize(str: String): String = str.linesIterator.toList
|
||||||
|
.drop(1)
|
||||||
|
.map(_.trim)
|
||||||
|
.mkString("\n")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Started to return:
|
Started to return:
|
||||||
|
|
@ -31,8 +35,8 @@ default:sbt_8ae1da13_2.12:0.1.0-SNAPSHOT [S]
|
||||||
| |
|
| |
|
||||||
| +-org.slf4j:slf4j-api:1.7.2
|
| +-org.slf4j:slf4j-api:1.7.2
|
||||||
| """.stripMargin
|
| """.stripMargin
|
||||||
IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n"))
|
// IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n"))
|
||||||
IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n"))
|
// IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n"))
|
||||||
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
|
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ version := "0.1"
|
||||||
name := "blubber"
|
name := "blubber"
|
||||||
libraryDependencies += "org.typelevel" %% "cats-effect" % "2.2.0"
|
libraryDependencies += "org.typelevel" %% "cats-effect" % "2.2.0"
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val candidates = "tree list stats licenses".split(' ').map(_.trim)
|
val candidates = "tree list stats".split(' ').map(_.trim)
|
||||||
candidates.foreach { c =>
|
candidates.foreach { c =>
|
||||||
val expected = new File(s"expected/$c.txt")
|
val expected = new File(s"expected/$c.txt")
|
||||||
val actual = new File(s"target/$c.txt")
|
val actual = new File(s"target/$c.txt")
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
> dependencyTree --out target/tree.txt
|
||||||
|
> dependencyTree list --out target/list.txt
|
||||||
|
> dependencyTree stats --out target/stats.txt
|
||||||
|
# > dependencyLicenseInfo/toFile target/licenses.txt
|
||||||
|
> check
|
||||||
|
|
@ -5,7 +5,7 @@ libraryDependencies +=
|
||||||
|
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val report = updateFull.value
|
val report = updateFull.value
|
||||||
val graph = (Test / dependencyTree / asString).value
|
val graph = (Test / dependencyTree).toTask(" --quiet").value
|
||||||
|
|
||||||
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
|
def sanitize(str: String): String = str.split('\n').drop(1).mkString("\n")
|
||||||
val expectedGraph =
|
val expectedGraph =
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ lazy val test_project = project
|
||||||
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
||||||
.settings(
|
.settings(
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val dotFile = (dependencyDot in Compile).value
|
val graph = (Compile / dependencyTree).toTask(" dot --quiet").value
|
||||||
val expectedGraph =
|
val expectedGraph =
|
||||||
"""digraph "dependency-graph" {
|
"""digraph "dependency-graph" {
|
||||||
| graph[rankdir="LR"; splines=polyline]
|
| graph[rankdir="LR"; splines=polyline]
|
||||||
|
|
@ -23,7 +23,7 @@ lazy val test_project = project
|
||||||
| ]
|
| ]
|
||||||
| "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justadependencyproject<BR/><B>justadependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#B6E316"]
|
| "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justadependencyproject<BR/><B>justadependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#B6E316"]
|
||||||
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justatransitivedependencyproject<BR/><B>justatransitivedependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#0E92BE"]
|
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justatransitivedependencyproject<BR/><B>justatransitivedependencyproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#0E92BE"]
|
||||||
| "justatransitivedependencyendpointproject:justatransitivedependencyendpointproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justatransitivedependencyendpointproject<BR/><B>justatransitivedependencyendpointproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#9EAD1B"]
|
| "justatransitivedependencyendpointproject:justatransitivedependencyendpointproject_2.9.2:0.1-SNAPSHOT"[shape=box label=<justatransitivedependencyendpointproject<BR/><B>justatransitivedependencyendpointproject_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#A1168F"]
|
||||||
| "test_project:test_project_2.9.2:0.1-SNAPSHOT"[shape=box label=<test_project<BR/><B>test_project_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#C37661"]
|
| "test_project:test_project_2.9.2:0.1-SNAPSHOT"[shape=box label=<test_project<BR/><B>test_project_2.9.2</B><BR/>0.1-SNAPSHOT> style="" penwidth="5" color="#C37661"]
|
||||||
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT" -> "justatransitivedependencyendpointproject:justatransitivedependencyendpointproject_2.9.2:0.1-SNAPSHOT"
|
| "justatransitivedependencyproject:justatransitivedependencyproject_2.9.2:0.1-SNAPSHOT" -> "justatransitivedependencyendpointproject:justatransitivedependencyendpointproject_2.9.2:0.1-SNAPSHOT"
|
||||||
| "test_project:test_project_2.9.2:0.1-SNAPSHOT" -> "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"
|
| "test_project:test_project_2.9.2:0.1-SNAPSHOT" -> "justadependencyproject:justadependencyproject_2.9.2:0.1-SNAPSHOT"
|
||||||
|
|
@ -31,8 +31,7 @@ lazy val test_project = project
|
||||||
|}
|
|}
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
|
|
||||||
val graph: String = scala.io.Source.fromFile(dotFile.getAbsolutePath).mkString
|
val errors = compareByLine(graph.trim, expectedGraph.trim)
|
||||||
val errors = compareByLine(graph, expectedGraph)
|
|
||||||
require(errors.isEmpty, errors.mkString("\n"))
|
require(errors.isEmpty, errors.mkString("\n"))
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +39,7 @@ lazy val test_project = project
|
||||||
|
|
||||||
def compareByLine(got: String, expected: String): Seq[String] = {
|
def compareByLine(got: String, expected: String): Seq[String] = {
|
||||||
val errors = ListBuffer[String]()
|
val errors = ListBuffer[String]()
|
||||||
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach {
|
got.linesIterator.toList.sorted.zip(expected.linesIterator.toList.sorted).zipWithIndex.foreach {
|
||||||
case ((got_line: String, expected_line: String), i: Int) =>
|
case ((got_line: String, expected_line: String), i: Int) =>
|
||||||
if (got_line != expected_line) {
|
if (got_line != expected_line) {
|
||||||
errors.append("""not matching lines at line %s
|
errors.append("""not matching lines at line %s
|
||||||
|
|
@ -49,5 +48,5 @@ def compareByLine(got: String, expected: String): Seq[String] = {
|
||||||
|""".stripMargin.format(i, expected_line, got_line))
|
|""".stripMargin.format(i, expected_line, got_line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors
|
errors.toList
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
addDependencyTreePlugin
|
|
||||||
|
|
@ -14,7 +14,7 @@ lazy val test_project = project
|
||||||
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
.dependsOn(justADependencyProject, justATransitiveDependencyProject)
|
||||||
.settings(
|
.settings(
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
val htmlFile = (dependencyBrowseGraphHTML in Compile).value
|
val htmlFile = (Compile / dependencyTree).toTask(" html-graph").value
|
||||||
val expectedHtml =
|
val expectedHtml =
|
||||||
"""<!doctype html>
|
"""<!doctype html>
|
||||||
|
|
|
|
||||||
|
|
@ -60,11 +60,10 @@ lazy val test_project = project
|
||||||
|<script>
|
|<script>
|
||||||
| d3.select("#graph").graphviz().renderDot(decodeURIComponent(data));
|
| d3.select("#graph").graphviz().renderDot(decodeURIComponent(data));
|
||||||
|</script>
|
|</script>
|
||||||
|
|
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
|
|
||||||
val html: String = scala.io.Source.fromFile(htmlFile).mkString
|
val html: String = scala.io.Source.fromFile(htmlFile).mkString
|
||||||
val errors = compareByLine(html, expectedHtml)
|
val errors = compareByLine(html.trim, expectedHtml.trim)
|
||||||
require(errors.isEmpty, errors.mkString("\n"))
|
require(errors.isEmpty, errors.mkString("\n"))
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +71,7 @@ lazy val test_project = project
|
||||||
|
|
||||||
def compareByLine(got: String, expected: String): Seq[String] = {
|
def compareByLine(got: String, expected: String): Seq[String] = {
|
||||||
val errors = ListBuffer[String]()
|
val errors = ListBuffer[String]()
|
||||||
got.split("\n").zip(expected.split("\n").toSeq).zipWithIndex.foreach {
|
got.linesIterator.toList.zip(expected.linesIterator.toList).zipWithIndex.foreach {
|
||||||
case ((got_line: String, expected_line: String), i: Int) =>
|
case ((got_line: String, expected_line: String), i: Int) =>
|
||||||
if (got_line != expected_line) {
|
if (got_line != expected_line) {
|
||||||
errors.append("""not matching lines at line %s
|
errors.append("""not matching lines at line %s
|
||||||
|
|
@ -81,5 +80,5 @@ def compareByLine(got: String, expected: String): Seq[String] = {
|
||||||
|""".stripMargin.format(i, expected_line, got_line))
|
|""".stripMargin.format(i, expected_line, got_line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors
|
errors.toList
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
addDependencyTreePlugin
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
addDependencyTreePlugin
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
> dependencyTree/toFile target/tree.txt
|
|
||||||
> dependencyList/toFile target/list.txt
|
|
||||||
> dependencyStats/toFile target/stats.txt
|
|
||||||
> dependencyLicenseInfo/toFile target/licenses.txt
|
|
||||||
> check
|
|
||||||
|
|
@ -11,26 +11,26 @@ libraryDependencies ++= Seq(
|
||||||
val check = TaskKey[Unit]("check")
|
val check = TaskKey[Unit]("check")
|
||||||
|
|
||||||
check := {
|
check := {
|
||||||
def sanitize(str: String): String = str.split('\n').map(_.trim).mkString("\n")
|
def sanitize(str: String): String = str.linesIterator.toList.map(_.trim).mkString("\n")
|
||||||
def checkOutput(output: String, expected: String): Unit =
|
def checkOutput(output: String, expected: String): Unit =
|
||||||
require(sanitize(expected) == sanitize(output), s"Tree should have been [\n${sanitize(expected)}\n] but was [\n${sanitize(output)}\n]")
|
require(sanitize(expected) == sanitize(output),
|
||||||
|
s"Tree should have been [\n${expected}\n] but was [\n${output}\n]")
|
||||||
|
|
||||||
val withVersion =
|
val withVersion =
|
||||||
(Compile / whatDependsOn)
|
(Compile / whatDependsOn)
|
||||||
.toTask(" org.typelevel cats-core_2.13 2.6.0")
|
.toTask(" org.typelevel cats-core_2.13 2.6.0")
|
||||||
.value
|
.value
|
||||||
val expectedGraphWithVersion = {
|
val expectedGraphWithVersion =
|
||||||
"""org.typelevel:cats-core_2.13:2.6.0 [S]
|
"""org.typelevel:cats-core_2.13:2.6.0 [S]
|
||||||
|+-org.typelevel:cats-effect-kernel_2.13:3.1.0 [S]
|
| +-org.typelevel:cats-effect-kernel_2.13:3.1.0 [S]
|
||||||
|+-org.typelevel:cats-effect-std_2.13:3.1.0 [S]
|
| +-org.typelevel:cats-effect-std_2.13:3.1.0 [S]
|
||||||
|| +-org.typelevel:cats-effect_2.13:3.1.0 [S]
|
| | +-org.typelevel:cats-effect_2.13:3.1.0 [S]
|
||||||
|| +-whatdependson:whatdependson_2.13:0.1.0-SNAPSHOT [S]
|
| | +-whatdependson:whatdependson_2.13:0.1.0-SNAPSHOT [S]
|
||||||
||
|
| |
|
||||||
|+-org.typelevel:cats-effect_2.13:3.1.0 [S]
|
| +-org.typelevel:cats-effect_2.13:3.1.0 [S]
|
||||||
|+-whatdependson:whatdependson_2.13:0.1.0-SNAPSHOT [S]""".stripMargin
|
| +-whatdependson:whatdependson_2.13:0.1.0-SNAPSHOT [S]""".stripMargin
|
||||||
}
|
|
||||||
|
|
||||||
checkOutput(withVersion.trim, expectedGraphWithVersion.trim)
|
checkOutput(withVersion.trim, expectedGraphWithVersion)
|
||||||
|
|
||||||
val withoutVersion =
|
val withoutVersion =
|
||||||
(Compile / whatDependsOn)
|
(Compile / whatDependsOn)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue