mirror of https://github.com/sbt/sbt.git
API docs, better terminology for negative cycle checking in logic system.
This commit is contained in:
parent
1afd1931c4
commit
9264099594
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 _)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]] =
|
||||
|
|
|
|||
Loading…
Reference in New Issue