From 5a051f6a3a68de29b3e2d1cc1318be63e4ea8e98 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Mon, 22 Oct 2012 09:28:32 +0200 Subject: [PATCH] refs #7: add new `what-depends-on` task to find out the reverse dependencies of a module --- .../sbt/graph/IvyGraphMLDependencies.scala | 59 ++++++++++++++++--- .../net/virtualvoid/sbt/graph/Plugin.scala | 36 +++++++++++ 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala b/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala index cec856fbe..45527b303 100644 --- a/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/IvyGraphMLDependencies.scala @@ -25,13 +25,31 @@ import sbt.Graph import xml.{Document, XML, Node} import com.github.mdr.ascii.layout import layout._ +import sbinary.{CollectionTypes, Format, DefaultProtocol} object IvyGraphMLDependencies extends App { case class Module(organisation: String, name: String, version: String, error: Option[String] = None) { 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 = buildGraph(buildDoc(ivyReportFile)) @@ -49,19 +67,36 @@ object IvyGraphMLDependencies extends App { 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 = Layouter.renderGraph(buildAsciiGraph(graph)) def asciiTree(graph: ModuleGraph): String = { - val deps = { - 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)) - } + val deps = graph.dependencyMap + // there should only be one root node (the project itself) val roots = graph.nodes.filter(n => !graph.edges.exists(_._2 == n)).sortBy(_.id) 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") } @@ -117,4 +152,12 @@ object IvyGraphMLDependencies extends App { val file = args.lift(0).filter(f => new File(f).exists).getOrElse(die(usage)) val inputFile = args.lift(1).getOrElse(die(usage)) saveAsGraphML(graph(file), inputFile) -} \ No newline at end of file +} + +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) +} diff --git a/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala b/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala index d3c831d48..d516c4659 100755 --- a/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala +++ b/src/main/scala/net/virtualvoid/sbt/graph/Plugin.scala @@ -21,6 +21,7 @@ import Keys._ import complete.Parser import org.apache.ivy.core.resolve.ResolveOptions +import net.virtualvoid.sbt.graph.IvyGraphMLDependencies.ModuleGraph object Plugin extends sbt.Plugin { val dependencyGraphMLFile = SettingKey[File]("dependency-graph-ml-file", @@ -44,6 +45,12 @@ object Plugin extends sbt.Plugin { val ignoreMissingUpdate = TaskKey[UpdateReport]("update-ignore-missing", "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( ivyReportFunction <<= (sbtVersion, target, projectID, ivyModule, appConfiguration, streams) map { (sbtV, target, projectID, ivyModule, config, streams) => sbtV match { @@ -62,6 +69,7 @@ object Plugin extends sbt.Plugin { def ivyReportForConfig(config: Configuration) = inConfig(config)(seq( ivyReport <<= ivyReportFunction map (_(config.toString)) dependsOn(ignoreMissingUpdate), moduleGraph <<= ivyReport map (absoluteReportPath.andThen(IvyGraphMLDependencies.graph)), + moduleGraphStore <<= moduleGraph storeAs moduleGraphStore triggeredBy moduleGraph, asciiGraph <<= moduleGraph map IvyGraphMLDependencies.asciiGraph, dependencyGraph <<= InputTask(shouldForceParser) { force => (force, moduleGraph, streams) map { (force, graph, streams) => @@ -83,6 +91,11 @@ object Plugin extends sbt.Plugin { dependencyTree <<= print(asciiTree), dependencyGraphMLFile <<= target / "dependencies-%s.graphml".format(config.toString), dependencyGraphML <<= dependencyGraphMLTask, + whatDependsOn <<= InputTask(artifactIdParser) { module => + (module, streams, moduleGraph) map { (module, streams, graph) => + streams.log.info(IvyGraphMLDependencies.asciiTree(IvyGraphMLDependencies.reverseGraphStartingAt(graph, module))) + } + }, Compat.ignoreMissingUpdateT )) @@ -108,6 +121,29 @@ object Plugin extends sbt.Plugin { (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) = ivyModule.moduleSettings match { case ic: InlineConfiguration => ic.module.name