Skip to content
This repository has been archived by the owner on Apr 8, 2021. It is now read-only.

Dependency tree filters #145

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ the notes of version [0.8.2](https://github.com/jrudolph/sbt-dependency-graph/tr

## Main Tasks

* `dependencyTree`: Shows an ASCII tree representation of the project's dependencies
* `dependencyTree`: Shows an ASCII tree representation of the project's dependencies (see [below](#dependencyTree-filtering) for examples filtering the output)
* `dependencyBrowseGraph`: Opens a browser window with a visualization of the dependency graph (courtesy of graphlib-dot + dagre-d3).
* `dependencyList`: Shows a flat list of all transitive dependencies on the sbt console (sorted by organization and name)
* `whatDependsOn <organization> <module> <revision>`: Find out what depends on an artifact. Shows a reverse dependency
Expand All @@ -40,6 +40,43 @@ All tasks can be scoped to a configuration to get the report for a specific conf
for example, prints the dependencies in the `test` configuration. If you don't specify any configuration, `compile` is
assumed as usual.

### `dependencyTree` filtering
The `dependencyTree` task supports filtering with inclusion/exclusion rules:

- exclusion rules are prefixed by `-`
- inclusion rules are the default (or can be prefixed by `+`)

Dependencies are "preserved" iff:
- they match at least one inclusion rule (or no inclusion rules are provided), and
- they match no exclusion rules (including when none are provided)

They are then displayed if they are preserved *or at least one of their transitive dependencies is preserved*.

This mimics the behavior of [Maven dependency:tree](https://maven.apache.org/plugins/maven-dependency-plugin/tree-mojo.html)'s `includes` and `excludes` parameters.

#### Examples

Inclusions/Exclusions can be partial-matched against any part of a dependency's Maven coordinate:

```
dependencyTree -foo // exclude deps that contain "foo" in the group, name, or version
dependencyTree foo // include deps that contain "foo" in the group, name, or version
```

Or they can be fully-matched against specific parts of the coordinate:

```
dependencyTree -:foo* // exclude deps whose name starts with "foo"
dependencyTree -*foo*::*bar // exclude deps whose group contains "foo" and version ends with "bar"
```

Inclusions and exclusions can be combined and repeated:
```
dependencyTree foo bar -baz // include only deps that contain "foo" or "bar" and not "baz"
```

In all cases, the full paths to dependencies that match the query are displayed (which can mean that dependencies are displayed even though they would have been excluded in their own right, because they form part of a chain to a dependency that was not excluded).

## Configuration settings

* `filterScalaLibrary`: Defines if the scala library should be excluded from the output of the dependency-* functions.
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ scalacOptions ++= Seq(
"-unchecked"
)

ScalariformSupport.formatSettings
ScalariformSupport.formatSettings

1 change: 1 addition & 0 deletions project/ScalariformSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ object ScalariformSupport {
.setPreference(AlignParameters, true)
.setPreference(AlignSingleLineCaseStatements, true)
.setPreference(DoubleIndentClassDeclaration, true)
.setPreference(PreserveDanglingCloseParenthesis, true)

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package rendering

import com.github.mdr.ascii.layout._
import net.virtualvoid.sbt.graph.DependencyGraphKeys._
import net.virtualvoid.sbt.graph.model.{ Module, ModuleGraph }
import sbt.Keys._

object AsciiGraph {
Expand Down Expand Up @@ -49,7 +50,7 @@ object AsciiGraph {
log.info("Note: The old tree layout is still available by using `dependency-tree`")
}

log.info(rendering.AsciiTree.asciiTree(moduleGraph.value))
log.info(AsciiTree(moduleGraph.value))

if (!force) {
log.info("\n")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package net.virtualvoid.sbt.graph

import net.virtualvoid.sbt.graph.model.ModuleGraph
import sbt._

trait DependencyGraphKeys {
Expand Down Expand Up @@ -49,9 +50,9 @@ trait DependencyGraphKeys {
"Returns a string containing the ascii representation of the dependency graph for a project")
val dependencyGraph = InputKey[Unit]("dependency-graph",
"Prints the ascii graph to the console")
val asciiTree = TaskKey[String]("dependency-tree-string",
val asciiTree = InputKey[String]("dependency-tree-string",
"Returns a string containing an ascii tree representation of the dependency graph for a project")
val dependencyTree = TaskKey[Unit]("dependency-tree",
val dependencyTree = InputKey[Unit]("dependency-tree",
"Prints an ascii tree of all the dependencies to the console")
val dependencyList = TaskKey[Unit]("dependency-list",
"Prints a list of all dependencies to the console")
Expand All @@ -74,4 +75,4 @@ trait DependencyGraphKeys {
private[graph] val crossProjectId = SettingKey[ModuleID]("dependency-graph-cross-project-id")
}

object DependencyGraphKeys extends DependencyGraphKeys
object DependencyGraphKeys extends DependencyGraphKeys
101 changes: 76 additions & 25 deletions src/main/scala/net/virtualvoid/sbt/graph/DependencyGraphSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@

package net.virtualvoid.sbt.graph

import scala.language.reflectiveCalls

import sbt._
import Keys._
import sbt.complete.Parser
import net.virtualvoid.sbt.graph.GraphTransformations.reverseGraphStartingAt
import net.virtualvoid.sbt.graph.backend.{ IvyReport, SbtUpdateReport }
import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, DagreHTML }
import net.virtualvoid.sbt.graph.model.{ FilterRule, ModuleGraph, ModuleId }
import net.virtualvoid.sbt.graph.rendering.{ AsciiGraph, AsciiTree, DagreHTML }
import net.virtualvoid.sbt.graph.util.IOUtil
import internal.librarymanagement._
import librarymanagement._
import sbt.Keys._
import sbt._
import sbt.complete.Parser
import sbt.dependencygraph.DependencyGraphSbtCompat
import sbt.dependencygraph.DependencyGraphSbtCompat.Implicits._
import sbt.internal.librarymanagement._

import scala.language.reflectiveCalls

object DependencyGraphSettings {
import DependencyGraphKeys._
import ModuleGraphProtocol._
import net.virtualvoid.sbt.graph.model.ModuleGraphProtocol._

def graphSettings = baseSettings ++ reportSettings

Expand All @@ -48,7 +49,17 @@ object DependencyGraphSettings {
ivyReport := { Def.task { ivyReportFunction.value.apply(config.toString) } dependsOn (ignoreMissingUpdate) }.value,
crossProjectId := sbt.CrossVersion(scalaVersion.value, scalaBinaryVersion.value)(projectID.value),
moduleGraphSbt :=
ignoreMissingUpdate.value.configuration(configuration.value).map(report ⇒ SbtUpdateReport.fromConfigurationReport(report, crossProjectId.value)).getOrElse(ModuleGraph.empty),
ignoreMissingUpdate
.value
.configuration(configuration.value)
.map(
report ⇒
SbtUpdateReport.fromConfigurationReport(
report,
crossProjectId.value
)
)
.getOrElse(ModuleGraph.empty),
moduleGraphIvyReport := IvyReport.fromReportFile(absoluteReportPath(ivyReport.value)),
moduleGraph := {
sbtVersion.value match {
Expand All @@ -65,8 +76,11 @@ object DependencyGraphSettings {
else moduleGraph
},
moduleGraphStore := (moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph).value,
asciiTree := rendering.AsciiTree.asciiTree(moduleGraph.value),
dependencyTree := print(asciiTree).value,
asciiTree := AsciiTree(
moduleGraph.value,
filterRulesParser.parsed: _*
),
dependencyTree := streams.value.log.info(asciiTree.evaluated),
dependencyGraphMLFile := { target.value / "dependencies-%s.graphml".format(config.toString) },
dependencyGraphML := dependencyGraphMLTask.value,
dependencyDotFile := { target.value / "dependencies-%s.dot".format(config.toString) },
Expand All @@ -92,8 +106,17 @@ object DependencyGraphSettings {
"""%s<BR/><B>%s</B><BR/>%s""".format(organisation, name, version)
},
whatDependsOn := {
val module = artifactIdParser.parsed
streams.value.log.info(rendering.AsciiTree.asciiTree(GraphTransformations.reverseGraphStartingAt(moduleGraph.value, module)))
streams
.value
.log
.info(
AsciiTree(
reverseGraphStartingAt(
moduleGraph.value,
artifactIdParser.parsed
)
)
)
},
licenseInfo := showLicenseInfo(moduleGraph.value, streams.value)) ++ AsciiGraph.asciiGraphSetttings)

Expand All @@ -105,7 +128,7 @@ object DependencyGraphSettings {
(config: String) ⇒ {
val org = projectID.organization
val name = crossName(ivyModule)
file(s"${crossTarget}/resolution-cache/reports/$org-$name-$config.xml")
file(s"$crossTarget/resolution-cache/reports/$org-$name-$config.xml")
}
}

Expand Down Expand Up @@ -151,29 +174,57 @@ object DependencyGraphSettings {
}.mkString("\n\n")
streams.log.info(output)
}

import Project._
val shouldForceParser: State ⇒ Parser[Boolean] = { (state: State) ⇒
import sbt.complete.DefaultParsers._

(Space ~> token("--force")).?.map(_.isDefined)
}

val filterRulesParser: Def.Initialize[State ⇒ Parser[Seq[FilterRule]]] =
resolvedScoped { ctx ⇒
(state: State) ⇒
import sbt.complete.DefaultParsers._
(Space ~> token(StringBasic, "filter")).*.map {
_.map(FilterRule(_))
}
}

val artifactIdParser: Def.Initialize[State ⇒ Parser[ModuleId]] =
resolvedScoped { ctx ⇒
(state: State) ⇒
val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil)

import sbt.complete.DefaultParsers._
graph.nodes.map(_.id).map {
case id @ ModuleId(org, name, version) ⇒
(Space ~ token(org) ~ token(Space ~ name) ~ token(Space ~ version)).map(_ ⇒ id)
}.reduceOption(_ | _).getOrElse {
(Space ~> token(StringBasic, "organization") ~ Space ~ token(StringBasic, "module") ~ Space ~ token(StringBasic, "version")).map {
case ((((org, _), mod), _), version) ⇒
ModuleId(org, mod, version)
graph
.nodes
.map(_.id)
.map {
case id @ ModuleId(org, name, version) ⇒
(
Space ~
token(org) ~
token(Space ~ name) ~
token(Space ~ version))
.map(_ ⇒ id)
}
.reduceOption(_ | _)
.getOrElse {
(
Space ~>
token(StringBasic, "organization") ~ Space ~
token(StringBasic, "module") ~ Space ~
token(StringBasic, "version"))
.map {
case (
(
((org, _), mod),
_
),
version
) ⇒
ModuleId(org, mod, version)
}
}
}
}

// This is to support 0.13.8's InlineConfigurationWithExcludes while not forcing 0.13.8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package net.virtualvoid.sbt.graph

import net.virtualvoid.sbt.graph.model.{ Module, ModuleGraph, ModuleId }

object GraphTransformations {
def reverseGraphStartingAt(graph: ModuleGraph, root: ModuleId): ModuleGraph = {
val deps = graph.reverseDependencyMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package net.virtualvoid.sbt.graph
package backend

import scala.xml.{ NodeSeq, Document, Node }
import net.virtualvoid.sbt.graph.model.{ Module, ModuleGraph, ModuleId }

import scala.xml.{ Document, Node, NodeSeq }
import scala.xml.parsing.ConstructingParser

object IvyReport {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package net.virtualvoid.sbt.graph
package backend

import net.virtualvoid.sbt.graph.model.{ Module, ModuleGraph, ModuleId }

import scala.language.implicitConversions
import scala.language.reflectiveCalls

import sbt._

object SbtUpdateReport {
Expand Down
Loading