![JAR search and dependency download from the Maven repository](/logo.png)
sbt.plugins.DependencyTreeSettings.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of main_2.12 Show documentation
Show all versions of main_2.12 Show documentation
sbt is an interactive build tool
/*
* 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 java.io.File
import sbt.Def._
import sbt.Keys._
import sbt.SlashSyntax0._
import sbt.Project._
import sbt.internal.graph._
import sbt.internal.graph.backend.SbtUpdateReport
import sbt.internal.graph.rendering.{ DagreHTML, TreeView }
import sbt.internal.librarymanagement._
import sbt.internal.util.complete.{ Parser, Parsers }
import sbt.io.IO
import sbt.io.syntax._
import sbt.librarymanagement._
object DependencyTreeSettings {
import sjsonnew.BasicJsonProtocol._
import MiniDependencyTreeKeys._
import DependencyTreeKeys._
/**
* Core settings needed for any graphing tasks.
*/
def coreSettings =
Seq(
// disable the cached resolution engine (exposing a scoped `ivyModule` used directly by `updateTask`), as it
// generates artificial module descriptors which are internal to sbt, making it hard to reconstruct the
// dependency tree
dependencyTreeIgnoreMissingUpdate / updateOptions := updateOptions.value
.withCachedResolution(false),
dependencyTreeIgnoreMissingUpdate / ivyConfiguration := {
// inTask will make sure the new definition will pick up `updateOptions in dependencyTreeIgnoreMissingUpdate`
inTask(dependencyTreeIgnoreMissingUpdate, Classpaths.mkIvyConfiguration).value
},
dependencyTreeIgnoreMissingUpdate / ivyModule := {
// concatenating & inlining ivySbt & ivyModule default task implementations, as `SbtAccess.inTask` does
// NOT correctly force the scope when applied to `TaskKey.toTask` instances (as opposed to raw
// implementations like `Classpaths.mkIvyConfiguration` or `Classpaths.updateTask`)
val is = new IvySbt((dependencyTreeIgnoreMissingUpdate / ivyConfiguration).value)
new is.Module(moduleSettings.value)
},
// don't fail on missing dependencies
dependencyTreeIgnoreMissingUpdate / updateConfiguration := updateConfiguration.value
.withMissingOk(true),
dependencyTreeIgnoreMissingUpdate := {
// inTask will make sure the new definition will pick up `ivyModule/updateConfiguration in ignoreMissingUpdate`
inTask(dependencyTreeIgnoreMissingUpdate, Classpaths.updateTask).value
},
)
/**
* MiniDependencyTreePlugin includes these settings for Compile and Test scopes
* to provide dependencyTree task.
*/
lazy val baseBasicReportingSettings: Seq[Def.Setting[_]] =
Seq(
dependencyTreeCrossProjectId := CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(
projectID.value
),
dependencyTreeModuleGraph0 := {
val sv = scalaVersion.value
val g = dependencyTreeIgnoreMissingUpdate.value
.configuration(configuration.value)
.map(
report =>
SbtUpdateReport.fromConfigurationReport(report, dependencyTreeCrossProjectId.value)
)
.getOrElse(ModuleGraph.empty)
if (dependencyTreeIncludeScalaLibrary.value) g
else GraphTransformations.ignoreScalaLibrary(sv, g)
},
dependencyTreeModuleGraphStore := (dependencyTreeModuleGraph0 storeAs dependencyTreeModuleGraphStore triggeredBy dependencyTreeModuleGraph0).value,
) ++ {
renderingTaskSettings(dependencyTree) :+ {
dependencyTree / asString := {
rendering.AsciiTree.asciiTree(dependencyTreeModuleGraph0.value, asciiGraphWidth.value)
}
}
}
/**
* This is the maximum strength settings for DependencyTreePlugin.
*/
lazy val baseFullReportingSettings: Seq[Def.Setting[_]] =
Seq(
// browse
dependencyBrowseGraphTarget := { target.value / "browse-dependency-graph" },
dependencyBrowseGraphHTML := browseGraphHTMLTask.value,
dependencyBrowseGraph := openBrowser(dependencyBrowseGraphHTML).value,
dependencyBrowseTreeTarget := { target.value / "browse-dependency-tree" },
dependencyBrowseTreeHTML := browseTreeHTMLTask.value,
dependencyBrowseTree := openBrowser(dependencyBrowseTreeHTML).value,
// dot support
dependencyDotFile := {
val config = configuration.value
target.value / "dependencies-%s.dot".format(config.toString)
},
dependencyDot / asString := rendering.DOT.dotGraph(
dependencyTreeModuleGraph0.value,
dependencyDotHeader.value,
dependencyDotNodeLabel.value,
rendering.DOT.AngleBrackets,
dependencyDotNodeColors.value
),
dependencyDot := writeToFile(dependencyDot / asString, dependencyDotFile).value,
dependencyDotHeader :=
"""|digraph "dependency-graph" {
| graph[rankdir="LR"; splines=polyline]
| edge [
| arrowtail="none"
| ]""".stripMargin,
dependencyDotNodeColors := true,
dependencyDotNodeLabel := { (organization: String, name: String, version: String) =>
"""%s
%s
%s""".format(organization, name, version)
},
// GraphML support
dependencyGraphMLFile := {
val config = configuration.value
target.value / "dependencies-%s.graphml".format(config.toString)
},
dependencyGraphML := dependencyGraphMLTask.value,
whatDependsOn := {
val ArtifactPattern(org, name, versionFilter) = artifactPatternParser.parsed
val graph = dependencyTreeModuleGraph0.value
val modules =
versionFilter match {
case Some(version) => GraphModuleId(org, name, version) :: Nil
case None =>
graph.nodes.withFilter(m => m.id.organization == org && m.id.name == name).map(_.id)
}
val graphWidth = asciiGraphWidth.value
val output =
modules
.map { module =>
rendering.AsciiTree
.asciiTree(GraphTransformations.reverseGraphStartingAt(graph, module), graphWidth)
}
.mkString("\n")
streams.value.log.info(output)
output
},
) ++
renderingAlternatives.flatMap { case (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[_]] =
renderingTaskSettings(key) :+ {
key / asString := renderer(dependencyTreeModuleGraph0.value)
}
def renderingTaskSettings(key: TaskKey[Unit]): Seq[Setting[_]] =
Seq(
key := {
val s = streams.value
val str = (key / asString).value
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("Wrote dependency graph to '%s'" format resultFile)
resultFile
}
def browseGraphHTMLTask =
Def.task {
val graph = dependencyTreeModuleGraph0.value
val dotGraph = rendering.DOT.dotGraph(
graph,
dependencyDotHeader.value,
dependencyDotNodeLabel.value,
rendering.DOT.AngleBrackets,
dependencyDotNodeColors.value
)
val link = DagreHTML.createLink(dotGraph, target.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, target.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("Wrote dependency graph to '%s'" format 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...")
java.awt.Desktop.getDesktop.browse(uri)
uri
}
case class ArtifactPattern(organization: String, name: String, version: Option[String])
import sbt.internal.util.complete.DefaultParsers._
val artifactPatternParser: Def.Initialize[State => Parser[ArtifactPattern]] =
Keys.resolvedScoped { ctx => (state: State) =>
val graph = Defaults.loadFromContext(dependencyTreeModuleGraphStore, ctx, state) getOrElse ModuleGraph(
Nil,
Nil
)
graph.nodes
.map(_.id)
.groupBy(m => (m.organization, m.name))
.map {
case ((org, name), modules) =>
val versionParsers: Seq[Parser[Option[String]]] =
modules.map { id =>
token(Space ~> id.version).?
}
(Space ~> token(org) ~ token(Space ~> name) ~ oneOf(versionParsers)).map {
case ((org, name), version) => ArtifactPattern(org, name, version)
}
}
.reduceOption(_ | _)
.getOrElse {
// If the dependencyTreeModuleGraphStore couldn't be loaded because no dependency tree command was run before, we should still provide a parser for the command.
((Space ~> token(StringBasic, "")) ~ (Space ~> token(
StringBasic,
""
)) ~ (Space ~> token(StringBasic, "")).?).map {
case ((org, mod), version) =>
ArtifactPattern(org, mod, version)
}
}
}
val shouldForceParser: Parser[Boolean] =
(Space ~> (Parser.literal("-f") | "--force")).?.map(_.isDefined)
val targetFileAndForceParser: Parser[(File, Boolean)] =
Parsers.fileParser(new File(".")) ~ shouldForceParser
// This is to support 0.13.8's InlineConfigurationWithExcludes while not forcing 0.13.8
type HasModule = {
val module: ModuleID
}
def crossName(ivyModule: IvySbt#Module) =
ivyModule.moduleSettings match {
case ic: ModuleDescriptorConfiguration => ic.module.name
case _ =>
throw new IllegalStateException(
"sbt-dependency-graph plugin currently only supports ModuleDescriptorConfiguration of ivy settings (the default in sbt)"
)
}
val VersionPattern = """(\d+)\.(\d+)\.(\d+)(?:-(.*))?""".r
object Version {
def unapply(str: String): Option[(Int, Int, Int, Option[String])] = str match {
case VersionPattern(major, minor, fix, appendix) =>
Some((major.toInt, minor.toInt, fix.toInt, Option(appendix)))
case _ => None
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy