mirror of https://github.com/sbt/sbt.git
Merge pull request #1516 from sbt/wip/fix-1455
Allow root plugins to be disabled.
This commit is contained in:
commit
3d59b18aef
|
|
@ -5,11 +5,11 @@ TODO:
|
||||||
- error message when a task doesn't exist that it would be provided by plugin x, enabled by natures y,z, blocked by a, b
|
- error message when a task doesn't exist that it would be provided by plugin x, enabled by natures y,z, blocked by a, b
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated}
|
import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated}
|
||||||
import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException}
|
import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException}
|
||||||
import Def.Setting
|
import Def.Setting
|
||||||
import Plugins._
|
import Plugins._
|
||||||
import annotation.tailrec
|
import annotation.tailrec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
An AutoPlugin defines a group of settings and the conditions where the settings are automatically added to a build (called "activation").
|
An AutoPlugin defines a group of settings and the conditions where the settings are automatically added to a build (called "activation").
|
||||||
|
|
@ -30,7 +30,7 @@ For example, the following will automatically add the settings in `projectSettin
|
||||||
override def projectSettings = Seq(...)
|
override def projectSettings = Seq(...)
|
||||||
|
|
||||||
object autoImport {
|
object autoImport {
|
||||||
lazy val obfuscate = taskKey[Seq[File]]("Obfuscates the source.")
|
lazy val obfuscate = taskKey[Seq[File]]("Obfuscates the source.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,278 +50,294 @@ will activate `MyPlugin` defined above and have its settings automatically added
|
||||||
then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added.
|
then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions
|
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
|
||||||
{
|
/** Determines whether this AutoPlugin will be activated for this project when the `requires` clause is satisfied.
|
||||||
/** Determines whether this AutoPlugin will be activated for this project when the `requires` clause is satisfied.
|
*
|
||||||
*
|
* When this method returns `allRequirements`, and `requires` method returns `Web && Javascript`, this plugin
|
||||||
* When this method returns `allRequirements`, and `requires` method returns `Web && Javascript`, this plugin
|
* instance will be added automatically if the `Web` and `Javascript` plugins are enabled.
|
||||||
* instance will be added automatically if the `Web` and `Javascript` plugins are enabled.
|
*
|
||||||
*
|
* When this method returns `noTrigger`, and `requires` method returns `Web && Javascript`, this plugin
|
||||||
* When this method returns `noTrigger`, and `requires` method returns `Web && Javascript`, this plugin
|
* instance will be added only if the build user enables it, but it will automatically add both `Web` and `Javascript`. */
|
||||||
* instance will be added only if the build user enables it, but it will automatically add both `Web` and `Javascript`. */
|
def trigger: PluginTrigger = noTrigger
|
||||||
def trigger: PluginTrigger = noTrigger
|
|
||||||
|
|
||||||
/** This AutoPlugin requires the plugins the [[Plugins]] matcher returned by this method. See [[trigger]].
|
/** This AutoPlugin requires the plugins the [[Plugins]] matcher returned by this method. See [[trigger]].
|
||||||
*/
|
*/
|
||||||
def requires: Plugins = empty
|
def requires: Plugins = empty
|
||||||
|
|
||||||
val label: String = getClass.getName.stripSuffix("$")
|
val label: String = getClass.getName.stripSuffix("$")
|
||||||
|
|
||||||
override def toString: String = label
|
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
|
||||||
|
|
||||||
/** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */
|
/** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */
|
||||||
def projectSettings: Seq[Setting[_]] = Nil
|
def projectSettings: Seq[Setting[_]] = Nil
|
||||||
|
|
||||||
/** The [[Setting]]s to add to the build scope for each project that activates this AutoPlugin.
|
/** The [[Setting]]s to add to the build scope for each project that activates this AutoPlugin.
|
||||||
* The settings returned here are guaranteed to be added to a given build scope only once
|
* The settings returned here are guaranteed to be added to a given build scope only once
|
||||||
* regardless of how many projects for that build activate this AutoPlugin. */
|
* regardless of how many projects for that build activate this AutoPlugin. */
|
||||||
def buildSettings: Seq[Setting[_]] = Nil
|
def buildSettings: Seq[Setting[_]] = Nil
|
||||||
|
|
||||||
/** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */
|
/** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */
|
||||||
def globalSettings: Seq[Setting[_]] = Nil
|
def globalSettings: Seq[Setting[_]] = Nil
|
||||||
|
|
||||||
// TODO?: def commands: Seq[Command]
|
// TODO?: def commands: Seq[Command]
|
||||||
|
|
||||||
private[sbt] def unary_! : Exclude = Exclude(this)
|
private[sbt] def unary_! : Exclude = Exclude(this)
|
||||||
|
|
||||||
|
|
||||||
/** If this plugin does not have any requirements, it means it is actually a root plugin. */
|
/** If this plugin does not have any requirements, it means it is actually a root plugin. */
|
||||||
private[sbt] final def isRoot: Boolean =
|
private[sbt] final def isRoot: Boolean =
|
||||||
requires match {
|
requires match {
|
||||||
case Empty => true
|
case Empty => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** If this plugin does not have any requirements, it means it is actually a root plugin. */
|
/** If this plugin does not have any requirements, it means it is actually a root plugin. */
|
||||||
private[sbt] final def isAlwaysEnabled: Boolean =
|
private[sbt] final def isAlwaysEnabled: Boolean =
|
||||||
isRoot && (trigger == AllRequirements)
|
isRoot && (trigger == AllRequirements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An error that occurs when auto-plugins aren't configured properly.
|
/** 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. */
|
* It translates the error from the underlying logic system to be targeted at end users. */
|
||||||
final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message)
|
final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message) {
|
||||||
{
|
/** Prepends `p` to the error message derived from `origin`. */
|
||||||
/** Prepends `p` to the error message derived from `origin`. */
|
def withPrefix(p: String) = new AutoPluginException(p + message, origin)
|
||||||
def withPrefix(p: String) = new AutoPluginException(p + message, origin)
|
|
||||||
}
|
}
|
||||||
object AutoPluginException
|
object AutoPluginException {
|
||||||
{
|
def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None)
|
||||||
def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None)
|
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin))
|
||||||
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait PluginTrigger
|
sealed trait PluginTrigger
|
||||||
case object AllRequirements extends PluginTrigger
|
case object AllRequirements extends PluginTrigger
|
||||||
case object NoTrigger extends PluginTrigger
|
case object NoTrigger extends PluginTrigger
|
||||||
|
|
||||||
/** An expression that matches `AutoPlugin`s. */
|
/** An expression that matches `AutoPlugin`s. */
|
||||||
sealed trait Plugins {
|
sealed trait Plugins {
|
||||||
def && (o: Basic): Plugins
|
def && (o: Basic): Plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed trait PluginsFunctions
|
sealed trait PluginsFunctions {
|
||||||
{
|
/** [[Plugins]] instance that doesn't require any [[Plugins]]s. */
|
||||||
/** [[Plugins]] instance that doesn't require any [[Plugins]]s. */
|
def empty: Plugins = Plugins.Empty
|
||||||
def empty: Plugins = Plugins.Empty
|
|
||||||
|
|
||||||
/** This plugin is activated when all required plugins are present. */
|
/** This plugin is activated when all required plugins are present. */
|
||||||
def allRequirements: PluginTrigger = AllRequirements
|
def allRequirements: PluginTrigger = AllRequirements
|
||||||
/** This plugin is activated only when it is manually activated. */
|
/** This plugin is activated only when it is manually activated. */
|
||||||
def noTrigger: PluginTrigger = NoTrigger
|
def noTrigger: PluginTrigger = NoTrigger
|
||||||
}
|
}
|
||||||
|
|
||||||
object Plugins extends PluginsFunctions
|
object Plugins extends PluginsFunctions {
|
||||||
{
|
/** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[AutoPlugin]]s.
|
||||||
/** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[AutoPlugin]]s.
|
* The [[AutoPlugin]]s are topologically sorted so that a required [[AutoPlugin]] comes before its requiring [[AutoPlugin]].*/
|
||||||
* The [[AutoPlugin]]s are topologically sorted so that a required [[AutoPlugin]] comes before its requiring [[AutoPlugin]].*/
|
def deducer(defined0: List[AutoPlugin]): (Plugins, Logger) => Seq[AutoPlugin] =
|
||||||
def deducer(defined0: List[AutoPlugin]): (Plugins, Logger) => Seq[AutoPlugin] =
|
if(defined0.isEmpty) (_, _) => Nil
|
||||||
if(defined0.isEmpty) (_, _) => Nil
|
else {
|
||||||
else
|
// TODO: defined should return all the plugins
|
||||||
{
|
val allReqs = (defined0 flatMap { asRequirements }).toSet
|
||||||
// TODO: defined should return all the plugins
|
val diff = allReqs diff defined0.toSet
|
||||||
val allReqs = (defined0 flatMap { asRequirements }).toSet
|
val defined = if (!diff.isEmpty) diff.toList ::: defined0
|
||||||
val diff = allReqs diff defined0.toSet
|
else defined0
|
||||||
val defined = if (!diff.isEmpty) diff.toList ::: defined0
|
|
||||||
else defined0
|
|
||||||
|
|
||||||
val byAtom = defined map { x => (Atom(x.label), x) }
|
val byAtom = defined map { x => (Atom(x.label), x) }
|
||||||
val byAtomMap = byAtom.toMap
|
val byAtomMap = byAtom.toMap
|
||||||
if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom)
|
if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom)
|
||||||
// Ignore clauses for plugins that does not require anything else.
|
// Ignore clauses for plugins that does not require anything else.
|
||||||
// Avoids the requirement for pure Nature strings *and* possible
|
// Avoids the requirement for pure Nature strings *and* possible
|
||||||
// circular dependencies in the logic.
|
// circular dependencies in the logic.
|
||||||
val allRequirementsClause = defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(d))
|
val allRequirementsClause = defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(d))
|
||||||
val allEnabledByClause = defined.filterNot(_.isRoot).flatMap(d => asEnabledByClauses(d))
|
val allEnabledByClause = defined.filterNot(_.isRoot).flatMap(d => asEnabledByClauses(d))
|
||||||
(requestedPlugins, log) => {
|
|
||||||
val alwaysEnabled: List[AutoPlugin] = defined.filter(_.isAlwaysEnabled)
|
|
||||||
val knowlege0: Set[Atom] = ((flatten(requestedPlugins) ++ alwaysEnabled) collect {
|
|
||||||
case x: AutoPlugin => Atom(x.label)
|
|
||||||
}).toSet
|
|
||||||
val clauses = Clauses((allRequirementsClause ::: allEnabledByClause) filterNot { _.head subsetOf knowlege0 })
|
|
||||||
log.debug(s"deducing auto plugins based on known facts ${knowlege0.toString} and clauses ${clauses.toString}")
|
|
||||||
Logic.reduce(clauses, (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match {
|
|
||||||
case Left(problem) => throw AutoPluginException(problem)
|
|
||||||
case Right(results) =>
|
|
||||||
log.debug(s" :: deduced result: ${results}")
|
|
||||||
val selectedAtoms: List[Atom] = results.ordered
|
|
||||||
val selectedPlugins = selectedAtoms map { a =>
|
|
||||||
byAtomMap.getOrElse(a, throw AutoPluginException(s"${a} was not found in atom map."))
|
|
||||||
}
|
|
||||||
val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet
|
|
||||||
val c = selectedPlugins.toSet & forbidden
|
|
||||||
if (!c.isEmpty) {
|
|
||||||
exlusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {_.label})
|
|
||||||
}
|
|
||||||
val retval = topologicalSort(selectedPlugins, log)
|
|
||||||
log.debug(s" :: sorted deduced result: ${retval.toString}")
|
|
||||||
retval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private[sbt] def topologicalSort(ns: List[AutoPlugin], log: Logger): List[AutoPlugin] = {
|
|
||||||
log.debug(s"sorting: ns: ${ns.toString}")
|
|
||||||
@tailrec def doSort(found0: List[AutoPlugin], notFound0: List[AutoPlugin], limit0: Int): List[AutoPlugin] = {
|
|
||||||
log.debug(s" :: sorting:: found: ${found0.toString} not found ${notFound0.toString}")
|
|
||||||
if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically")
|
|
||||||
else if (notFound0.isEmpty) found0
|
|
||||||
else {
|
|
||||||
val (found1, notFound1) = notFound0 partition { n => asRequirements(n).toSet subsetOf found0.toSet }
|
|
||||||
doSort(found0 ::: found1, notFound1, limit0 - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val (roots, nonRoots) = ns partition (_.isRoot)
|
|
||||||
doSort(roots, nonRoots, ns.size * ns.size + 1)
|
|
||||||
}
|
|
||||||
private[sbt] def translateMessage(e: LogicException) = e match {
|
|
||||||
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 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(", ")
|
|
||||||
|
|
||||||
private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]) {
|
// Note: Here is where the function begins. We're given a list of plugins now.
|
||||||
val dupsByAtom = byAtom.groupBy(_._1).mapValues(_.map(_._2))
|
(requestedPlugins, log) => {
|
||||||
val dupStrings = for( (atom, dups) <- dupsByAtom if dups.size > 1 ) yield
|
def explicitlyDisabled(p: AutoPlugin): Boolean = hasExclude(requestedPlugins, p)
|
||||||
s"${atom.label} by ${dups.mkString(", ")}"
|
val alwaysEnabled: List[AutoPlugin] = defined.filter(_.isAlwaysEnabled).filterNot(explicitlyDisabled)
|
||||||
val (ns, nl) = if(dupStrings.size > 1) ("s", "\n\t") else ("", " ")
|
val knowlege0: Set[Atom] = ((flatten(requestedPlugins) ++ alwaysEnabled) collect {
|
||||||
val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}"
|
case x: AutoPlugin => Atom(x.label)
|
||||||
throw AutoPluginException(message)
|
}).toSet
|
||||||
}
|
val clauses = Clauses((allRequirementsClause ::: allEnabledByClause) filterNot { _.head subsetOf knowlege0 })
|
||||||
private[this] def exlusionConflictError(requested: Plugins, selected: Seq[AutoPlugin], conflicting: Seq[AutoPlugin]) {
|
log.debug(s"deducing auto plugins based on known facts ${knowlege0.toString} and clauses ${clauses.toString}")
|
||||||
def listConflicts(ns: Seq[AutoPlugin]) = (ns map { c =>
|
Logic.reduce(clauses, (flattenConvert(requestedPlugins) ++ convertAll(alwaysEnabled)).toSet) match {
|
||||||
val reasons = (if (flatten(requested) contains c) List("requested")
|
case Left(problem) => throw AutoPluginException(problem)
|
||||||
else Nil) ++
|
case Right(results) =>
|
||||||
(if (c.requires != empty && c.trigger == allRequirements) List(s"enabled by ${c.requires.toString}")
|
log.debug(s" :: deduced result: ${results}")
|
||||||
else Nil) ++
|
val selectedAtoms: List[Atom] = results.ordered
|
||||||
{
|
val selectedPlugins = selectedAtoms map { a =>
|
||||||
val reqs = selected filter { x => asRequirements(x) contains c }
|
byAtomMap.getOrElse(a, throw AutoPluginException(s"${a} was not found in atom map."))
|
||||||
if (!reqs.isEmpty) List(s"""required by ${reqs.mkString(", ")}""")
|
}
|
||||||
else Nil
|
val forbidden: Set[AutoPlugin] = (selectedPlugins flatMap { Plugins.asExclusions }).toSet
|
||||||
} ++
|
val c = selectedPlugins.toSet & forbidden
|
||||||
{
|
if (!c.isEmpty) {
|
||||||
val exs = selected filter { x => asExclusions(x) contains c }
|
exlusionConflictError(requestedPlugins, selectedPlugins, c.toSeq sortBy {_.label})
|
||||||
if (!exs.isEmpty) List(s"""excluded by ${exs.mkString(", ")}""")
|
}
|
||||||
else Nil
|
val retval = topologicalSort(selectedPlugins, log)
|
||||||
}
|
log.debug(s" :: sorted deduced result: ${retval.toString}")
|
||||||
s""" - conflict: ${c.label} is ${reasons.mkString("; ")}"""
|
retval
|
||||||
}).mkString("\n")
|
}
|
||||||
throw AutoPluginException(s"""Contradiction in enabled plugins:
|
}
|
||||||
|
}
|
||||||
|
private[sbt] def topologicalSort(ns: List[AutoPlugin], log: Logger): List[AutoPlugin] = {
|
||||||
|
log.debug(s"sorting: ns: ${ns.toString}")
|
||||||
|
@tailrec def doSort(found0: List[AutoPlugin], notFound0: List[AutoPlugin], limit0: Int): List[AutoPlugin] = {
|
||||||
|
log.debug(s" :: sorting:: found: ${found0.toString} not found ${notFound0.toString}")
|
||||||
|
if (limit0 < 0) throw AutoPluginException(s"Failed to sort ${ns} topologically")
|
||||||
|
else if (notFound0.isEmpty) found0
|
||||||
|
else {
|
||||||
|
val (found1, notFound1) = notFound0 partition { n => asRequirements(n).toSet subsetOf found0.toSet }
|
||||||
|
doSort(found0 ::: found1, notFound1, limit0 - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val (roots, nonRoots) = ns partition (_.isRoot)
|
||||||
|
doSort(roots, nonRoots, ns.size * ns.size + 1)
|
||||||
|
}
|
||||||
|
private[sbt] def translateMessage(e: LogicException) = e match {
|
||||||
|
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 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(", ")
|
||||||
|
|
||||||
|
private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]) {
|
||||||
|
val dupsByAtom = byAtom.groupBy(_._1).mapValues(_.map(_._2))
|
||||||
|
val dupStrings = for( (atom, dups) <- dupsByAtom if dups.size > 1 ) yield
|
||||||
|
s"${atom.label} by ${dups.mkString(", ")}"
|
||||||
|
val (ns, nl) = if(dupStrings.size > 1) ("s", "\n\t") else ("", " ")
|
||||||
|
val message = s"Plugin$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}"
|
||||||
|
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}
|
- requested: ${requested.toString}
|
||||||
- enabled: ${selected.mkString(", ")}
|
- enabled: ${selected.mkString(", ")}
|
||||||
${listConflicts(conflicting)}""")
|
${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
|
||||||
override def toString = "<none>"
|
override def toString = "<none>"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An included or excluded Nature/Plugin. TODO: better name than Basic. Also, can we dump
|
/** An included or excluded Nature/Plugin. TODO: better name than Basic. Also, can we dump
|
||||||
* this class.
|
* this class.
|
||||||
*/
|
*/
|
||||||
sealed abstract class Basic extends Plugins {
|
sealed abstract class Basic extends Plugins {
|
||||||
def &&(o: Basic): Plugins = And(this :: o :: Nil)
|
def &&(o: Basic): Plugins = And(this :: o :: Nil)
|
||||||
}
|
}
|
||||||
private[sbt] final case class Exclude(n: AutoPlugin) extends Basic {
|
private[sbt] final case class Exclude(n: AutoPlugin) extends Basic {
|
||||||
override def toString = s"!$n"
|
override def toString = s"!$n"
|
||||||
}
|
}
|
||||||
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
|
||||||
case And(ns) => (a /: ns)(_ && _)
|
case And(ns) => (a /: ns)(_ && _)
|
||||||
case b: Basic => a && b
|
case b: Basic => a && b
|
||||||
}
|
}
|
||||||
private[sbt] def remove(a: Plugins, del: Set[Basic]): Plugins = a match {
|
private[sbt] def remove(a: Plugins, del: Set[Basic]): Plugins = a match {
|
||||||
case b: Basic => if(del(b)) Empty else b
|
case b: Basic => if(del(b)) Empty else b
|
||||||
case Empty => Empty
|
case Empty => Empty
|
||||||
case And(ns) =>
|
case And(ns) =>
|
||||||
val removed = ns.filterNot(del)
|
val removed = ns.filterNot(del)
|
||||||
if(removed.isEmpty) Empty else And(removed)
|
if(removed.isEmpty) Empty else And(removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Defines enabled-by clauses for `ap`. */
|
/** Defines enabled-by clauses for `ap`. */
|
||||||
private[sbt] def asEnabledByClauses(ap: AutoPlugin): List[Clause] =
|
private[sbt] def asEnabledByClauses(ap: AutoPlugin): List[Clause] =
|
||||||
// `ap` is the head and the required plugins for `ap` is the body.
|
// `ap` is the head and the required plugins for `ap` is the body.
|
||||||
if (ap.trigger == AllRequirements) Clause( convert(ap.requires), Set(Atom(ap.label)) ) :: Nil
|
if (ap.trigger == AllRequirements) Clause( convert(ap.requires), Set(Atom(ap.label)) ) :: Nil
|
||||||
else Nil
|
else Nil
|
||||||
/** Defines requirements clauses for `ap`. */
|
/** Defines requirements clauses for `ap`. */
|
||||||
private[sbt] def asRequirementsClauses(ap: AutoPlugin): List[Clause] =
|
private[sbt] def asRequirementsClauses(ap: AutoPlugin): List[Clause] =
|
||||||
// required plugin is the head and `ap` is the body.
|
// required plugin is the head and `ap` is the body.
|
||||||
asRequirements(ap) map { x => Clause( convert(ap), Set(Atom(x.label)) ) }
|
asRequirements(ap) map { x => Clause( convert(ap), Set(Atom(x.label)) ) }
|
||||||
private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
|
private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
|
||||||
case x: AutoPlugin => x
|
case x: AutoPlugin => x
|
||||||
}
|
}
|
||||||
private[sbt] def asExclusions(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
|
private[sbt] def asExclusions(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
|
||||||
case Exclude(x) => x
|
case Exclude(x) => x
|
||||||
}
|
}
|
||||||
private[this] def flattenConvert(n: Plugins): Seq[Literal] = n match {
|
// TODO - This doesn't handle nested AND boolean logic...
|
||||||
case And(ns) => convertAll(ns)
|
private[sbt] def hasExclude(n: Plugins, p: AutoPlugin): Boolean = n match {
|
||||||
case b: Basic => convertBasic(b) :: Nil
|
case `p` => false
|
||||||
case Empty => Nil
|
case Exclude(`p`) => true
|
||||||
}
|
// TODO - This is stupidly advanced. We do a nested check through possible and-ed
|
||||||
private[sbt] def flatten(n: Plugins): Seq[Basic] = n match {
|
// lists of plugins exclusions to see if the plugin ever winds up in an excluded=true case.
|
||||||
case And(ns) => ns
|
// This would handle things like !!p or !(p && z)
|
||||||
case b: Basic => b :: Nil
|
case Exclude(n) => hasInclude(n, p)
|
||||||
case Empty => Nil
|
case And(ns) => ns.forall(n => hasExclude(n, p))
|
||||||
}
|
case b: Basic => false
|
||||||
|
case Empty => false
|
||||||
|
}
|
||||||
|
private[sbt] def hasInclude(n: Plugins, p: AutoPlugin): Boolean = n match {
|
||||||
|
case `p` => true
|
||||||
|
case Exclude(n) => hasExclude(n, p)
|
||||||
|
case And(ns) => ns.forall(n => hasInclude(n, p))
|
||||||
|
case b: Basic => false
|
||||||
|
case Empty => false
|
||||||
|
}
|
||||||
|
private[this] def flattenConvert(n: Plugins): Seq[Literal] = n match {
|
||||||
|
case And(ns) => convertAll(ns)
|
||||||
|
case b: Basic => convertBasic(b) :: Nil
|
||||||
|
case Empty => Nil
|
||||||
|
}
|
||||||
|
private[sbt] def flatten(n: Plugins): Seq[Basic] = n match {
|
||||||
|
case And(ns) => ns
|
||||||
|
case b: Basic => b :: Nil
|
||||||
|
case Empty => Nil
|
||||||
|
}
|
||||||
|
|
||||||
private[this] def convert(n: Plugins): Formula = n match {
|
private[this] def convert(n: Plugins): Formula = n match {
|
||||||
case And(ns) => convertAll(ns).reduce[Formula](_ && _)
|
case And(ns) => convertAll(ns).reduce[Formula](_ && _)
|
||||||
case b: Basic => convertBasic(b)
|
case b: Basic => convertBasic(b)
|
||||||
case Empty => Formula.True
|
case Empty => Formula.True
|
||||||
}
|
}
|
||||||
private[this] def convertBasic(b: Basic): Literal = b match {
|
private[this] def convertBasic(b: Basic): Literal = b match {
|
||||||
case Exclude(n) => !convertBasic(n)
|
case Exclude(n) => !convertBasic(n)
|
||||||
case a: AutoPlugin => Atom(a.label)
|
case a: AutoPlugin => Atom(a.label)
|
||||||
}
|
}
|
||||||
private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic
|
private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic
|
||||||
|
|
||||||
/** True if the trigger clause `n` is satisifed by `model`. */
|
/** True if the trigger clause `n` is satisifed by `model`. */
|
||||||
def satisfied(n: Plugins, model: Set[AutoPlugin]): Boolean =
|
def satisfied(n: Plugins, model: Set[AutoPlugin]): Boolean =
|
||||||
flatten(n) forall {
|
flatten(n) forall {
|
||||||
case Exclude(a) => !model(a)
|
case Exclude(a) => !model(a)
|
||||||
case ap: AutoPlugin => model(ap)
|
case ap: AutoPlugin => model(ap)
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] def hasAutoImportGetter(ap: AutoPlugin, loader: ClassLoader): Boolean = {
|
private[sbt] def hasAutoImportGetter(ap: AutoPlugin, loader: ClassLoader): Boolean = {
|
||||||
import reflect.runtime.{universe => ru}
|
import reflect.runtime.{universe => ru}
|
||||||
import util.control.Exception.catching
|
import util.control.Exception.catching
|
||||||
val m = ru.runtimeMirror(loader)
|
val m = ru.runtimeMirror(loader)
|
||||||
val im = m.reflect(ap)
|
val im = m.reflect(ap)
|
||||||
val hasGetterOpt = catching(classOf[ScalaReflectionException]) opt {
|
val hasGetterOpt = catching(classOf[ScalaReflectionException]) opt {
|
||||||
im.symbol.asType.toType.declaration(ru.newTermName("autoImport")) match {
|
im.symbol.asType.toType.declaration(ru.newTermName("autoImport")) match {
|
||||||
case ru.NoSymbol => false
|
case ru.NoSymbol => false
|
||||||
case sym => sym.asTerm.isGetter || sym.asTerm.isModule
|
case sym => sym.asTerm.isGetter || sym.asTerm.isModule
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasGetterOpt getOrElse false
|
hasGetterOpt getOrElse false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,382 +1,375 @@
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
import Def.Setting
|
import Def.Setting
|
||||||
import Plugins._
|
import Plugins._
|
||||||
import PluginsDebug._
|
import PluginsDebug._
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: Map[String, AttributeKey[_]], val provided: Relation[AutoPlugin, AttributeKey[_]])
|
private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: Map[String, AttributeKey[_]], val provided: Relation[AutoPlugin, AttributeKey[_]]) {
|
||||||
{
|
/** The set of [[AutoPlugin]]s that might define a key named `keyName`.
|
||||||
/** The set of [[AutoPlugin]]s that might define a key named `keyName`.
|
* Because plugins can define keys in different scopes, this should only be used as a guideline. */
|
||||||
* Because plugins can define keys in different scopes, this should only be used as a guideline. */
|
def providers(keyName: String): Set[AutoPlugin] = nameToKey.get(keyName) match {
|
||||||
def providers(keyName: String): Set[AutoPlugin] = nameToKey.get(keyName) match {
|
case None => Set.empty
|
||||||
case None => Set.empty
|
case Some(key) => provided.reverse(key)
|
||||||
case Some(key) => provided.reverse(key)
|
}
|
||||||
}
|
/** Describes alternative approaches for defining key [[keyName]] in [[context]].*/
|
||||||
/** Describes alternative approaches for defining key [[keyName]] in [[context]].*/
|
def toEnable(keyName: String, context: Context): List[PluginEnable] =
|
||||||
def toEnable(keyName: String, context: Context): List[PluginEnable] =
|
providers(keyName).toList.map(plugin => pluginEnable(context, plugin))
|
||||||
providers(keyName).toList.map(plugin => pluginEnable(context, plugin))
|
|
||||||
|
|
||||||
/** Provides text to suggest how [[notFoundKey]] can be defined in [[context]]. */
|
/** Provides text to suggest how [[notFoundKey]] can be defined in [[context]]. */
|
||||||
def debug(notFoundKey: String, context: Context): String =
|
def debug(notFoundKey: String, context: Context): String =
|
||||||
{
|
{
|
||||||
val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) {
|
val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) {
|
||||||
case pa: PluginActivated => Left(pa)
|
case pa: PluginActivated => Left(pa)
|
||||||
case pd: EnableDeactivated => Right(pd)
|
case pd: EnableDeactivated => Right(pd)
|
||||||
}
|
}
|
||||||
val activePrefix = if(activated.nonEmpty) s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n" else ""
|
val activePrefix = if(activated.nonEmpty) s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n" else ""
|
||||||
activePrefix + debugDeactivated(notFoundKey, deactivated)
|
activePrefix + debugDeactivated(notFoundKey, deactivated)
|
||||||
}
|
}
|
||||||
private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String =
|
private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String =
|
||||||
{
|
{
|
||||||
val (impossible, possible) = Util.separate(deactivated) {
|
val (impossible, possible) = Util.separate(deactivated) {
|
||||||
case pi: PluginImpossible => Left(pi)
|
case pi: PluginImpossible => Left(pi)
|
||||||
case pr: PluginRequirements => Right(pr)
|
case pr: PluginRequirements => Right(pr)
|
||||||
}
|
}
|
||||||
if(possible.nonEmpty) {
|
if(possible.nonEmpty) {
|
||||||
val explained = possible.map(explainPluginEnable)
|
val explained = possible.map(explainPluginEnable)
|
||||||
val possibleString =
|
val possibleString =
|
||||||
if(explained.size > 1) explained.zipWithIndex.map{case (s,i) => s"$i. $s"}.mkString("Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
|
if(explained.size > 1) explained.zipWithIndex.map{case (s,i) => s"$i. $s"}.mkString("Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
|
||||||
else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}"
|
else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}"
|
||||||
def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ")
|
def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ")
|
||||||
val imPostfix = if(impossible.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are impossible to add: $impossiblePlugins"
|
val imPostfix = if(impossible.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are impossible to add: $impossiblePlugins"
|
||||||
possibleString + imPostfix
|
possibleString + imPostfix
|
||||||
}
|
}
|
||||||
else if(impossible.isEmpty)
|
else if(impossible.isEmpty)
|
||||||
s"No available plugin provides key $notFoundKey."
|
s"No available plugin provides key $notFoundKey."
|
||||||
else {
|
else {
|
||||||
val explanations = impossible.map(explainPluginEnable)
|
val explanations = impossible.map(explainPluginEnable)
|
||||||
explanations.mkString(s"Plugins are available that could provide $notFoundKey, but they are impossible to add:\n\t", "\n\t", "")
|
explanations.mkString(s"Plugins are available that could provide $notFoundKey, but they are impossible to add:\n\t", "\n\t", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Text that suggests how to activate [[plugin]] in [[context]] if possible and if it is not already activated.*/
|
/** Text that suggests how to activate [[plugin]] in [[context]] if possible and if it is not already activated.*/
|
||||||
def help(plugin: AutoPlugin, context: Context): String =
|
def help(plugin: AutoPlugin, context: Context): String =
|
||||||
if(context.enabled.contains(plugin))
|
if (context.enabled.contains(plugin)) activatedHelp(plugin)
|
||||||
activatedHelp(plugin)
|
else deactivatedHelp(plugin, context)
|
||||||
else
|
private def activatedHelp(plugin: AutoPlugin): String =
|
||||||
deactivatedHelp(plugin, context)
|
{
|
||||||
private def activatedHelp(plugin: AutoPlugin): String =
|
val prefix = s"${plugin.label} is activated."
|
||||||
{
|
val keys = provided.forward(plugin)
|
||||||
val prefix = s"${plugin.label} is activated."
|
val keysString = if(keys.isEmpty) "" else s"\nIt may affect these keys: ${multi(keys.toList.map(_.label))}"
|
||||||
val keys = provided.forward(plugin)
|
val configs = plugin.projectConfigurations
|
||||||
val keysString = if(keys.isEmpty) "" else s"\nIt may affect these keys: ${multi(keys.toList.map(_.label))}"
|
val confsString = if(configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}"
|
||||||
val configs = plugin.projectConfigurations
|
prefix + keysString + confsString
|
||||||
val confsString = if(configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}"
|
}
|
||||||
prefix + keysString + confsString
|
private def deactivatedHelp(plugin: AutoPlugin, context: Context): String =
|
||||||
}
|
{
|
||||||
private def deactivatedHelp(plugin: AutoPlugin, context: Context): String =
|
val prefix = s"${plugin.label} is NOT activated."
|
||||||
{
|
val keys = provided.forward(plugin)
|
||||||
val prefix = s"${plugin.label} is NOT activated."
|
val keysString = if(keys.isEmpty) "" else s"\nActivating it may affect these keys: ${multi(keys.toList.map(_.label))}"
|
||||||
val keys = provided.forward(plugin)
|
val configs = plugin.projectConfigurations
|
||||||
val keysString = if(keys.isEmpty) "" else s"\nActivating it may affect these keys: ${multi(keys.toList.map(_.label))}"
|
val confsString = if(configs.isEmpty) "" else s"\nActivating it will define these configurations: ${multi(configs.map(_.name))}"
|
||||||
val configs = plugin.projectConfigurations
|
val toActivate = explainPluginEnable(pluginEnable(context, plugin))
|
||||||
val confsString = if(configs.isEmpty) "" else s"\nActivating it will define these configurations: ${multi(configs.map(_.name))}"
|
s"$prefix$keysString$confsString\n$toActivate"
|
||||||
val toActivate = explainPluginEnable(pluginEnable(context, plugin))
|
}
|
||||||
s"$prefix$keysString$confsString\n$toActivate"
|
|
||||||
}
|
|
||||||
|
|
||||||
private[this] def multi(strs: Seq[String]): String = strs.mkString(if(strs.size > 4) "\n\t" else ", ")
|
private[this] def multi(strs: Seq[String]): String = strs.mkString(if(strs.size > 4) "\n\t" else ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] object PluginsDebug
|
private[sbt] object PluginsDebug {
|
||||||
{
|
def helpAll(s: State): String =
|
||||||
def helpAll(s: State): String =
|
if(Project.isProjectLoaded(s))
|
||||||
if(Project.isProjectLoaded(s))
|
{
|
||||||
{
|
val extracted = Project.extract(s)
|
||||||
val extracted = Project.extract(s)
|
import extracted._
|
||||||
import extracted._
|
def helpBuild(uri: URI, build: LoadedBuildUnit): String =
|
||||||
def helpBuild(uri: URI, build: LoadedBuildUnit): String =
|
{
|
||||||
{
|
val pluginStrings = for(plugin <- availableAutoPlugins(build)) yield {
|
||||||
val pluginStrings = for(plugin <- availableAutoPlugins(build)) yield {
|
val activatedIn = build.defined.values.toList.filter(_.autoPlugins.contains(plugin)).map(_.id)
|
||||||
val activatedIn = build.defined.values.toList.filter(_.autoPlugins.contains(plugin)).map(_.id)
|
val actString = if(activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "") else "" // TODO: deal with large builds
|
||||||
val actString = if(activatedIn.nonEmpty) activatedIn.mkString(": enabled in ", ", ", "") else "" // TODO: deal with large builds
|
s"\n\t${plugin.label}$actString"
|
||||||
s"\n\t${plugin.label}$actString"
|
}
|
||||||
}
|
s"In $uri${pluginStrings.mkString}"
|
||||||
s"In $uri${pluginStrings.mkString}"
|
}
|
||||||
}
|
val buildStrings = for((uri, build) <- structure.units) yield helpBuild(uri, build)
|
||||||
val buildStrings = for((uri, build) <- structure.units) yield helpBuild(uri, build)
|
buildStrings.mkString("\n")
|
||||||
buildStrings.mkString("\n")
|
}
|
||||||
}
|
else "No project is currently loaded."
|
||||||
else
|
|
||||||
"No project is currently loaded."
|
|
||||||
|
|
||||||
def autoPluginMap(s: State): Map[String, AutoPlugin] =
|
def autoPluginMap(s: State): Map[String, AutoPlugin] =
|
||||||
{
|
{
|
||||||
val extracted = Project.extract(s)
|
val extracted = Project.extract(s)
|
||||||
import extracted._
|
import extracted._
|
||||||
structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap
|
structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap
|
||||||
}
|
}
|
||||||
private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] =
|
private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] =
|
||||||
build.unit.plugins.detected.autoPlugins map {_.value}
|
build.unit.plugins.detected.autoPlugins map {_.value}
|
||||||
|
|
||||||
def help(plugin: AutoPlugin, s: State): String =
|
def help(plugin: AutoPlugin, s: State): String =
|
||||||
{
|
{
|
||||||
val extracted = Project.extract(s)
|
val extracted = Project.extract(s)
|
||||||
import extracted._
|
import extracted._
|
||||||
def definesPlugin(p: ResolvedProject): Boolean = p.autoPlugins.contains(plugin)
|
def definesPlugin(p: ResolvedProject): Boolean = p.autoPlugins.contains(plugin)
|
||||||
def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref)
|
def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref)
|
||||||
val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet)
|
val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet)
|
||||||
val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList
|
val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList
|
||||||
lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.deducer(pluginsThisBuild), pluginsThisBuild, s.log)
|
lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.deducer(pluginsThisBuild), pluginsThisBuild, s.log)
|
||||||
lazy val debug = PluginsDebug(context.available)
|
lazy val debug = PluginsDebug(context.available)
|
||||||
if(!pluginsThisBuild.contains(plugin)) {
|
if(!pluginsThisBuild.contains(plugin)) {
|
||||||
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
|
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
|
||||||
s"Plugin ${plugin.label} is only available in builds:\n\t${availableInBuilds.mkString("\n\t")}\nSwitch to a project in one of those builds using `project` and rerun this command for more information."
|
s"Plugin ${plugin.label} is only available in builds:\n\t${availableInBuilds.mkString("\n\t")}\nSwitch to a project in one of those builds using `project` and rerun this command for more information."
|
||||||
} else if(definesPlugin(currentProject))
|
} else if(definesPlugin(currentProject))
|
||||||
debug.activatedHelp(plugin)
|
debug.activatedHelp(plugin)
|
||||||
else {
|
else {
|
||||||
val thisAggregated = BuildUtil.dependencies(structure.units).aggregateTransitive.getOrElse(currentRef, Nil)
|
val thisAggregated = BuildUtil.dependencies(structure.units).aggregateTransitive.getOrElse(currentRef, Nil)
|
||||||
val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref)))
|
val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref)))
|
||||||
if(definedInAggregated.nonEmpty) {
|
if(definedInAggregated.nonEmpty) {
|
||||||
val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified
|
val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified
|
||||||
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t${projectNames.mkString("\n\t")}"
|
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t${projectNames.mkString("\n\t")}"
|
||||||
} else {
|
} else {
|
||||||
val base = debug.deactivatedHelp(plugin, context)
|
val base = debug.deactivatedHelp(plugin, context)
|
||||||
val aggNote = if(thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This"
|
val aggNote = if(thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This"
|
||||||
val common = " information is for this project only."
|
val common = " information is for this project only."
|
||||||
val helpOther = "To see how to activate this plugin for another project, change to the project using `project <name>` and rerun this command."
|
val helpOther = "To see how to activate this plugin for another project, change to the project using `project <name>` and rerun this command."
|
||||||
s"$base\n$aggNote$common\n$helpOther"
|
s"$base\n$aggNote$common\n$helpOther"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Precomputes information for debugging plugins. */
|
/** Precomputes information for debugging plugins. */
|
||||||
def apply(available: List[AutoPlugin]): PluginsDebug =
|
def apply(available: List[AutoPlugin]): PluginsDebug =
|
||||||
{
|
{
|
||||||
val keyR = definedKeys(available)
|
val keyR = definedKeys(available)
|
||||||
val nameToKey: Map[String, AttributeKey[_]] = keyR._2s.toList.map(key => (key.label, key)).toMap
|
val nameToKey: Map[String, AttributeKey[_]] = keyR._2s.toList.map(key => (key.label, key)).toMap
|
||||||
new PluginsDebug(available, nameToKey, keyR)
|
new PluginsDebug(available, nameToKey, keyR)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The context for debugging a plugin (de)activation.
|
/** The context for debugging a plugin (de)activation.
|
||||||
* @param initial The initially defined [[AutoPlugin]]s.
|
* @param initial The initially defined [[AutoPlugin]]s.
|
||||||
* @param enabled The resulting model.
|
* @param enabled The resulting model.
|
||||||
* @param deducePlugin The function used to compute the model.
|
* @param deducePlugin The function used to compute the model.
|
||||||
* @param available All [[AutoPlugin]]s available for consideration. */
|
* @param available All [[AutoPlugin]]s available for consideration. */
|
||||||
final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], deducePlugin: (Plugins, Logger) => Seq[AutoPlugin], available: List[AutoPlugin], log: Logger)
|
final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], deducePlugin: (Plugins, Logger) => Seq[AutoPlugin], available: List[AutoPlugin], log: Logger)
|
||||||
|
|
||||||
/** Describes the steps to activate a plugin in some context. */
|
/** Describes the steps to activate a plugin in some context. */
|
||||||
sealed abstract class PluginEnable
|
sealed abstract class PluginEnable
|
||||||
/** Describes a [[plugin]] that is already activated in the [[context]].*/
|
/** Describes a [[plugin]] that is already activated in the [[context]].*/
|
||||||
final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable
|
final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable
|
||||||
sealed abstract class EnableDeactivated extends PluginEnable
|
sealed abstract class EnableDeactivated extends PluginEnable
|
||||||
/** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */
|
/** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */
|
||||||
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated
|
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated
|
||||||
|
|
||||||
/** Describes the requirements for activating [[plugin]] in [[context]].
|
/** Describes the requirements for activating [[plugin]] in [[context]].
|
||||||
* @param context The base plugins, exclusions, and ultimately activated plugins
|
* @param context The base plugins, exclusions, and ultimately activated plugins
|
||||||
* @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped
|
* @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped
|
||||||
* @param enablingPlugins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate
|
* @param enablingPlugins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate
|
||||||
* @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating, but are not required for [[plugin]] to activate
|
* @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating, but are not required for [[plugin]] to activate
|
||||||
* @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating
|
* @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating
|
||||||
* @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate. These require an explicit exclusion or dropping a transitive [[AutoPlugin]].*/
|
* @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate. These require an explicit exclusion or dropping a transitive [[AutoPlugin]].*/
|
||||||
final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingPlugins: Set[AutoPlugin], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated
|
final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingPlugins: Set[AutoPlugin], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated
|
||||||
|
|
||||||
/** Describes a [[plugin]] that must be removed in order to activate another plugin in some context.
|
/** Describes a [[plugin]] that must be removed in order to activate another plugin in some context.
|
||||||
* The [[plugin]] can always be directly, explicitly excluded.
|
* The [[plugin]] can always be directly, explicitly excluded.
|
||||||
* @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without affecting the other plugin. If empty, a direct exclusion is required.
|
* @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without affecting the other plugin. If empty, a direct exclusion is required.
|
||||||
* @param newlySelected If false, this plugin was selected in the original context. */
|
* @param newlySelected If false, this plugin was selected in the original context. */
|
||||||
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean)
|
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean)
|
||||||
|
|
||||||
/** Determines how to enable [[plugin]] in [[context]]. */
|
/** Determines how to enable [[plugin]] in [[context]]. */
|
||||||
def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable =
|
def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable =
|
||||||
if(context.enabled.contains(plugin))
|
if(context.enabled.contains(plugin))
|
||||||
PluginActivated(plugin, context)
|
PluginActivated(plugin, context)
|
||||||
else
|
else
|
||||||
enableDeactivated(context, plugin)
|
enableDeactivated(context, plugin)
|
||||||
|
|
||||||
private[this] def enableDeactivated(context: Context, plugin: AutoPlugin): PluginEnable =
|
private[this] def enableDeactivated(context: Context, plugin: AutoPlugin): PluginEnable = {
|
||||||
{
|
// deconstruct the context
|
||||||
// deconstruct the context
|
val initialModel = context.enabled.toSet
|
||||||
val initialModel = context.enabled.toSet
|
val initial = flatten(context.initial)
|
||||||
val initial = flatten(context.initial)
|
val initialPlugins = plugins(initial)
|
||||||
val initialPlugins = plugins(initial)
|
val initialExcludes = excludes(initial)
|
||||||
val initialExcludes = excludes(initial)
|
|
||||||
|
|
||||||
val minModel = minimalModel(plugin)
|
val minModel = minimalModel(plugin)
|
||||||
|
|
||||||
/* example 1
|
/* example 1
|
||||||
A :- B, not C
|
A :- B, not C
|
||||||
C :- D, E
|
C :- D, E
|
||||||
initial: B, D, E
|
initial: B, D, E
|
||||||
propose: drop D or E
|
propose: drop D or E
|
||||||
|
|
||||||
initial: B, not A
|
initial: B, not A
|
||||||
propose: drop 'not A'
|
propose: drop 'not A'
|
||||||
|
|
||||||
example 2
|
example 2
|
||||||
A :- B, not C
|
A :- B, not C
|
||||||
C :- B
|
C :- B
|
||||||
initial: <empty>
|
initial: <empty>
|
||||||
propose: B, exclude C
|
propose: B, exclude C
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// `plugin` will only be activated when all of these plugins are activated
|
// `plugin` will only be activated when all of these plugins are activated
|
||||||
// Deactivating any one of these would deactivate `plugin`.
|
// Deactivating any one of these would deactivate `plugin`.
|
||||||
val minRequiredPlugins = plugins(minModel)
|
val minRequiredPlugins = plugins(minModel)
|
||||||
|
|
||||||
// The presence of any one of these plugins would deactivate `plugin`
|
// The presence of any one of these plugins would deactivate `plugin`
|
||||||
val minAbsentPlugins = excludes(minModel).toSet
|
val minAbsentPlugins = excludes(minModel).toSet
|
||||||
|
|
||||||
// Plugins that must be both activated and deactivated for `plugin` to activate.
|
// Plugins that must be both activated and deactivated for `plugin` to activate.
|
||||||
// A non-empty list here cannot be satisfied and is an error.
|
// A non-empty list here cannot be satisfied and is an error.
|
||||||
val contradictions = minAbsentPlugins & minRequiredPlugins
|
val contradictions = minAbsentPlugins & minRequiredPlugins
|
||||||
|
|
||||||
if(contradictions.nonEmpty)
|
if(contradictions.nonEmpty) PluginImpossible(plugin, context, contradictions)
|
||||||
PluginImpossible(plugin, context, contradictions)
|
else {
|
||||||
else
|
// Plguins that the user has to add to the currently selected plugins in order to enable `plugin`.
|
||||||
{
|
val addToExistingPlugins = minRequiredPlugins -- initialPlugins
|
||||||
// Plguins that the user has to add to the currently selected plugins in order to enable `plugin`.
|
|
||||||
val addToExistingPlugins = minRequiredPlugins -- initialPlugins
|
|
||||||
|
|
||||||
// Plugins that are currently excluded that need to be allowed.
|
// Plugins that are currently excluded that need to be allowed.
|
||||||
val blockingExcludes = initialExcludes & minRequiredPlugins
|
val blockingExcludes = initialExcludes & minRequiredPlugins
|
||||||
|
|
||||||
// The model that results when the minimal plugins are enabled and the minimal plugins are excluded.
|
// The model that results when the minimal plugins are enabled and the minimal plugins are excluded.
|
||||||
// This can include more plugins than just `minRequiredPlugins` because the plguins required for `plugin`
|
// This can include more plugins than just `minRequiredPlugins` because the plguins required for `plugin`
|
||||||
// might activate other plugins as well.
|
// might activate other plugins as well.
|
||||||
val modelForMin = context.deducePlugin(and(includeAll(minRequiredPlugins), excludeAll(minAbsentPlugins)), context.log)
|
val modelForMin = context.deducePlugin(and(includeAll(minRequiredPlugins), excludeAll(minAbsentPlugins)), context.log)
|
||||||
|
|
||||||
val incrementalInputs = and( includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins))
|
val incrementalInputs = and( includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins))
|
||||||
val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet
|
val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet
|
||||||
|
|
||||||
// Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`.
|
// Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`.
|
||||||
// These could be excluded and `plugin` and the user's current plugins would still be activated.
|
// These could be excluded and `plugin` and the user's current plugins would still be activated.
|
||||||
val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel
|
val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel
|
||||||
|
|
||||||
// Plugins that will no longer be enabled as a result of enabling `plugin`.
|
// Plugins that will no longer be enabled as a result of enabling `plugin`.
|
||||||
val willRemove = initialModel -- incrementalModel
|
val willRemove = initialModel -- incrementalModel
|
||||||
|
|
||||||
// Determine the plugins that must be independently deactivated.
|
// Determine the plugins that must be independently deactivated.
|
||||||
// If both A and B must be deactivated, but A transitively depends on B, deactivating B will deactivate A.
|
// If both A and B must be deactivated, but A transitively depends on B, deactivating B will deactivate A.
|
||||||
// If A must be deactivated, but one if its (transitively) required plugins isn't present, it won't be activated.
|
// If A must be deactivated, but one if its (transitively) required plugins isn't present, it won't be activated.
|
||||||
// So, in either of these cases, A doesn't need to be considered further and won't be included in this set.
|
// So, in either of these cases, A doesn't need to be considered further and won't be included in this set.
|
||||||
val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel))
|
val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel))
|
||||||
|
|
||||||
val deactivate = for(d <- minDeactivate.toList) yield {
|
val deactivate = for(d <- minDeactivate.toList) yield {
|
||||||
// removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation.
|
// removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation.
|
||||||
val removeToDeactivate = plugins(minimalModel(d)) -- minRequiredPlugins
|
val removeToDeactivate = plugins(minimalModel(d)) -- minRequiredPlugins
|
||||||
val newlySelected = !initialModel(d)
|
val newlySelected = !initialModel(d)
|
||||||
// a. suggest removing a plugin in removeOneToDeactivate to deactivate d
|
// a. suggest removing a plugin in removeOneToDeactivate to deactivate d
|
||||||
// b. suggest excluding `d` to directly deactivate it in any case
|
// b. suggest excluding `d` to directly deactivate it in any case
|
||||||
// c. note whether d was already activated (in context.enabled) or is newly selected
|
// c. note whether d was already activated (in context.enabled) or is newly selected
|
||||||
DeactivatePlugin(d, removeToDeactivate, newlySelected)
|
DeactivatePlugin(d, removeToDeactivate, newlySelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginRequirements(plugin, context, blockingExcludes, addToExistingPlugins, extraPlugins, willRemove, deactivate)
|
PluginRequirements(plugin, context, blockingExcludes, addToExistingPlugins, extraPlugins, willRemove, deactivate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def includeAll[T <: Basic](basic: Set[T]): Plugins = And(basic.toList)
|
private[this] def includeAll[T <: Basic](basic: Set[T]): Plugins = And(basic.toList)
|
||||||
private[this] def excludeAll(plugins: Set[AutoPlugin]): Plugins = And(plugins map (p => Exclude(p)) toList)
|
private[this] def excludeAll(plugins: Set[AutoPlugin]): Plugins = And(plugins map (p => Exclude(p)) toList)
|
||||||
|
|
||||||
private[this] def excludes(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case Exclude(b) => b }.toSet
|
private[this] def excludes(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case Exclude(b) => b }.toSet
|
||||||
private[this] def plugins(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case n: AutoPlugin => n }.toSet
|
private[this] def plugins(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case n: AutoPlugin => n }.toSet
|
||||||
|
|
||||||
// If there is a model that includes `plugin`, it includes at least what is returned by this method.
|
// If there is a model that includes `plugin`, it includes at least what is returned by this method.
|
||||||
// This is the list of plugins that must be included as well as list of plugins that must not be present.
|
// This is the list of plugins that must be included as well as list of plugins that must not be present.
|
||||||
// It might not be valid, such as if there are contradictions or if there are cycles that are unsatisfiable.
|
// It might not be valid, such as if there are contradictions or if there are cycles that are unsatisfiable.
|
||||||
// The actual model might be larger, since other plugins might be enabled by the selected plugins.
|
// The actual model might be larger, since other plugins might be enabled by the selected plugins.
|
||||||
private[this] def minimalModel(plugin: AutoPlugin): Seq[Basic] = Dag.topologicalSortUnchecked(plugin: Basic) {
|
private[this] def minimalModel(plugin: AutoPlugin): Seq[Basic] = Dag.topologicalSortUnchecked(plugin: Basic) {
|
||||||
case _: Exclude => Nil
|
case _: Exclude => Nil
|
||||||
case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin
|
case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
/** String representation of [[PluginEnable]], intended for end users. */
|
/** String representation of [[PluginEnable]], intended for end users. */
|
||||||
def explainPluginEnable(ps: PluginEnable): String =
|
def explainPluginEnable(ps: PluginEnable): String =
|
||||||
ps match {
|
ps match {
|
||||||
case PluginRequirements(plugin, context, blockingExcludes, enablingPlugins, extraEnabledPlugins, toBeRemoved, deactivate) =>
|
case PluginRequirements(plugin, context, blockingExcludes, enablingPlugins, extraEnabledPlugins, toBeRemoved, deactivate) =>
|
||||||
def indent(str: String) = if(str.isEmpty) "" else s"\t$str"
|
def indent(str: String) = if(str.isEmpty) "" else s"\t$str"
|
||||||
def note(str: String) = if(str.isEmpty) "" else s"Note: $str"
|
def note(str: String) = if(str.isEmpty) "" else s"Note: $str"
|
||||||
val parts =
|
val parts =
|
||||||
indent(excludedError(false /* TODO */, blockingExcludes.toList)) ::
|
indent(excludedError(false /* TODO */, blockingExcludes.toList)) ::
|
||||||
indent(required(enablingPlugins.toList)) ::
|
indent(required(enablingPlugins.toList)) ::
|
||||||
indent(needToDeactivate(deactivate)) ::
|
indent(needToDeactivate(deactivate)) ::
|
||||||
note(willAdd(plugin, extraEnabledPlugins.toList)) ::
|
note(willAdd(plugin, extraEnabledPlugins.toList)) ::
|
||||||
note(willRemove(plugin, toBeRemoved.toList)) ::
|
note(willRemove(plugin, toBeRemoved.toList)) ::
|
||||||
Nil
|
Nil
|
||||||
parts.filterNot(_.isEmpty).mkString("\n")
|
parts.filterNot(_.isEmpty).mkString("\n")
|
||||||
case PluginImpossible(plugin, context, contradictions) => pluginImpossible(plugin, contradictions)
|
case PluginImpossible(plugin, context, contradictions) => pluginImpossible(plugin, contradictions)
|
||||||
case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated."
|
case PluginActivated(plugin, context) => s"Plugin ${plugin.label} already activated."
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a [[Relation]] between plugins and the keys they potentially define.
|
/** Provides a [[Relation]] between plugins and the keys they potentially define.
|
||||||
* Because plugins can define keys in different scopes and keys can be overridden, this is not definitive.*/
|
* Because plugins can define keys in different scopes and keys can be overridden, this is not definitive.*/
|
||||||
def definedKeys(available: List[AutoPlugin]): Relation[AutoPlugin, AttributeKey[_]] =
|
def definedKeys(available: List[AutoPlugin]): Relation[AutoPlugin, AttributeKey[_]] =
|
||||||
{
|
{
|
||||||
def extractDefinedKeys(ss: Seq[Setting[_]]): Seq[AttributeKey[_]] =
|
def extractDefinedKeys(ss: Seq[Setting[_]]): Seq[AttributeKey[_]] =
|
||||||
ss.map(_.key.key)
|
ss.map(_.key.key)
|
||||||
def allSettings(p: AutoPlugin): Seq[Setting[_]] = p.projectSettings ++ p.buildSettings ++ p.globalSettings
|
def allSettings(p: AutoPlugin): Seq[Setting[_]] = p.projectSettings ++ p.buildSettings ++ p.globalSettings
|
||||||
val empty = Relation.empty[AutoPlugin, AttributeKey[_]]
|
val empty = Relation.empty[AutoPlugin, AttributeKey[_]]
|
||||||
(empty /: available)( (r,p) => r + (p, extractDefinedKeys(allSettings(p))) )
|
(empty /: available)( (r,p) => r + (p, extractDefinedKeys(allSettings(p))) )
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def excludedError(transitive: Boolean, dependencies: List[AutoPlugin]): String =
|
private[this] def excludedError(transitive: Boolean, dependencies: List[AutoPlugin]): String =
|
||||||
str(dependencies)(excludedPluginError(transitive), excludedPluginsError(transitive))
|
str(dependencies)(excludedPluginError(transitive), excludedPluginsError(transitive))
|
||||||
|
|
||||||
private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) =
|
private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) =
|
||||||
s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded."
|
s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded."
|
||||||
private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) =
|
private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) =
|
||||||
s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}"
|
s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}"
|
||||||
private[this] def transitiveString(transitive: Boolean) =
|
private[this] def transitiveString(transitive: Boolean) =
|
||||||
if(transitive) "(transitive) " else ""
|
if(transitive) "(transitive) " else ""
|
||||||
|
|
||||||
private[this] def required(plugins: List[AutoPlugin]): String =
|
private[this] def required(plugins: List[AutoPlugin]): String =
|
||||||
str(plugins)(requiredPlugin, requiredPlugins)
|
str(plugins)(requiredPlugin, requiredPlugins)
|
||||||
|
|
||||||
private[this] def requiredPlugin(plugin: AutoPlugin) =
|
private[this] def requiredPlugin(plugin: AutoPlugin) =
|
||||||
s"Required plugin ${plugin.label} not present."
|
s"Required plugin ${plugin.label} not present."
|
||||||
private[this] def requiredPlugins(plugins: List[AutoPlugin]) =
|
private[this] def requiredPlugins(plugins: List[AutoPlugin]) =
|
||||||
s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}"
|
s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}"
|
||||||
|
|
||||||
private[this] def str[A](list: List[A])(f: A => String, fs: List[A] => String): String = list match {
|
private[this] def str[A](list: List[A])(f: A => String, fs: List[A] => String): String =
|
||||||
case Nil => ""
|
list match {
|
||||||
case single :: Nil => f(single)
|
case Nil => ""
|
||||||
case _ => fs(list)
|
case single :: Nil => f(single)
|
||||||
}
|
case _ => fs(list)
|
||||||
|
}
|
||||||
|
|
||||||
private[this] def willAdd(base: AutoPlugin, plugins: List[AutoPlugin]): String =
|
private[this] def willAdd(base: AutoPlugin, plugins: List[AutoPlugin]): String =
|
||||||
str(plugins)(willAddPlugin(base), willAddPlugins(base))
|
str(plugins)(willAddPlugin(base), willAddPlugins(base))
|
||||||
|
|
||||||
private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) =
|
private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) =
|
||||||
s"Enabling ${base.label} will also enable ${plugin.label}"
|
s"Enabling ${base.label} will also enable ${plugin.label}"
|
||||||
private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
|
private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
|
||||||
s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}"
|
s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}"
|
||||||
|
|
||||||
private[this] def willRemove(base: AutoPlugin, plugins: List[AutoPlugin]): String =
|
private[this] def willRemove(base: AutoPlugin, plugins: List[AutoPlugin]): String =
|
||||||
str(plugins)(willRemovePlugin(base), willRemovePlugins(base))
|
str(plugins)(willRemovePlugin(base), willRemovePlugins(base))
|
||||||
|
|
||||||
private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) =
|
private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) =
|
||||||
s"Enabling ${base.label} will disable ${plugin.label}"
|
s"Enabling ${base.label} will disable ${plugin.label}"
|
||||||
private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
|
private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
|
||||||
s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}"
|
s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}"
|
||||||
|
|
||||||
private[this] def labels(plugins: List[AutoPlugin]): List[String] =
|
private[this] def labels(plugins: List[AutoPlugin]): List[String] =
|
||||||
plugins.map(_.label)
|
plugins.map(_.label)
|
||||||
|
|
||||||
private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String =
|
private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String =
|
||||||
str(deactivate)(deactivate1, deactivateN)
|
str(deactivate)(deactivate1, deactivateN)
|
||||||
private[this] def deactivateN(plugins: List[DeactivatePlugin]): String =
|
private[this] def deactivateN(plugins: List[DeactivatePlugin]): String =
|
||||||
plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "")
|
plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "")
|
||||||
private[this] def deactivate1(deactivate: DeactivatePlugin): String =
|
private[this] def deactivate1(deactivate: DeactivatePlugin): String =
|
||||||
s"Need to deactivate ${deactivateString(deactivate)}"
|
s"Need to deactivate ${deactivateString(deactivate)}"
|
||||||
private[this] def deactivateString(d: DeactivatePlugin): String =
|
private[this] def deactivateString(d: DeactivatePlugin): String =
|
||||||
{
|
{
|
||||||
val removePluginsString: String =
|
val removePluginsString: String =
|
||||||
d.removeOneOf.toList match {
|
d.removeOneOf.toList match {
|
||||||
case Nil => ""
|
case Nil => ""
|
||||||
case x :: Nil => s" or no longer include $x"
|
case x :: Nil => s" or no longer include $x"
|
||||||
case xs => s" or remove one of ${xs.mkString(", ")}"
|
case xs => s" or remove one of ${xs.mkString(", ")}"
|
||||||
}
|
}
|
||||||
s"${d.plugin.label}: directly exclude it${removePluginsString}"
|
s"${d.plugin.label}: directly exclude it${removePluginsString}"
|
||||||
}
|
}
|
||||||
|
|
||||||
private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String =
|
private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String =
|
||||||
str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin))
|
str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin))
|
||||||
|
|
||||||
private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String =
|
private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String =
|
||||||
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent. Please report the problem to the plugin's author."
|
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent. Please report the problem to the plugin's author."
|
||||||
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String =
|
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String =
|
||||||
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires these plugins to be both present and absent:\n\t${labels(contradictions).mkString("\n\t")}\nPlease report the problem to the plugin's author."
|
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires these plugins to be both present and absent:\n\t${labels(contradictions).mkString("\n\t")}\nPlease report the problem to the plugin's author."
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
[1488]: https://github.com/sbt/sbt/pull/1488
|
[1488]: https://github.com/sbt/sbt/pull/1488
|
||||||
[1489]: https://github.com/sbt/sbt/pull/1489
|
[1489]: https://github.com/sbt/sbt/pull/1489
|
||||||
[1494]: https://github.com/sbt/sbt/pull/1494
|
[1494]: https://github.com/sbt/sbt/pull/1494
|
||||||
|
[1516]: https://github.com/sbt/sbt/pull/1516
|
||||||
|
|
||||||
[@dansanduleac]: https://github.com/dansanduleac
|
[@dansanduleac]: https://github.com/dansanduleac
|
||||||
[@2m]: https://github.com/2m
|
[@2m]: https://github.com/2m
|
||||||
|
|
@ -87,6 +88,7 @@
|
||||||
- Allows keys defined inside `build.sbt` to be used from sbt shell. [#1059][1059]/[#1456][1456]
|
- Allows keys defined inside `build.sbt` to be used from sbt shell. [#1059][1059]/[#1456][1456]
|
||||||
- Updates internal Ivy instance to cache the results of dependency exclusion rules. [#1476][1476] by [@eed3si9n][@eed3si9n]
|
- Updates internal Ivy instance to cache the results of dependency exclusion rules. [#1476][1476] by [@eed3si9n][@eed3si9n]
|
||||||
- Adds `Resolver.jcenterRepo` and `Resolver.bintrayRepo(owner, repo)` to add Bintray easier. [#1405][1405] by [@evgeny-goldin][@evgeny-goldin]
|
- Adds `Resolver.jcenterRepo` and `Resolver.bintrayRepo(owner, repo)` to add Bintray easier. [#1405][1405] by [@evgeny-goldin][@evgeny-goldin]
|
||||||
|
- AutoPlugins with no requirements enabled by allRequirements can now be disable dby the user. [#1516][1516] by [@jsuereth]
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
|
|
@ -103,6 +105,7 @@
|
||||||
- Fixes `Scope.parseScopedKey`. [#1384][1384] by [@eed3si9n][@eed3si9n]
|
- Fixes `Scope.parseScopedKey`. [#1384][1384] by [@eed3si9n][@eed3si9n]
|
||||||
- Fixes `build.sbt` errors causing `ArrayIndexOutOfBoundsException` due to invalid source in position. [#1181][1181] by [@eed3si9n][@eed3si9n]
|
- Fixes `build.sbt` errors causing `ArrayIndexOutOfBoundsException` due to invalid source in position. [#1181][1181] by [@eed3si9n][@eed3si9n]
|
||||||
|
|
||||||
|
|
||||||
### Maven Central Repository defaults to HTTPS
|
### Maven Central Repository defaults to HTTPS
|
||||||
|
|
||||||
Thanks to Sonatype, HTTPS access to Maven Central Repository is available to public. This is now enabled by default, but if HTTP is required for some reason the following system properties can be used:
|
Thanks to Sonatype, HTTPS access to Maven Central Repository is available to public. This is now enabled by default, but if HTTP is required for some reason the following system properties can be used:
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ lazy val projH = project.enablePlugins(TopB)
|
||||||
lazy val projI = project.enablePlugins(TopC)
|
lazy val projI = project.enablePlugins(TopC)
|
||||||
|
|
||||||
|
|
||||||
|
// Tests that we can disable an auto-enabled root plugin
|
||||||
|
lazy val disableAutoNoRequirePlugin = project.disablePlugins(OrgPlugin)
|
||||||
|
|
||||||
|
|
||||||
disablePlugins(plugins.IvyPlugin)
|
disablePlugins(plugins.IvyPlugin)
|
||||||
|
|
||||||
check := {
|
check := {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue