mirror of https://github.com/sbt/sbt.git
Translate errors from logic system to Natures system.
This commit is contained in:
parent
5add7306c2
commit
1afd1931c4
|
|
@ -1,6 +1,7 @@
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
import logic.{Atom, Clause, Clauses, Formula, Literal, Logic}
|
import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated}
|
||||||
|
import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException}
|
||||||
import Def.Setting
|
import Def.Setting
|
||||||
import Natures._
|
import Natures._
|
||||||
|
|
||||||
|
|
@ -75,6 +76,11 @@ abstract class AutoPlugin
|
||||||
// TODO?: def commands: Seq[Command]
|
// TODO?: def commands: Seq[Command]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class AutoPluginException(val origin: LogicException, prefix: String) extends RuntimeException(prefix + Natures.translateMessage(origin)) {
|
||||||
|
def withPrefix(p: String) = new AutoPluginException(origin, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/** An expression that matches `Nature`s. */
|
/** An expression that matches `Nature`s. */
|
||||||
sealed trait Natures {
|
sealed trait Natures {
|
||||||
def && (o: Basic): Natures
|
def && (o: Basic): Natures
|
||||||
|
|
@ -101,14 +107,24 @@ object Natures
|
||||||
{
|
{
|
||||||
val byAtom = defined.map(x => (Atom(x.provides.label), x)).toMap
|
val byAtom = defined.map(x => (Atom(x.provides.label), x)).toMap
|
||||||
val clauses = Clauses( defined.map(d => asClause(d)) )
|
val clauses = Clauses( defined.map(d => asClause(d)) )
|
||||||
requestedNatures => {
|
requestedNatures =>
|
||||||
val results = Logic.reduce(clauses, flatten(requestedNatures).toSet)
|
Logic.reduce(clauses, flatten(requestedNatures).toSet) match {
|
||||||
// results includes the originally requested (positive) atoms,
|
case Left(problem) => throw new AutoPluginException(problem, "")
|
||||||
// which won't have a corresponding AutoPlugin to map back to
|
case Right(results) =>
|
||||||
results.ordered.flatMap(a => byAtom.get(a).toList)
|
// results includes the originally requested (positive) atoms,
|
||||||
}
|
// which won't have a corresponding AutoPlugin to map back to
|
||||||
|
results.ordered.flatMap(a => byAtom.get(a).toList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private[sbt] def translateMessage(e: LogicException) = e match {
|
||||||
|
case ic: InitialContradictions => s"Contradiction in selected natures. These natures were both included and excluded: ${literalsString(ic.literals.toSeq)}"
|
||||||
|
case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required natures are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}"
|
||||||
|
case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}"
|
||||||
|
}
|
||||||
|
private[this] def literalsString(lits: Seq[Literal]): String =
|
||||||
|
lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString(", ")
|
||||||
|
|
||||||
/** [[Natures]] instance that doesn't require any [[Nature]]s. */
|
/** [[Natures]] instance that doesn't require any [[Nature]]s. */
|
||||||
def empty: Natures = Empty
|
def empty: Natures = Empty
|
||||||
private[sbt] final object Empty extends Natures {
|
private[sbt] final object Empty extends Natures {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ package sbt
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.{URI,URL}
|
import java.net.{URI,URL}
|
||||||
import compiler.{Eval,EvalImports}
|
import compiler.{Eval,EvalImports}
|
||||||
import xsbti.compile.CompileOrder
|
|
||||||
import classpath.ClasspathUtilities
|
import classpath.ClasspathUtilities
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import collection.mutable
|
import collection.mutable
|
||||||
import Compiler.{Compilers,Inputs}
|
import Compiler.Compilers
|
||||||
import inc.{FileValueCache, Locate}
|
import inc.{FileValueCache, Locate}
|
||||||
import Project.{inScope,makeSettings}
|
import Project.{inScope,makeSettings}
|
||||||
import Def.{isDummy, ScopedKey, ScopeLocal, Setting}
|
import Def.{isDummy, ScopedKey, ScopeLocal, Setting}
|
||||||
|
|
@ -463,7 +462,9 @@ object Load
|
||||||
def loadSbtFiles(auto: AddSettings, base: File, autoPlugins: Seq[AutoPlugin]): LoadedSbtFile =
|
def loadSbtFiles(auto: AddSettings, base: File, autoPlugins: Seq[AutoPlugin]): LoadedSbtFile =
|
||||||
loadSettings(auto, base, plugins, eval, injectSettings, memoSettings, autoPlugins)
|
loadSettings(auto, base, plugins, eval, injectSettings, memoSettings, autoPlugins)
|
||||||
def loadForProjects = newProjects map { project =>
|
def loadForProjects = newProjects map { project =>
|
||||||
val autoPlugins = plugins.detected.compileNatures(project.natures)
|
val autoPlugins =
|
||||||
|
try plugins.detected.compileNatures(project.natures)
|
||||||
|
catch { case e: AutoPluginException => throw translateAutoPluginException(e, project) }
|
||||||
val autoConfigs = autoPlugins.flatMap(_.projectConfigurations)
|
val autoConfigs = autoPlugins.flatMap(_.projectConfigurations)
|
||||||
val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins)
|
val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins)
|
||||||
val newSettings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings
|
val newSettings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings
|
||||||
|
|
@ -485,6 +486,8 @@ object Load
|
||||||
else
|
else
|
||||||
loadTransitive(nextProjects, buildBase, plugins, eval, injectSettings, loadedProjects, memoSettings)
|
loadTransitive(nextProjects, buildBase, plugins, eval, injectSettings, loadedProjects, memoSettings)
|
||||||
}
|
}
|
||||||
|
private[this] def translateAutoPluginException(e: AutoPluginException, project: Project): AutoPluginException =
|
||||||
|
e.withPrefix(s"Error determining plugins for project '${project.id}' in ${project.base}:\n")
|
||||||
|
|
||||||
private[this] def loadSettings(auto: AddSettings, projectBase: File, loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], autoPlugins: Seq[AutoPlugin]): LoadedSbtFile =
|
private[this] def loadSettings(auto: AddSettings, projectBase: File, loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], autoPlugins: Seq[AutoPlugin]): LoadedSbtFile =
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ object Dag
|
||||||
def visit(nodes: List[B], stack: List[B]): List[B] = nodes match {
|
def visit(nodes: List[B], stack: List[B]): List[B] = nodes match {
|
||||||
case Nil => Nil
|
case Nil => Nil
|
||||||
case node :: tail =>
|
case node :: tail =>
|
||||||
|
def indent = "\t" * stack.size
|
||||||
val atom = toA(node)
|
val atom = toA(node)
|
||||||
if(!visited(atom))
|
if(!visited(atom))
|
||||||
{
|
{
|
||||||
|
|
@ -102,7 +103,7 @@ object Dag
|
||||||
else if(!finished(atom))
|
else if(!finished(atom))
|
||||||
{
|
{
|
||||||
// cycle. If negation is involved, it is an error.
|
// cycle. If negation is involved, it is an error.
|
||||||
val between = stack.takeWhile(f => toA(f) != atom)
|
val between = node :: stack.takeWhile(f => toA(f) != atom)
|
||||||
if(between exists isNegated)
|
if(between exists isNegated)
|
||||||
between
|
between
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -82,22 +82,26 @@ object Formula {
|
||||||
|
|
||||||
object Logic
|
object Logic
|
||||||
{
|
{
|
||||||
def reduceAll(clauses: List[Clause], initialFacts: Set[Literal]): Matched = reduce(Clauses(clauses), initialFacts)
|
def reduceAll(clauses: List[Clause], initialFacts: Set[Literal]): Either[LogicException, Matched] =
|
||||||
|
reduce(Clauses(clauses), initialFacts)
|
||||||
|
|
||||||
/** Computes the variables in the unique stable model for the program represented by `clauses` and `initialFacts`.
|
/** Computes the variables in the unique stable model for the program represented by `clauses` and `initialFacts`.
|
||||||
* `clause` may not have any negative feedback (that is, negation is acyclic)
|
* `clause` may not have any negative feedback (that is, negation is acyclic)
|
||||||
* and `initialFacts` cannot be in the head of any clauses in `clause`.
|
* and `initialFacts` cannot be in the head of any clauses in `clause`.
|
||||||
* These restrictions ensure that the logic program has a unique minimal model. */
|
* These restrictions ensure that the logic program has a unique minimal model. */
|
||||||
def reduce(clauses: Clauses, initialFacts: Set[Literal]): Matched =
|
def reduce(clauses: Clauses, initialFacts: Set[Literal]): Either[LogicException, Matched] =
|
||||||
{
|
{
|
||||||
val (posSeq, negSeq) = separate(initialFacts.toSeq)
|
val (posSeq, negSeq) = separate(initialFacts.toSeq)
|
||||||
val (pos, neg) = (posSeq.toSet, negSeq.toSet)
|
val (pos, neg) = (posSeq.toSet, negSeq.toSet)
|
||||||
|
|
||||||
checkContradictions(pos, neg)
|
val problem =
|
||||||
checkOverlap(clauses, pos)
|
checkContradictions(pos, neg) orElse
|
||||||
checkAcyclic(clauses)
|
checkOverlap(clauses, pos) orElse
|
||||||
|
checkAcyclic(clauses)
|
||||||
|
|
||||||
reduce0(clauses, initialFacts, Matched.empty)
|
problem.toLeft(
|
||||||
|
reduce0(clauses, initialFacts, Matched.empty)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,22 +109,21 @@ object Logic
|
||||||
* This avoids the situation where an atom is proved but no clauses prove it.
|
* This avoids the situation where an atom is proved but no clauses prove it.
|
||||||
* This isn't necessarily a problem, but the main sbt use cases expects
|
* This isn't necessarily a problem, but the main sbt use cases expects
|
||||||
* a proven atom to have at least one clause satisfied. */
|
* a proven atom to have at least one clause satisfied. */
|
||||||
def checkOverlap(clauses: Clauses, initialFacts: Set[Atom]) {
|
private[this] def checkOverlap(clauses: Clauses, initialFacts: Set[Atom]): Option[InitialOverlap] = {
|
||||||
val as = atoms(clauses)
|
val as = atoms(clauses)
|
||||||
val initialOverlap = initialFacts.filter(as.inHead)
|
val initialOverlap = initialFacts.filter(as.inHead)
|
||||||
if(initialOverlap.nonEmpty) throw new InitialOverlap(initialOverlap)
|
if(initialOverlap.nonEmpty) Some(new InitialOverlap(initialOverlap)) else None
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def checkContradictions(pos: Set[Atom], neg: Set[Atom]) {
|
private[this] def checkContradictions(pos: Set[Atom], neg: Set[Atom]): Option[InitialContradictions] = {
|
||||||
val contradictions = pos intersect neg
|
val contradictions = pos intersect neg
|
||||||
if(contradictions.nonEmpty) throw new InitialContradictions(contradictions)
|
if(contradictions.nonEmpty) Some(new InitialContradictions(contradictions)) else None
|
||||||
}
|
}
|
||||||
|
|
||||||
def checkAcyclic(clauses: Clauses) {
|
private[this] def checkAcyclic(clauses: Clauses): Option[CyclicNegation] = {
|
||||||
val deps = dependencyMap(clauses)
|
val deps = dependencyMap(clauses)
|
||||||
val cycle = Dag.findNegativeCycle(system(deps))(deps.keys.toList)
|
val cycle = Dag.findNegativeCycle(system(deps))(deps.keys.toList)
|
||||||
if(cycle.nonEmpty)
|
if(cycle.nonEmpty) Some(new CyclicNegation(cycle)) else None
|
||||||
throw new CyclicNegation(cycle)
|
|
||||||
}
|
}
|
||||||
private[this] def system(deps: Map[Atom, Set[Literal]]) = new Dag.System[Atom] {
|
private[this] def system(deps: Map[Atom, Set[Literal]]) = new Dag.System[Atom] {
|
||||||
type B = Literal
|
type B = Literal
|
||||||
|
|
@ -139,9 +142,10 @@ object Logic
|
||||||
(m /: heads) { (n, head) => n.updated(head, n.getOrElse(head, Set.empty) ++ deps) }
|
(m /: heads) { (n, head) => n.updated(head, n.getOrElse(head, Set.empty) ++ deps) }
|
||||||
}
|
}
|
||||||
|
|
||||||
final class InitialContradictions(val literals: Set[Atom]) extends RuntimeException("Initial facts cannot be both true and false:\n\t" + literals.mkString("\n\t"))
|
sealed abstract class LogicException(override val toString: String)
|
||||||
final class InitialOverlap(val literals: Set[Atom]) extends RuntimeException("Initial positive facts cannot be implied by any clauses:\n\t" + literals.mkString("\n\t"))
|
final class InitialContradictions(val literals: Set[Atom]) extends LogicException("Initial facts cannot be both true and false:\n\t" + literals.mkString("\n\t"))
|
||||||
final class CyclicNegation(val cycle: List[Literal]) extends RuntimeException("Negation may not be involved in a cycle:\n\t" + cycle.mkString("\n\t"))
|
final class InitialOverlap(val literals: Set[Atom]) extends LogicException("Initial positive facts cannot be implied by any clauses:\n\t" + literals.mkString("\n\t"))
|
||||||
|
final class CyclicNegation(val cycle: List[Literal]) extends LogicException("Negation may not be involved in a cycle:\n\t" + cycle.mkString("\n\t"))
|
||||||
|
|
||||||
/** Tracks proven atoms in the reverse order they were proved. */
|
/** Tracks proven atoms in the reverse order they were proved. */
|
||||||
final class Matched private(val provenSet: Set[Atom], reverseOrdered: List[Atom]) {
|
final class Matched private(val provenSet: Set[Atom], reverseOrdered: List[Atom]) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue