Remove Natures from AutoPlugins feature.

* remove the notion of Natures from Autoplugins.
* Update tests to use AutoPlugins with no selection for inclusion.
* Rename exisitng Natures code to Plugins/PluginsDebug.
This commit is contained in:
Josh Suereth 2014-03-05 17:56:34 -05:00
parent 75282195f4
commit 7f8d21c2f1
9 changed files with 140 additions and 146 deletions

View File

@ -71,7 +71,7 @@ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Resolv
* In addition to auto-discovered [[Build]]s, this includes any auto-generated default [[Build]]s.
* @param projects The list of all [[Project]]s from all [[Build]]s.
* These projects have not yet been resolved, but they have had auto-plugins applied.
* In particular, each [[Project]]'s `autoPlugins` field is populated according to their configured `natures`
* In particular, each [[Project]]'s `autoPlugins` field is populated according to their configured `plugins`
* and their `settings` and `configurations` updated as appropriate.
* @param buildNames No longer used and will be deprecated once feasible.
*/
@ -99,8 +99,8 @@ final class DetectedPlugins(val plugins: DetectedModules[Plugin], val autoImport
/** Sequence of import expressions for the build definition. This includes the names of the [[Plugin]], [[Build]], and [[AutoImport]] modules, but not the [[AutoPlugin]] modules. */
lazy val imports: Seq[String] = BuildUtil.getImports(plugins.names ++ builds.names ++ autoImports.names)
/** A function to select the right [[AutoPlugin]]s from [[autoPlugins]] given the defined [[Natures]] for a [[Project]]. */
lazy val compileNatures: Natures => Seq[AutoPlugin] = Natures.compile(autoPlugins.values.toList)
/** A function to select the right [[AutoPlugin]]s from [[autoPlugins]] for a [[Project]]. */
lazy val compilePlugins: Plugins => Seq[AutoPlugin] = Plugins.compile(autoPlugins.values.toList)
}
/** The built and loaded build definition project.

View File

@ -463,7 +463,7 @@ object Load
loadSettings(auto, base, plugins, eval, injectSettings, memoSettings, autoPlugins)
def loadForProjects = newProjects map { project =>
val autoPlugins =
try plugins.detected.compileNatures(project.natures)
try plugins.detected.compilePlugins(project.plugins)
catch { case e: AutoPluginException => throw translateAutoPluginException(e, project) }
val autoConfigs = autoPlugins.flatMap(_.projectConfigurations)
val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins)

View File

@ -376,16 +376,16 @@ object BuiltinCommands
else
Help.empty
def plugins = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s =>
val helpString = NaturesDebug.helpAll(s)
val helpString = PluginsDebug.helpAll(s)
System.out.println(helpString)
s
}
val pluginParser: State => Parser[AutoPlugin] = s => {
val autoPlugins: Map[String, AutoPlugin] = NaturesDebug.autoPluginMap(s)
val autoPlugins: Map[String, AutoPlugin] = PluginsDebug.autoPluginMap(s)
token(Space) ~> Act.knownIDParser(autoPlugins, "plugin")
}
def plugin = Command(PluginCommand)(pluginParser) { (s, plugin) =>
val helpString = NaturesDebug.help(plugin, s)
val helpString = PluginsDebug.help(plugin, s)
System.out.println(helpString)
s
}

View File

@ -8,7 +8,7 @@ TODO:
import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated}
import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException}
import Def.Setting
import Natures._
import Plugins._
/** Marks a top-level object so that sbt will wildcard import it for .sbt files, `consoleProject`, and `set`. */
trait AutoImport
@ -18,11 +18,11 @@ An AutoPlugin defines a group of settings and the conditions where the settings
The `select` method defines the conditions and a method like `projectSettings` defines the settings to add.
Steps for plugin authors:
1. Determine the [[Nature]]s that, when present (or absent), activate the AutoPlugin.
1. Determine the [[AutoPlugins]]s that, when present (or absent), activate the AutoPlugin.
2. Determine the settings/configurations to automatically inject when activated.
For example, the following will automatically add the settings in `projectSettings`
to a project that has both the `Web` and `Javascript` natures enabled.
to a project that has both the `Web` and `Javascript` plugins enabled.
object MyPlugin extends AutoPlugin {
def select = Web && Javascript
@ -30,28 +30,28 @@ For example, the following will automatically add the settings in `projectSettin
}
Steps for users:
1. Add dependencies on plugins as usual with addSbtPlugin
2. Add Natures to Projects, which will automatically select the plugin settings to add for those Projects.
1. Add dependencies on plugins in `project/plugins.sbt` as usual with `addSbtPlugin`
2. Add key plugins to Projects, which will automatically select the plugin + dependent plugin settings to add for those Projects.
3. Exclude plugins, if desired.
For example, given natures Web and Javascript (perhaps provided by plugins added with addSbtPlugin),
For example, given plugins Web and Javascript (perhaps provided by plugins added with addSbtPlugin),
<Project>.natures( Web && Javascript )
<Project>.plugins( Web && Javascript )
will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines
<Project>.natures( Web && Javascript && !MyPlugin)
<Project>.plugins( Web && Javascript && !MyPlugin)
then the `MyPlugin` settings (and anything that activates only when `MyPlugin` is activated) will not be added.
*/
abstract class AutoPlugin extends Natures.Basic
abstract class AutoPlugin extends Plugins.Basic
{
/** This AutoPlugin will be activated for a project when the [[Natures]] matcher returned by this method matches that project's natures
* AND the user does not explicitly exclude the Nature returned by `provides`.
/** This AutoPlugin will be activated for a project when the [[Plugins]] matcher returned by this method matches that project's plugins
* AND the user does not explicitly exclude the Plugin returned by `provides`.
*
* For example, if this method returns `Web && Javascript`, this plugin instance will only be added
* if the `Web` and `Javascript` natures are enabled. */
def select: Natures
* if the `Web` and `Javascript` plugins are enabled. */
def select: Plugins
val label: String = getClass.getName.stripSuffix("$")
@ -84,26 +84,19 @@ final class AutoPluginException private(val message: String, val origin: Option[
object AutoPluginException
{
def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None)
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Natures.translateMessage(origin), Some(origin))
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin))
}
/** An expression that matches `Nature`s. */
sealed trait Natures {
def && (o: Basic): Natures
/** An expression that matches `AutoPlugin`s. */
sealed trait Plugins {
def && (o: Basic): Plugins
}
/** Represents a feature or conceptual group of settings.
* `label` is the unique ID for this nature. */
final case class Nature(label: String) extends Basic {
/** Constructs a Natures matcher that excludes this Nature. */
override def toString = label
}
object Natures
object Plugins
{
/** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[Nature]]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 selected [[AutoPlugin]] comes before its selecting [[AutoPlugin]].*/
def compile(defined: List[AutoPlugin]): Natures => Seq[AutoPlugin] =
def compile(defined: List[AutoPlugin]): Plugins => Seq[AutoPlugin] =
if(defined.isEmpty)
Types.const(Nil)
else
@ -112,8 +105,8 @@ object Natures
val byAtomMap = byAtom.toMap
if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom)
val clauses = Clauses( defined.map(d => asClause(d)) )
requestedNatures =>
Logic.reduce(clauses, flattenConvert(requestedNatures).toSet) match {
requestedPlugins =>
Logic.reduce(clauses, flattenConvert(requestedPlugins).toSet) match {
case Left(problem) => throw AutoPluginException(problem)
case Right(results) =>
// results includes the originally requested (positive) atoms,
@ -123,8 +116,8 @@ object Natures
}
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 ic: InitialContradictions => s"Contradiction in selected plugins. These plguins 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 =
@ -135,34 +128,36 @@ object Natures
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"Nature$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)
}
/** [[Natures]] instance that doesn't require any [[Nature]]s. */
def empty: Natures = Empty
private[sbt] final object Empty extends Natures {
def &&(o: Basic): Natures = o
/** [[Plugins]] instance that doesn't require any [[Plugins]]s. */
def empty: Plugins = Empty
private[sbt] final object Empty extends Plugins {
def &&(o: Basic): Plugins = o
override def toString = "<none>"
}
/** An included or excluded Nature. TODO: better name than Basic. */
sealed abstract class Basic extends Natures {
def &&(o: Basic): Natures = And(this :: o :: Nil)
/** An included or excluded Nature/Plugin. TODO: better name than Basic. Also, can we dump
* this class.
*/
sealed abstract class Basic extends Plugins {
def &&(o: Basic): Plugins = And(this :: o :: Nil)
}
private[sbt] final case class Exclude(n: AutoPlugin) extends Basic {
override def toString = s"!$n"
}
private[sbt] final case class And(natures: List[Basic]) extends Natures {
def &&(o: Basic): Natures = And(o :: natures)
override def toString = natures.mkString(", ")
private[sbt] final case class And(plugins: List[Basic]) extends Plugins {
def &&(o: Basic): Plugins = And(o :: plugins)
override def toString = plugins.mkString(", ")
}
private[sbt] def and(a: Natures, b: Natures) = b match {
private[sbt] def and(a: Plugins, b: Plugins) = b match {
case Empty => a
case And(ns) => (a /: ns)(_ && _)
case b: Basic => a && b
}
private[sbt] def remove(a: Natures, del: Set[Basic]): Natures = a match {
private[sbt] def remove(a: Plugins, del: Set[Basic]): Plugins = a match {
case b: Basic => if(del(b)) Empty else b
case Empty => Empty
case And(ns) =>
@ -170,38 +165,36 @@ object Natures
if(removed.isEmpty) Empty else And(removed)
}
/** Defines a clause for `ap` such that the [[Nature]] provided by `ap` is the head and the selector for `ap` is the body. */
/** Defines a clause for `ap` such that the [[AutPlugin]] provided by `ap` is the head and the selector for `ap` is the body. */
private[sbt] def asClause(ap: AutoPlugin): Clause =
Clause( convert(ap.select), Set(Atom(ap.label)) )
private[this] def flattenConvert(n: Natures): Seq[Literal] = n match {
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: Natures): Seq[Basic] = n match {
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: Natures): Formula = n match {
private[this] def convert(n: Plugins): Formula = n match {
case And(ns) => convertAll(ns).reduce[Formula](_ && _)
case b: Basic => convertBasic(b)
case Empty => Formula.True
}
private[this] def convertBasic(b: Basic): Literal = b match {
case Exclude(n) => !convertBasic(n)
case Nature(s) => Atom(s)
case a: AutoPlugin => Atom(a.label)
}
private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic
/** True if the select clause `n` is satisifed by `model`. */
def satisfied(n: Natures, model: Set[AutoPlugin], natures: Set[Nature]): Boolean =
def satisfied(n: Plugins, model: Set[AutoPlugin]): Boolean =
flatten(n) forall {
case Exclude(a) => !model(a)
case n: Nature => natures(n)
case ap: AutoPlugin => model(ap)
}
}

View File

@ -1,11 +1,11 @@
package sbt
import Def.Setting
import Natures._
import NaturesDebug._
import Plugins._
import PluginsDebug._
import java.net.URI
private[sbt] class NaturesDebug(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`.
* Because plugins can define keys in different scopes, this should only be used as a guideline. */
@ -79,7 +79,7 @@ private[sbt] class NaturesDebug(val available: List[AutoPlugin], val nameToKey:
private[this] def multi(strs: Seq[String]): String = strs.mkString(if(strs.size > 4) "\n\t" else ", ")
}
private[sbt] object NaturesDebug
private[sbt] object PluginsDebug
{
def helpAll(s: State): String =
if(Project.isProjectLoaded(s))
@ -118,8 +118,8 @@ private[sbt] object NaturesDebug
def projectForRef(ref: ProjectRef): ResolvedProject = get(Keys.thisProject in ref)
val perBuild: Map[URI, Set[AutoPlugin]] = structure.units.mapValues(unit => availableAutoPlugins(unit).toSet)
val pluginsThisBuild = perBuild.getOrElse(currentRef.build, Set.empty).toList
lazy val context = Context(currentProject.natures, currentProject.autoPlugins, Natures.compile(pluginsThisBuild), pluginsThisBuild)
lazy val debug = NaturesDebug(context.available)
lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.compile(pluginsThisBuild), pluginsThisBuild)
lazy val debug = PluginsDebug(context.available)
if(!pluginsThisBuild.contains(plugin)) {
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."
@ -141,20 +141,20 @@ private[sbt] object NaturesDebug
}
}
/** Precomputes information for debugging natures and plugins. */
def apply(available: List[AutoPlugin]): NaturesDebug =
/** Precomputes information for debugging plugins. */
def apply(available: List[AutoPlugin]): PluginsDebug =
{
val keyR = definedKeys(available)
val nameToKey: Map[String, AttributeKey[_]] = keyR._2s.toList.map(key => (key.label, key)).toMap
new NaturesDebug(available, nameToKey, keyR)
new PluginsDebug(available, nameToKey, keyR)
}
/** The context for debugging a plugin (de)activation.
* @param initial The initially defined [[Nature]]s.
* @param initial The initially defined [[AutoPlugin]]s.
* @param enabled The resulting model.
* @param compile The function used to compute the model.
* @param available All [[AutoPlugin]]s available for consideration. */
final case class Context(initial: Natures, enabled: Seq[AutoPlugin], compile: Natures => Seq[AutoPlugin], available: List[AutoPlugin])
final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], compile: Plugins => Seq[AutoPlugin], available: List[AutoPlugin])
/** Describes the steps to activate a plugin in some context. */
sealed abstract class PluginEnable
@ -165,19 +165,19 @@ private[sbt] object NaturesDebug
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated
/** Describes the requirements for activating [[plugin]] in [[context]].
* @param context The base natures, exclusions, and ultimately activated plugins
* @param context The base plguins, exclusions, and ultimately activated plugins
* @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped
* @param enablingNatures [[Nature]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate
* @param enablingPlguins [[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 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 [[Nature]].*/
final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingNatures: Set[Nature], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated
* @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
/** Describes a [[plugin]] that must be removed in order to activate another plugin in some context.
* The [[plugin]] can always be directly, explicitly excluded.
* @param removeOneOf If non-empty, removing one of these [[Nature]]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. */
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[Nature], newlySelected: Boolean)
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean)
/** Determines how to enable [[plugin]] in [[context]]. */
def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable =
@ -191,7 +191,7 @@ private[sbt] object NaturesDebug
// deconstruct the context
val initialModel = context.enabled.toSet
val initial = flatten(context.initial)
val initialNatures = natures(initial)
val initialPlugins = plugins(initial)
val initialExcludes = excludes(initial)
val minModel = minimalModel(plugin)
@ -212,13 +212,9 @@ private[sbt] object NaturesDebug
propose: B, exclude C
*/
// `plugin` will only be activated when all of these natures are activated
// Deactivating any one of these would deactivate `plugin`.
val minRequiredNatures = natures(minModel)
// `plugin` will only be activated when all of these plugins are activated
// Deactivating any one of these would deactivate `plugin`.
val minRequiredPlugins = minModel.collect{ case a: AutoPlugin => a }.toSet
val minRequiredPlugins = plugins(minModel)
// The presence of any one of these plugins would deactivate `plugin`
val minAbsentPlugins = excludes(minModel).toSet
@ -231,21 +227,21 @@ private[sbt] object NaturesDebug
PluginImpossible(plugin, context, contradictions)
else
{
// Natures that the user has to add to the currently selected natures in order to enable `plugin`.
val addToExistingNatures = minRequiredNatures -- initialNatures
// 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.
val blockingExcludes = initialExcludes & minRequiredPlugins
// The model that results when the minimal natures are enabled and the minimal plugins are excluded.
// This can include more plugins than just `minRequiredPlugins` because the natures required for `plugin`
// 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`
// might activate other plugins as well.
val modelForMin = context.compile(and(includeAll(minRequiredNatures), excludeAll(minAbsentPlugins)))
val modelForMin = context.compile(and(includeAll(minRequiredPlugins), excludeAll(minAbsentPlugins)))
val incrementalInputs = and( includeAll(minRequiredNatures ++ initialNatures), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins))
val incrementalInputs = and( includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins))
val incrementalModel = context.compile(incrementalInputs).toSet
// Plugins that are newly enabled as a result of selecting the natures 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.
val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel
@ -254,48 +250,48 @@ private[sbt] object NaturesDebug
// 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 A must be deactivated, but one if its (transitively) required natures 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.
val minDeactivate = minAbsentPlugins.filter(p => Natures.satisfied(p.select, incrementalModel, natures(flatten(incrementalInputs))))
val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.select, incrementalModel))
val deactivate = for(d <- minDeactivate.toList) yield {
// removing any one of these natures will deactivate `d`. TODO: This is not an especially efficient implementation.
val removeToDeactivate = natures(minimalModel(d)) -- minRequiredNatures
// removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation.
val removeToDeactivate = plugins(minimalModel(d)) -- minRequiredPlugins
val newlySelected = !initialModel(d)
// a. suggest removing a nature 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
// c. note whether d was already activated (in context.enabled) or is newly selected
DeactivatePlugin(d, removeToDeactivate, newlySelected)
}
PluginRequirements(plugin, context, blockingExcludes, addToExistingNatures, extraPlugins, willRemove, deactivate)
PluginRequirements(plugin, context, blockingExcludes, addToExistingPlugins, extraPlugins, willRemove, deactivate)
}
}
private[this] def includeAll[T <: Basic](basic: Set[T]): Natures = And(basic.toList)
private[this] def excludeAll(plugins: Set[AutoPlugin]): Natures = And(plugins map (p => Exclude(p)) 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 excludes(bs: Seq[Basic]): Set[AutoPlugin] = bs.collect { case Exclude(b) => b }.toSet
private[this] def natures(bs: Seq[Basic]): Set[Nature] = bs.collect { case n: Nature => 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.
// This is the list of natures and 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.
// The actual model might be larger, since other plugins might be enabled by the selected natures.
// 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) {
case _: Exclude | _: Nature => Nil
case ap: AutoPlugin => Natures.flatten(ap.select)
case _: Exclude => Nil
case ap: AutoPlugin => Plugins.flatten(ap.select)
}
/** String representation of [[PluginEnable]], intended for end users. */
def explainPluginEnable(ps: PluginEnable): String =
ps match {
case PluginRequirements(plugin, context, blockingExcludes, enablingNatures, extraEnabledPlugins, toBeRemoved, deactivate) =>
case PluginRequirements(plugin, context, blockingExcludes, enablingPlugins, extraEnabledPlugins, toBeRemoved, deactivate) =>
def indent(str: String) = if(str.isEmpty) "" else s"\t$str"
def note(str: String) = if(str.isEmpty) "" else s"Note: $str"
val parts =
indent(excludedError(false /* TODO */, blockingExcludes.toList)) ::
indent(required(enablingNatures.toList)) ::
indent(required(enablingPlugins.toList)) ::
indent(needToDeactivate(deactivate)) ::
note(willAdd(plugin, extraEnabledPlugins.toList)) ::
note(willRemove(plugin, toBeRemoved.toList)) ::
@ -326,13 +322,13 @@ private[sbt] object NaturesDebug
private[this] def transitiveString(transitive: Boolean) =
if(transitive) "(transitive) " else ""
private[this] def required(natures: List[Nature]): String =
str(natures)(requiredNature, requiredNatures)
private[this] def required(plugins: List[AutoPlugin]): String =
str(plugins)(requiredPlugin, requiredPlugins)
private[this] def requiredNature(nature: Nature) =
s"Required nature ${nature.label} not present."
private[this] def requiredNatures(natures: List[Nature]) =
s"Required natures not present:\n\t${natures.map(_.label).mkString("\n\t")}"
private[this] def requiredPlugin(plugin: AutoPlugin) =
s"Required plugin ${plugin.label} not present."
private[this] def requiredPlugins(plugins: List[AutoPlugin]) =
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 {
case Nil => ""
@ -367,13 +363,13 @@ private[sbt] object NaturesDebug
s"Need to deactivate ${deactivateString(deactivate)}"
private[this] def deactivateString(d: DeactivatePlugin): String =
{
val removeNaturesString: String =
val removePluginsString: String =
d.removeOneOf.toList match {
case Nil => ""
case x :: Nil => s" or no longer include $x"
case xs => s" or remove one of ${xs.mkString(", ")}"
}
s"${d.plugin.label}: directly exclude it${removeNaturesString}"
s"${d.plugin.label}: directly exclude it${removePluginsString}"
}
private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String =

View File

@ -50,9 +50,9 @@ sealed trait ProjectDefinition[PR <: ProjectReference]
/** Configures the sources of automatically appended settings.*/
def auto: AddSettings
/** The [[Natures]] associated with this project.
A [[Nature]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def natures: Natures
/** The defined [[Plugins]] associated with this project.
A [[AutoPlguin]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def plugins: Plugins
/** The [[AutoPlugin]]s enabled for this project. This value is only available on a loaded Project. */
private[sbt] def autoPlugins: Seq[AutoPlugin]
@ -68,18 +68,18 @@ sealed trait ProjectDefinition[PR <: ProjectReference]
val dep = ifNonEmpty("dependencies", dependencies)
val conf = ifNonEmpty("configurations", configurations)
val autos = ifNonEmpty("autoPlugins", autoPlugins.map(_.label))
val fields = s"id $id" :: s"base: $base" :: agg ::: dep ::: conf ::: (s"natures: List($natures)" :: autos)
val fields = s"id $id" :: s"base: $base" :: agg ::: dep ::: conf ::: (s"plugins: List($plugins)" :: autos)
s"Project(${fields.mkString(", ")})"
}
private[this] def ifNonEmpty[T](label: String, ts: Iterable[T]): List[String] = if(ts.isEmpty) Nil else s"$label: $ts" :: Nil
}
sealed trait Project extends ProjectDefinition[ProjectReference]
{
// TODO: add parameters for natures and autoPlugins in 0.14.0 (not reasonable to do in a binary compatible way in 0.13)
// TODO: add parameters for plugins in 0.14.0 (not reasonable to do in a binary compatible way in 0.13)
def copy(id: String = id, base: File = base, aggregate: => Seq[ProjectReference] = aggregate, dependencies: => Seq[ClasspathDep[ProjectReference]] = dependencies,
delegates: => Seq[ProjectReference] = delegates, settings: => Seq[Setting[_]] = settings, configurations: Seq[Configuration] = configurations,
auto: AddSettings = auto): Project =
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, natures, autoPlugins)
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autoPlugins)
def resolve(resolveRef: ProjectReference => ProjectRef): ResolvedProject =
{
@ -87,7 +87,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference]
def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep
def resolveDep(d: ClasspathDep[ProjectReference]) = ResolvedClasspathDependency(resolveRef(d.project), d.configuration)
resolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates),
settings, configurations, auto, natures, autoPlugins)
settings, configurations, auto, plugins, autoPlugins)
}
def resolveBuild(resolveRef: ProjectReference => ProjectReference): Project =
{
@ -95,7 +95,7 @@ sealed trait Project extends ProjectDefinition[ProjectReference]
def resolveDeps(ds: Seq[ClasspathDep[ProjectReference]]) = ds map resolveDep
def resolveDep(d: ClasspathDep[ProjectReference]) = ClasspathDependency(resolveRef(d.project), d.configuration)
unresolved(id, base, aggregate = resolveRefs(aggregate), dependencies = resolveDeps(dependencies), delegates = resolveRefs(delegates),
settings, configurations, auto, natures, autoPlugins)
settings, configurations, auto, plugins, autoPlugins)
}
/** Applies the given functions to this Project.
@ -136,27 +136,27 @@ sealed trait Project extends ProjectDefinition[ProjectReference]
* Any configured .sbt files are removed from this project's list.*/
def setSbtFiles(files: File*): Project = copy(auto = AddSettings.append( AddSettings.clearSbtFiles(auto), AddSettings.sbtFiles(files: _*)) )
/** Sets the [[Nature]]s of this project.
A [[Nature]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def addNatures(ns: Nature*): Project = setNatures(Natures.and(natures, Natures.And(ns.toList)))
/** Sets the [[AutoPlugin]]s of this project.
A [[AutoPlugin]] is a common label that is used by plugins to determine what settings, if any, to add to a project. */
def addPlugins(ns: AutoPlugin*): Project = setPlugins(Plugins.and(plugins, Plugins.And(ns.toList)))
/** Disable the given plugins on this project. */
def disablePlugins(plugins: AutoPlugin*): Project =
setNatures(Natures.and(natures, Natures.And(plugins.map(p => Natures.Exclude(p)).toList)))
def disablePlugins(ps: AutoPlugin*): Project =
setPlugins(Plugins.and(plugins, Plugins.And(ps.map(p => Plugins.Exclude(p)).toList)))
private[this] def setNatures(ns: Natures): Project = {
// TODO: for 0.14.0, use copy when it has the additional `natures` parameter
private[this] def setPlugins(ns: Plugins): Project = {
// TODO: for 0.14.0, use copy when it has the additional `plugins` parameter
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, ns, autoPlugins)
}
/** Definitively set the [[AutoPlugin]]s for this project. */
private[sbt] def setAutoPlugins(autos: Seq[AutoPlugin]): Project = {
// TODO: for 0.14.0, use copy when it has the additional `autoPlugins` parameter
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, natures, autos)
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, plugins, autos)
}
}
sealed trait ResolvedProject extends ProjectDefinition[ProjectRef] {
/** The [[AutoPlugin]]s enabled for this project as computed from [[natures]].*/
/** The [[AutoPlugin]]s enabled for this project as computed from [[plugins]].*/
def autoPlugins: Seq[AutoPlugin]
}
@ -192,7 +192,7 @@ object Project extends ProjectExtra
private abstract class ProjectDef[PR <: ProjectReference](val id: String, val base: File, aggregate0: => Seq[PR], dependencies0: => Seq[ClasspathDep[PR]],
delegates0: => Seq[PR], settings0: => Seq[Def.Setting[_]], val configurations: Seq[Configuration], val auto: AddSettings,
val natures: Natures, val autoPlugins: Seq[AutoPlugin]) extends ProjectDefinition[PR]
val plugins: Plugins, val autoPlugins: Seq[AutoPlugin]) extends ProjectDefinition[PR]
{
lazy val aggregate = aggregate0
lazy val dependencies = dependencies0
@ -202,11 +202,11 @@ object Project extends ProjectExtra
Dag.topologicalSort(configurations)(_.extendsConfigs) // checks for cyclic references here instead of having to do it in Scope.delegates
}
// TODO: add parameter for natures in 0.14.0
// TODO: add parameter for plugins in 0.14.0
def apply(id: String, base: File, aggregate: => Seq[ProjectReference] = Nil, dependencies: => Seq[ClasspathDep[ProjectReference]] = Nil,
delegates: => Seq[ProjectReference] = Nil, settings: => Seq[Def.Setting[_]] = defaultSettings, configurations: Seq[Configuration] = Configurations.default,
auto: AddSettings = AddSettings.allDefaults): Project =
unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Natures.empty, Nil)
unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil)
/** Returns None if `id` is a valid Project ID or Some containing the parser error message if it is not.*/
def validProjectID(id: String): Option[String] = DefaultParsers.parse(id, DefaultParsers.ID).left.toOption
@ -228,19 +228,19 @@ object Project extends ProjectExtra
@deprecated("Will be removed.", "0.13.2")
def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ResolvedClasspathDependency], delegates: => Seq[ProjectRef],
settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings): ResolvedProject =
resolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Natures.empty, Nil)
resolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil)
private def resolved(id: String, base: File, aggregate: => Seq[ProjectRef], dependencies: => Seq[ClasspathDep[ProjectRef]],
delegates: => Seq[ProjectRef], settings: Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings,
natures: Natures, autoPlugins: Seq[AutoPlugin]): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto, natures, autoPlugins) with ResolvedProject
plugins: Plugins, autoPlugins: Seq[AutoPlugin]): ResolvedProject =
new ProjectDef[ProjectRef](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins) with ResolvedProject
private def unresolved(id: String, base: File, aggregate: => Seq[ProjectReference], dependencies: => Seq[ClasspathDep[ProjectReference]],
delegates: => Seq[ProjectReference], settings: => Seq[Def.Setting[_]], configurations: Seq[Configuration], auto: AddSettings,
natures: Natures, autoPlugins: Seq[AutoPlugin]): Project =
plugins: Plugins, autoPlugins: Seq[AutoPlugin]): Project =
{
validProjectID(id).foreach(errMsg => sys.error("Invalid project ID: " + errMsg))
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto, natures, autoPlugins) with Project
new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins) with Project
}
def defaultSettings: Seq[Def.Setting[_]] = Defaults.defaultSettings

View File

@ -1,11 +1,11 @@
// excludePlugins(C) will prevent C, and thus D, from being auto-added
lazy val a = project.addNatures(A, B).disablePlugins(Q)
lazy val a = project.addPlugins(A, B).disablePlugins(Q)
// without B, C is not added
lazy val b = project.addNatures(A)
lazy val b = project.addPlugins(A)
// with both A and B, C is selected, which in turn selects D
lazy val c = project.addNatures(A, B)
lazy val c = project.addPlugins(A, B)
// with no natures defined, nothing is auto-added
lazy val d = project

View File

@ -4,9 +4,12 @@
object AI extends AutoImport
{
lazy val A = Nature("A")
lazy val B = Nature("B")
lazy val E = Nature("E")
trait EmptyAutoPlugin extends AutoPlugin {
def select = Plugins.empty
}
object A extends EmptyAutoPlugin
object B extends EmptyAutoPlugin
object E extends EmptyAutoPlugin
lazy val q = config("q")
lazy val p = config("p").extend(q)
@ -20,12 +23,12 @@ object AI extends AutoImport
import AI._
object D extends AutoPlugin {
def select: Natures = E
def select: Plugins = E
}
object Q extends AutoPlugin
{
def select: Natures = A && B
def select: Plugins = A && B
override def projectConfigurations: Seq[Configuration] =
p ::

View File

@ -3,7 +3,9 @@ import Keys._
object C extends AutoImport {
lazy val bN = Nature("B")
object bN extends AutoPlugin {
def select = Plugins.empty
}
lazy val check = taskKey[Unit]("Checks that the AutoPlugin and Build are automatically added.")
}
@ -17,5 +19,5 @@ object A extends AutoPlugin {
}
object B extends Build {
lazy val extra = project.addNatures(bN)
lazy val extra = project.addPlugins(bN)
}