mirror of https://github.com/sbt/sbt.git
Added conflict report and unit tests
This commit is contained in:
parent
f43daecee3
commit
5b1c33dd6e
|
|
@ -64,6 +64,8 @@ abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions
|
||||||
|
|
||||||
val label: String = getClass.getName.stripSuffix("$")
|
val label: String = getClass.getName.stripSuffix("$")
|
||||||
|
|
||||||
|
override def toString: String = label
|
||||||
|
|
||||||
/** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/
|
/** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/
|
||||||
def projectConfigurations: Seq[Configuration] = Nil
|
def projectConfigurations: Seq[Configuration] = Nil
|
||||||
|
|
||||||
|
|
@ -169,8 +171,7 @@ object Plugins extends PluginsFunctions
|
||||||
val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet
|
val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet
|
||||||
val c = selectedPlugins.toSet & forbidden
|
val c = selectedPlugins.toSet & forbidden
|
||||||
if (!c.isEmpty) {
|
if (!c.isEmpty) {
|
||||||
val listString = (c map {_.label}).mkString(", ")
|
exlusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {_.label})
|
||||||
throw AutoPluginException(s"Contradiction in selected plugins. These plguins were both included and excluded: ${listString}")
|
|
||||||
}
|
}
|
||||||
val retval = topologicalSort(selectedPlugins, log)
|
val retval = topologicalSort(selectedPlugins, log)
|
||||||
log.debug(s" :: sorted deduced result: ${retval.toString}")
|
log.debug(s" :: sorted deduced result: ${retval.toString}")
|
||||||
|
|
@ -193,7 +194,7 @@ object Plugins extends PluginsFunctions
|
||||||
doSort(roots, nonRoots, ns.size * ns.size + 1)
|
doSort(roots, nonRoots, ns.size * ns.size + 1)
|
||||||
}
|
}
|
||||||
private[sbt] def translateMessage(e: LogicException) = e match {
|
private[sbt] def translateMessage(e: LogicException) = e match {
|
||||||
case ic: InitialContradictions => s"Contradiction in selected plugins. These plguins were both included and excluded: ${literalsString(ic.literals.toSeq)}"
|
case ic: InitialContradictions => s"Contradiction in selected plugins. These plugins were both included and excluded: ${literalsString(ic.literals.toSeq)}"
|
||||||
case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required plugins are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}"
|
case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required plugins 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)}"
|
case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}"
|
||||||
}
|
}
|
||||||
|
|
@ -208,6 +209,29 @@ object Plugins extends PluginsFunctions
|
||||||
val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}"
|
val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}"
|
||||||
throw AutoPluginException(message)
|
throw AutoPluginException(message)
|
||||||
}
|
}
|
||||||
|
private[this] def exlusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]) {
|
||||||
|
def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c =>
|
||||||
|
val reasons = (if (flatten(requested) contains c) List("requested")
|
||||||
|
else Nil) ++
|
||||||
|
(if (c.requires != empty && c.trigger == allRequirements) List(s"enabled by ${c.requires.toString}")
|
||||||
|
else Nil) ++
|
||||||
|
{
|
||||||
|
val reqs = selected filter { x => asRequirements(x) contains c }
|
||||||
|
if (!reqs.isEmpty) List(s"""required by ${reqs.mkString(", ")}""")
|
||||||
|
else Nil
|
||||||
|
} ++
|
||||||
|
{
|
||||||
|
val exs = selected filter { x => asExclusions(x) contains c }
|
||||||
|
if (!exs.isEmpty) List(s"""excluded by ${exs.mkString(", ")}""")
|
||||||
|
else Nil
|
||||||
|
}
|
||||||
|
s""" - conflict: ${c.label} is ${reasons.mkString("; ")}"""
|
||||||
|
}).mkString("\n")
|
||||||
|
throw AutoPluginException(s"""Contradiction in enabled plugins:
|
||||||
|
- requested: ${requested.toString}
|
||||||
|
- enabled: ${selected.mkString(", ")}
|
||||||
|
${listConflicts(conflicting)}""")
|
||||||
|
}
|
||||||
|
|
||||||
private[sbt] final object Empty extends Plugins {
|
private[sbt] final object Empty extends Plugins {
|
||||||
def &&(o: Basic): Plugins = o
|
def &&(o: Basic): Plugins = o
|
||||||
|
|
@ -225,7 +249,7 @@ object Plugins extends PluginsFunctions
|
||||||
}
|
}
|
||||||
private[sbt] final case class And(plugins: List[Basic]) extends Plugins {
|
private[sbt] final case class And(plugins: List[Basic]) extends Plugins {
|
||||||
def &&(o: Basic): Plugins = And(o :: plugins)
|
def &&(o: Basic): Plugins = And(o :: plugins)
|
||||||
override def toString = plugins.mkString(", ")
|
override def toString = plugins.mkString(" && ")
|
||||||
}
|
}
|
||||||
private[sbt] def and(a: Plugins, b: Plugins) = b match {
|
private[sbt] def and(a: Plugins, b: Plugins) = b match {
|
||||||
case Empty => a
|
case Empty => a
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import org.specs2._
|
||||||
|
import mutable.Specification
|
||||||
|
|
||||||
|
object PluginsTest extends Specification
|
||||||
|
{
|
||||||
|
import AI._
|
||||||
|
|
||||||
|
"Auto plugin" should {
|
||||||
|
"enable plugins with trigger=allRequirements AND requirements met" in {
|
||||||
|
deducePlugin(A && B, log) must contain(Q)
|
||||||
|
}
|
||||||
|
"enable transive plugins with trigger=allRequirements AND requirements met" in {
|
||||||
|
deducePlugin(A && B, log) must contain(R)
|
||||||
|
}
|
||||||
|
"order enable plugins after required plugins" in {
|
||||||
|
val ns = deducePlugin(A && B, log)
|
||||||
|
( (ns indexOf Q) must beGreaterThan(ns indexOf A) ) and
|
||||||
|
( (ns indexOf Q) must beGreaterThan(ns indexOf B) ) and
|
||||||
|
( (ns indexOf R) must beGreaterThan(ns indexOf A) ) and
|
||||||
|
( (ns indexOf R) must beGreaterThan(ns indexOf B) ) and
|
||||||
|
( (ns indexOf R) must beGreaterThan(ns indexOf Q) )
|
||||||
|
}
|
||||||
|
"not enable plugins with trigger=allRequirements but conflicting requirements" in {
|
||||||
|
deducePlugin(A && B, log) must not contain(S)
|
||||||
|
}
|
||||||
|
"enable plugins that are required by the requested plugins" in {
|
||||||
|
val ns = deducePlugin(Q, log)
|
||||||
|
(ns must contain(A)) and
|
||||||
|
(ns must contain(B))
|
||||||
|
}
|
||||||
|
"throw an AutoPluginException on conflicting requirements" in {
|
||||||
|
deducePlugin(S, log) must throwAn[AutoPluginException](message = """Contradiction in enabled plugins:
|
||||||
|
- requested: sbt.AI\$S
|
||||||
|
- enabled: sbt.AI\$S, sbt.AI\$Q, sbt.AI\$R, sbt.AI\$B, sbt.AI\$A
|
||||||
|
- conflict: sbt.AI\$R is enabled by sbt.AI\$Q; excluded by sbt.AI\$S""")
|
||||||
|
}
|
||||||
|
"generates a detailed report on conflicting requirements" in {
|
||||||
|
deducePlugin(T && U, log) must throwAn[AutoPluginException](message = """Contradiction in enabled plugins:
|
||||||
|
- requested: sbt.AI\$T && sbt.AI\$U
|
||||||
|
- enabled: sbt.AI\$U, sbt.AI\$T, sbt.AI\$A, sbt.AI\$Q, sbt.AI\$R, sbt.AI\$B
|
||||||
|
- conflict: sbt.AI\$Q is enabled by sbt.AI\$A && sbt.AI\$B; required by sbt.AI\$T, sbt.AI\$R; excluded by sbt.AI\$U
|
||||||
|
- conflict: sbt.AI\$R is enabled by sbt.AI\$Q; excluded by sbt.AI\$T""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AI
|
||||||
|
{
|
||||||
|
lazy val allPlugins: List[AutoPlugin] = List(A, B, Q, R, S, T, U)
|
||||||
|
lazy val deducePlugin = Plugins.deducer(allPlugins)
|
||||||
|
lazy val log = Logger.Null
|
||||||
|
|
||||||
|
trait EmptyAutoPlugin extends AutoPlugin {
|
||||||
|
def requires = empty
|
||||||
|
def trigger = noTrigger
|
||||||
|
}
|
||||||
|
object A extends EmptyAutoPlugin
|
||||||
|
object B extends EmptyAutoPlugin
|
||||||
|
|
||||||
|
object Q extends AutoPlugin
|
||||||
|
{
|
||||||
|
def requires: Plugins = A && B
|
||||||
|
def trigger = allRequirements
|
||||||
|
}
|
||||||
|
|
||||||
|
object R extends AutoPlugin
|
||||||
|
{
|
||||||
|
def requires = Q
|
||||||
|
def trigger = allRequirements
|
||||||
|
}
|
||||||
|
|
||||||
|
object S extends AutoPlugin
|
||||||
|
{
|
||||||
|
def requires = Q && !R
|
||||||
|
def trigger = allRequirements
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an opt-in plugin with a requirement
|
||||||
|
// Unless explicitly loaded by the build user, this will not be activated.
|
||||||
|
object T extends AutoPlugin
|
||||||
|
{
|
||||||
|
def requires = Q && !R
|
||||||
|
def trigger = noTrigger
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an opt-in plugin with a requirement
|
||||||
|
// Unless explicitly loaded by the build user, this will not be activated.
|
||||||
|
object U extends AutoPlugin
|
||||||
|
{
|
||||||
|
def requires = A && !Q
|
||||||
|
def trigger = noTrigger
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue