From 9264099594b853a12dc4a6a6b7292fc40f260057 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 24 Jan 2014 14:19:18 -0500 Subject: [PATCH] API docs, better terminology for negative cycle checking in logic system. --- main/src/main/scala/sbt/AutoPlugin.scala | 6 +- main/src/main/scala/sbt/PluginDiscovery.scala | 16 +++++- util/collection/src/main/scala/sbt/Dag.scala | 57 ++++++++++++------- .../src/main/scala/sbt/logic/Logic.scala | 11 ++-- 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/main/src/main/scala/sbt/AutoPlugin.scala b/main/src/main/scala/sbt/AutoPlugin.scala index 3a799d601..7521cd8c9 100644 --- a/main/src/main/scala/sbt/AutoPlugin.scala +++ b/main/src/main/scala/sbt/AutoPlugin.scala @@ -76,7 +76,11 @@ abstract class AutoPlugin // TODO?: def commands: Seq[Command] } -final class AutoPluginException(val origin: LogicException, prefix: String) extends RuntimeException(prefix + Natures.translateMessage(origin)) { +/** An error that occurs when auto-plugins aren't configured properly. +* It translates the error from the underlying logic system to be targeted at end users. */ +final class AutoPluginException(val origin: LogicException, prefix: String) extends RuntimeException(prefix + Natures.translateMessage(origin)) +{ + /** Prepends `p` to the error message derived from `origin`. */ def withPrefix(p: String) = new AutoPluginException(origin, p) } diff --git a/main/src/main/scala/sbt/PluginDiscovery.scala b/main/src/main/scala/sbt/PluginDiscovery.scala index 351debfb8..0d49e6fd7 100644 --- a/main/src/main/scala/sbt/PluginDiscovery.scala +++ b/main/src/main/scala/sbt/PluginDiscovery.scala @@ -17,9 +17,12 @@ object PluginDiscovery final val Builds = "sbt/sbt.builds" final val AutoImports = "sbt/sbt.autoimports" } + /** Names of top-level modules that subclass sbt plugin-related classes: [[Plugin]], [[AutoImport]], [[AutoPlugin]], and [[Build]]. */ final class DiscoveredNames(val plugins: Seq[String], val autoImports: Seq[String], val autoPlugins: Seq[String], val builds: Seq[String]) + def emptyDiscoveredNames: DiscoveredNames = new DiscoveredNames(Nil, Nil, Nil, Nil) + /** Discovers and loads the sbt-plugin-related top-level modules from the classpath and source analysis in `data` and using the provided class `loader`. */ def discoverAll(data: PluginData, loader: ClassLoader): DetectedPlugins = { def discover[T](resource: String)(implicit mf: reflect.ClassManifest[T]) = @@ -27,6 +30,8 @@ object PluginDiscovery import Paths._ new DetectedPlugins(discover[Plugin](Plugins), discover[AutoImport](AutoImports), discover[AutoPlugin](AutoPlugins), discover[Build](Builds)) } + + /** Discovers the sbt-plugin-related top-level modules from the provided source `analysis`. */ def discoverSourceAll(analysis: inc.Analysis): DiscoveredNames = { def discover[T](implicit mf: reflect.ClassManifest[T]): Seq[String] = @@ -35,6 +40,7 @@ object PluginDiscovery } // TODO: for 0.14.0, consider consolidating into a single file, which would make the classpath search 4x faster + /** Writes discovered module `names` to zero or more files in `dir` as per [[writeDescriptor]] and returns the list of files written. */ def writeDescriptors(names: DiscoveredNames, dir: File): Seq[File] = { import Paths._ @@ -47,6 +53,7 @@ object PluginDiscovery files.flatMap(_.toList) } + /** Stores the module `names` in `dir / path`, one per line, unless `names` is empty and then the file is deleted and `None` returned. */ def writeDescriptor(names: Seq[String], dir: File, path: String): Option[File] = { val descriptor: File = new File(dir, path) @@ -62,13 +69,15 @@ object PluginDiscovery } } - + /** Discovers the names of top-level modules listed in resources named `resourceName` as per [[binaryModuleNames]] or + * available as analyzed source and extending from any of `subclasses` as per [[sourceModuleNames]]. */ def binarySourceModuleNames(classpath: Seq[Attributed[File]], loader: ClassLoader, resourceName: String, subclasses: String*): Seq[String] = ( binaryModuleNames(data(classpath), loader, resourceName) ++ (analyzed(classpath) flatMap ( a => sourceModuleNames(a, subclasses : _*) )) ).distinct + /** Discovers top-level modules in `analysis` that inherit from any of `subclasses`. */ def sourceModuleNames(analysis: inc.Analysis, subclasses: String*): Seq[String] = { val subclassSet = subclasses.toSet @@ -80,6 +89,9 @@ object PluginDiscovery } } + /** Obtains the list of modules identified in all resource files `resourceName` from `loader` that are on `classpath`. + * `classpath` and `loader` are both required to ensure that `loader` + * doesn't bring in any resources outside of the intended `classpath`, such as from parent loaders. */ def binaryModuleNames(classpath: Seq[File], loader: ClassLoader, resourceName: String): Seq[String] = { import collection.JavaConversions._ @@ -87,6 +99,8 @@ object PluginDiscovery IO.readLinesURL(u).map( _.trim).filter(!_.isEmpty) } } + + /** Returns `true` if `url` is an entry in `classpath`.*/ def onClasspath(classpath: Seq[File])(url: URL): Boolean = IO.urlAsFile(url) exists (classpath.contains _) diff --git a/util/collection/src/main/scala/sbt/Dag.scala b/util/collection/src/main/scala/sbt/Dag.scala index 0ce07baf2..f0594ed50 100644 --- a/util/collection/src/main/scala/sbt/Dag.scala +++ b/util/collection/src/main/scala/sbt/Dag.scala @@ -72,39 +72,52 @@ object Dag new Cyclic(value, a :: all, false) } - private[sbt] trait System[A] { - type B - def dependencies(t: A): List[B] - def isNegated(b: B): Boolean - def toA(b: B): A + /** A directed graph with edges labeled positive or negative. */ + private[sbt] trait DirectedSignedGraph[Node] + { + /** Directed edge type that tracks the sign and target (head) vertex. + * The sign can be obtained via [[isNegative]] and the target vertex via [[head]]. */ + type Arrow + /** List of initial nodes. */ + def nodes: List[Arrow] + /** Outgoing edges for `n`. */ + def dependencies(n: Node): List[Arrow] + /** `true` if the edge `a` is "negative", false if it is "positive". */ + def isNegative(a: Arrow): Boolean + /** The target of the directed edge `a`. */ + def head(a: Arrow): Node } - private[sbt] def findNegativeCycle[T](system: System[T])(nodes: List[system.B]): List[system.B] = + + /** Traverses a directed graph defined by `graph` looking for a cycle that includes a "negative" edge. + * The directed edges are weighted by the caller as "positive" or "negative". + * If a cycle containing a "negative" edge is detected, its member edges are returned in order. + * Otherwise, the empty list is returned. */ + private[sbt] def findNegativeCycle[Node](graph: DirectedSignedGraph[Node]): List[graph.Arrow] = { import scala.annotation.tailrec - import system._ - val finished = new mutable.HashSet[T] - val visited = new mutable.HashSet[T] + import graph._ + val finished = new mutable.HashSet[Node] + val visited = new mutable.HashSet[Node] - def visit(nodes: List[B], stack: List[B]): List[B] = nodes match { + def visit(edges: List[Arrow], stack: List[Arrow]): List[Arrow] = edges match { case Nil => Nil - case node :: tail => - def indent = "\t" * stack.size - val atom = toA(node) - if(!visited(atom)) + case edge :: tail => + val node = head(edge) + if(!visited(node)) { - visited += atom - visit(dependencies(atom), node :: stack) match { + visited += node + visit(dependencies(node), edge :: stack) match { case Nil => - finished += atom + finished += node visit(tail, stack) case cycle => cycle } } - else if(!finished(atom)) + else if(!finished(node)) { - // cycle. If negation is involved, it is an error. - val between = node :: stack.takeWhile(f => toA(f) != atom) - if(between exists isNegated) + // cycle. If a negative edge is involved, it is an error. + val between = edge :: stack.takeWhile(f => head(f) != node) + if(between exists isNegative) between else visit(tail, stack) @@ -113,7 +126,7 @@ object Dag visit(tail, stack) } - visit(nodes, Nil) + visit(graph.nodes, Nil) } } diff --git a/util/logic/src/main/scala/sbt/logic/Logic.scala b/util/logic/src/main/scala/sbt/logic/Logic.scala index 2181fbb7e..4eb8e64b1 100644 --- a/util/logic/src/main/scala/sbt/logic/Logic.scala +++ b/util/logic/src/main/scala/sbt/logic/Logic.scala @@ -122,17 +122,18 @@ object Logic private[this] def checkAcyclic(clauses: Clauses): Option[CyclicNegation] = { val deps = dependencyMap(clauses) - val cycle = Dag.findNegativeCycle(system(deps))(deps.keys.toList) + val cycle = Dag.findNegativeCycle(graph(deps)) if(cycle.nonEmpty) Some(new CyclicNegation(cycle)) else None } - private[this] def system(deps: Map[Atom, Set[Literal]]) = new Dag.System[Atom] { - type B = Literal + private[this] def graph(deps: Map[Atom, Set[Literal]]) = new Dag.DirectedSignedGraph[Atom] { + type Arrow = Literal + def nodes = deps.keys.toList def dependencies(a: Atom) = deps.getOrElse(a, Set.empty).toList - def isNegated(b: Literal) = b match { + def isNegative(b: Literal) = b match { case Negated(_) => true case Atom(_) => false } - def toA(b: Literal) = b.atom + def head(b: Literal) = b.atom } private[this] def dependencyMap(clauses: Clauses): Map[Atom, Set[Literal]] =