mirror of https://github.com/sbt/sbt.git
refs #7: add new `what-depends-on` task to find out the reverse dependencies of a module
This commit is contained in:
parent
4d705ed256
commit
5a051f6a3a
|
|
@ -25,13 +25,31 @@ import sbt.Graph
|
||||||
import xml.{Document, XML, Node}
|
import xml.{Document, XML, Node}
|
||||||
import com.github.mdr.ascii.layout
|
import com.github.mdr.ascii.layout
|
||||||
import layout._
|
import layout._
|
||||||
|
import sbinary.{CollectionTypes, Format, DefaultProtocol}
|
||||||
|
|
||||||
object IvyGraphMLDependencies extends App {
|
object IvyGraphMLDependencies extends App {
|
||||||
case class Module(organisation: String, name: String, version: String, error: Option[String] = None) {
|
case class Module(organisation: String, name: String, version: String, error: Option[String] = None) {
|
||||||
def id: String = organisation+":"+name+":"+version
|
def id: String = organisation+":"+name+":"+version
|
||||||
|
|
||||||
|
override def hashCode(): Int = id.hashCode
|
||||||
|
override def equals(p1: Any): Boolean = p1 match {
|
||||||
|
case m: Module => id == m.id
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ModuleGraph(nodes: Seq[Module], edges: Seq[(Module, Module)])
|
case class ModuleGraph(nodes: Seq[Module], edges: Seq[(Module, Module)]) {
|
||||||
|
lazy val dependencyMap: Map[Module, Seq[Module]] = {
|
||||||
|
val m = new HashMap[Module, MSet[Module]] with MultiMap[Module, Module]
|
||||||
|
edges.foreach { case (from, to) => m.addBinding(from, to) }
|
||||||
|
m.toMap.mapValues(_.toSeq.sortBy(_.id))
|
||||||
|
}
|
||||||
|
lazy val reverseDependencyMap: Map[Module, Seq[Module]] = {
|
||||||
|
val m = new HashMap[Module, MSet[Module]] with MultiMap[Module, Module]
|
||||||
|
edges.foreach { case (from, to) => m.addBinding(to, from) }
|
||||||
|
m.toMap.mapValues(_.toSeq.sortBy(_.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def graph(ivyReportFile: String): ModuleGraph =
|
def graph(ivyReportFile: String): ModuleGraph =
|
||||||
buildGraph(buildDoc(ivyReportFile))
|
buildGraph(buildDoc(ivyReportFile))
|
||||||
|
|
@ -49,19 +67,36 @@ object IvyGraphMLDependencies extends App {
|
||||||
ModuleGraph(nodes, edges)
|
ModuleGraph(nodes, edges)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def reverseGraphStartingAt(graph: ModuleGraph, root: Module): ModuleGraph = {
|
||||||
|
val deps = graph.reverseDependencyMap
|
||||||
|
|
||||||
|
def visit(module: Module, visited: Set[Module]): Seq[(Module, Module)] =
|
||||||
|
if (visited(module))
|
||||||
|
Nil
|
||||||
|
else
|
||||||
|
deps.get(module) match {
|
||||||
|
case Some(deps) =>
|
||||||
|
deps.flatMap { to =>
|
||||||
|
(module, to) +: visit(to, visited + module)
|
||||||
|
}
|
||||||
|
case None => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
val edges = visit(root, Set.empty)
|
||||||
|
val nodes = edges.foldLeft(Set.empty[Module])((set, edge) => set + edge._1 + edge._2)
|
||||||
|
ModuleGraph(nodes.toSeq, edges)
|
||||||
|
}
|
||||||
|
|
||||||
def asciiGraph(graph: ModuleGraph): String =
|
def asciiGraph(graph: ModuleGraph): String =
|
||||||
Layouter.renderGraph(buildAsciiGraph(graph))
|
Layouter.renderGraph(buildAsciiGraph(graph))
|
||||||
|
|
||||||
def asciiTree(graph: ModuleGraph): String = {
|
def asciiTree(graph: ModuleGraph): String = {
|
||||||
val deps = {
|
val deps = graph.dependencyMap
|
||||||
val m = new HashMap[String, MSet[Module]] with MultiMap[String, Module]
|
|
||||||
graph.edges.foreach { case (from, to) => m.addBinding(from.id, to) }
|
|
||||||
m.toMap.mapValues(_.toSeq.sortBy(_.id))
|
|
||||||
}
|
|
||||||
// there should only be one root node (the project itself)
|
// there should only be one root node (the project itself)
|
||||||
val roots = graph.nodes.filter(n => !graph.edges.exists(_._2 == n)).sortBy(_.id)
|
val roots = graph.nodes.filter(n => !graph.edges.exists(_._2 == n)).sortBy(_.id)
|
||||||
roots.map { root =>
|
roots.map { root =>
|
||||||
Graph.toAscii[Module](root, node => deps.getOrElse(node.id, Seq.empty[Module]), x => x.id + x.error.map(" (error: "+_+")").getOrElse(""))
|
Graph.toAscii[Module](root, node => deps.getOrElse(node, Seq.empty[Module]), x => x.id + x.error.map(" (error: "+_+")").getOrElse(""))
|
||||||
}.mkString("\n")
|
}.mkString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,4 +152,12 @@ object IvyGraphMLDependencies extends App {
|
||||||
val file = args.lift(0).filter(f => new File(f).exists).getOrElse(die(usage))
|
val file = args.lift(0).filter(f => new File(f).exists).getOrElse(die(usage))
|
||||||
val inputFile = args.lift(1).getOrElse(die(usage))
|
val inputFile = args.lift(1).getOrElse(die(usage))
|
||||||
saveAsGraphML(graph(file), inputFile)
|
saveAsGraphML(graph(file), inputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ModuleGraphProtocol extends DefaultProtocol {
|
||||||
|
import IvyGraphMLDependencies._
|
||||||
|
|
||||||
|
implicit def seqFormat[T: Format]: Format[Seq[T]] = wrap[Seq[T], List[T]](_.toList, _.toSeq)
|
||||||
|
implicit val ModuleFormat: Format[Module] = asProduct4(Module)(Module.unapply(_).get)
|
||||||
|
implicit val ModuleGraphFormat: Format[ModuleGraph] = asProduct2(ModuleGraph)(ModuleGraph.unapply(_).get)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import Keys._
|
||||||
import complete.Parser
|
import complete.Parser
|
||||||
|
|
||||||
import org.apache.ivy.core.resolve.ResolveOptions
|
import org.apache.ivy.core.resolve.ResolveOptions
|
||||||
|
import net.virtualvoid.sbt.graph.IvyGraphMLDependencies.ModuleGraph
|
||||||
|
|
||||||
object Plugin extends sbt.Plugin {
|
object Plugin extends sbt.Plugin {
|
||||||
val dependencyGraphMLFile = SettingKey[File]("dependency-graph-ml-file",
|
val dependencyGraphMLFile = SettingKey[File]("dependency-graph-ml-file",
|
||||||
|
|
@ -44,6 +45,12 @@ object Plugin extends sbt.Plugin {
|
||||||
val ignoreMissingUpdate = TaskKey[UpdateReport]("update-ignore-missing",
|
val ignoreMissingUpdate = TaskKey[UpdateReport]("update-ignore-missing",
|
||||||
"A copy of the update task which ignores missing artifacts")
|
"A copy of the update task which ignores missing artifacts")
|
||||||
|
|
||||||
|
// internal
|
||||||
|
import ModuleGraphProtocol._
|
||||||
|
val moduleGraphStore = TaskKey[IvyGraphMLDependencies.ModuleGraph]("module-graph-store", "The stored module-graph from the last run")
|
||||||
|
|
||||||
|
val whatDependsOn = InputKey[Unit]("what-depends-on", "Shows information about what depends on the given module")
|
||||||
|
|
||||||
def graphSettings = seq(
|
def graphSettings = seq(
|
||||||
ivyReportFunction <<= (sbtVersion, target, projectID, ivyModule, appConfiguration, streams) map { (sbtV, target, projectID, ivyModule, config, streams) =>
|
ivyReportFunction <<= (sbtVersion, target, projectID, ivyModule, appConfiguration, streams) map { (sbtV, target, projectID, ivyModule, config, streams) =>
|
||||||
sbtV match {
|
sbtV match {
|
||||||
|
|
@ -62,6 +69,7 @@ object Plugin extends sbt.Plugin {
|
||||||
def ivyReportForConfig(config: Configuration) = inConfig(config)(seq(
|
def ivyReportForConfig(config: Configuration) = inConfig(config)(seq(
|
||||||
ivyReport <<= ivyReportFunction map (_(config.toString)) dependsOn(ignoreMissingUpdate),
|
ivyReport <<= ivyReportFunction map (_(config.toString)) dependsOn(ignoreMissingUpdate),
|
||||||
moduleGraph <<= ivyReport map (absoluteReportPath.andThen(IvyGraphMLDependencies.graph)),
|
moduleGraph <<= ivyReport map (absoluteReportPath.andThen(IvyGraphMLDependencies.graph)),
|
||||||
|
moduleGraphStore <<= moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph,
|
||||||
asciiGraph <<= moduleGraph map IvyGraphMLDependencies.asciiGraph,
|
asciiGraph <<= moduleGraph map IvyGraphMLDependencies.asciiGraph,
|
||||||
dependencyGraph <<= InputTask(shouldForceParser) { force =>
|
dependencyGraph <<= InputTask(shouldForceParser) { force =>
|
||||||
(force, moduleGraph, streams) map { (force, graph, streams) =>
|
(force, moduleGraph, streams) map { (force, graph, streams) =>
|
||||||
|
|
@ -83,6 +91,11 @@ object Plugin extends sbt.Plugin {
|
||||||
dependencyTree <<= print(asciiTree),
|
dependencyTree <<= print(asciiTree),
|
||||||
dependencyGraphMLFile <<= target / "dependencies-%s.graphml".format(config.toString),
|
dependencyGraphMLFile <<= target / "dependencies-%s.graphml".format(config.toString),
|
||||||
dependencyGraphML <<= dependencyGraphMLTask,
|
dependencyGraphML <<= dependencyGraphMLTask,
|
||||||
|
whatDependsOn <<= InputTask(artifactIdParser) { module =>
|
||||||
|
(module, streams, moduleGraph) map { (module, streams, graph) =>
|
||||||
|
streams.log.info(IvyGraphMLDependencies.asciiTree(IvyGraphMLDependencies.reverseGraphStartingAt(graph, module)))
|
||||||
|
}
|
||||||
|
},
|
||||||
Compat.ignoreMissingUpdateT
|
Compat.ignoreMissingUpdateT
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -108,6 +121,29 @@ object Plugin extends sbt.Plugin {
|
||||||
(Space ~> token("--force")).?.map(_.isDefined)
|
(Space ~> token("--force")).?.map(_.isDefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import IvyGraphMLDependencies.Module
|
||||||
|
|
||||||
|
val artifactIdParser: Initialize[State => Parser[Module]] =
|
||||||
|
resolvedScoped { ctx => (state: State) =>
|
||||||
|
val graph = loadFromContext(moduleGraphStore, ctx, state) getOrElse ModuleGraph(Nil, Nil)
|
||||||
|
|
||||||
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
|
def moduleFrom(modules: Seq[Module]) =
|
||||||
|
modules.map { m =>
|
||||||
|
(token(m.name) ~ Space ~ token(m.version)).map(_ => m)
|
||||||
|
}.reduce(_ | _)
|
||||||
|
|
||||||
|
graph.nodes.groupBy(_.organisation).map {
|
||||||
|
case (org, modules) =>
|
||||||
|
Space ~ token(org) ~ Space ~> moduleFrom(modules)
|
||||||
|
}.reduceOption(_ | _).getOrElse {
|
||||||
|
(Space ~> token(StringBasic, "organization") ~ Space ~ token(StringBasic, "module") ~ Space ~ token(StringBasic, "version") ).map { case ((((org, _), mod), _), version) =>
|
||||||
|
Module(org, mod, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def crossName(ivyModule: IvySbt#Module) =
|
def crossName(ivyModule: IvySbt#Module) =
|
||||||
ivyModule.moduleSettings match {
|
ivyModule.moduleSettings match {
|
||||||
case ic: InlineConfiguration => ic.module.name
|
case ic: InlineConfiguration => ic.module.name
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue