mirror of https://github.com/sbt/sbt.git
Restructure Natures/AutoPlugin types
- remove AutoPlugin.provides * name comes from module name * AutoPlugin is Nature-like via Basic - Project.addNatures only accepts varags of Nature values * enforces that a user cannot explicitly enable an AutoPlugin * drops need for && and - combinators - Project.excludeNatures accepts varags of AutoPlugin values * enforces that only AutoPlugins can be excluded * drops need for && and - combinators
This commit is contained in:
parent
e037731d81
commit
49bf842b3d
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
TODO:
|
||||
- Natured type contains AutoPlugin and Nature
|
||||
- atoms of AutoPlugin.select are Natured
|
||||
- atoms of Project.natures are Nature
|
||||
- no more AutoPlugin.provides: name comes from module name
|
||||
- index all available AutoPlugins to get the tasks that will be added
|
||||
- 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
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import Def.Setting
|
||||
import logic.Logic.LogicException
|
||||
|
||||
/** Marks a top-level object so that sbt will wildcard import it for .sbt files, `consoleProject`, and `set`. */
|
||||
trait AutoImport
|
||||
|
||||
/**
|
||||
An AutoPlugin defines a group of settings and the conditions where the settings are automatically added to a build (called "activation").
|
||||
The `select` method defines the conditions,
|
||||
`provides` defines an identifier for the AutoPlugin,
|
||||
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.
|
||||
2. Determine the settings/configurations to automatically inject when activated.
|
||||
3. Define a new, unique identifying [[Nature]] associated with the AutoPlugin, where a Nature is essentially a String ID.
|
||||
|
||||
For example, the following will automatically add the settings in `projectSettings`
|
||||
to a project that has both the `Web` and `Javascript` natures enabled. It will itself
|
||||
define the `MyStuff` nature. This nature can be explicitly disabled by the user to
|
||||
prevent the plugin from activating.
|
||||
|
||||
object MyPlugin extends AutoPlugin {
|
||||
def select = Web && Javascript
|
||||
def provides = MyStuff
|
||||
override def projectSettings = Seq(...)
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
For example, given natures Web and Javascript (perhaps provided by plugins added with addSbtPlugin),
|
||||
|
||||
<Project>.natures( Web && Javascript )
|
||||
|
||||
will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines
|
||||
|
||||
<Project>.natures( Web && Javascript && !MyStuff)
|
||||
|
||||
then the `MyPlugin` settings (and anything that activates only when `MyStuff` is activated) will not be added.
|
||||
*/
|
||||
abstract class AutoPlugin
|
||||
{
|
||||
/** 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`.
|
||||
*
|
||||
* 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
|
||||
|
||||
/** The unique [[Nature]] for this AutoPlugin instance. This has two purposes:
|
||||
* 1. The user can explicitly disable this AutoPlugin.
|
||||
* 2. Other plugins can activate based on whether this AutoPlugin was activated.
|
||||
*/
|
||||
def provides: Nature
|
||||
|
||||
/** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/
|
||||
def projectConfigurations: Seq[Configuration] = Nil
|
||||
|
||||
/** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */
|
||||
def projectSettings: Seq[Setting[_]] = Nil
|
||||
|
||||
/** 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
|
||||
* regardless of how many projects for that build activate this AutoPlugin. */
|
||||
def buildSettings: Seq[Setting[_]] = Nil
|
||||
|
||||
/** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */
|
||||
def globalSettings: Seq[Setting[_]] = Nil
|
||||
|
||||
// TODO?: def commands: Seq[Command]
|
||||
}
|
||||
|
||||
/** An error that occurs when auto-plugins aren't configured properly.
|
||||
* It translates the error from the underlying logic system to be targeted at end users. */
|
||||
final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message)
|
||||
{
|
||||
/** Prepends `p` to the error message derived from `origin`. */
|
||||
def withPrefix(p: String) = new AutoPluginException(p + message, origin)
|
||||
}
|
||||
object AutoPluginException
|
||||
{
|
||||
def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None)
|
||||
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Natures.translateMessage(origin), Some(origin))
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ object BuiltinCommands
|
|||
|
||||
def aboutPlugins(e: Extracted): String =
|
||||
{
|
||||
def list(b: BuildUnit) = b.plugins.detected.autoPlugins.values.map(_.provides) ++ b.plugins.detected.plugins.names
|
||||
def list(b: BuildUnit) = b.plugins.detected.autoPlugins.values.map(_.label) ++ b.plugins.detected.plugins.names
|
||||
val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct
|
||||
if(allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,95 @@
|
|||
package sbt
|
||||
/*
|
||||
TODO:
|
||||
- index all available AutoPlugins to get the tasks that will be added
|
||||
- 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.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException}
|
||||
import Def.Setting
|
||||
import Natures._
|
||||
|
||||
/** Marks a top-level object so that sbt will wildcard import it for .sbt files, `consoleProject`, and `set`. */
|
||||
trait AutoImport
|
||||
|
||||
/**
|
||||
An AutoPlugin defines a group of settings and the conditions where the settings are automatically added to a build (called "activation").
|
||||
The `select` method defines the conditions,
|
||||
`provides` defines an identifier for the AutoPlugin,
|
||||
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.
|
||||
2. Determine the settings/configurations to automatically inject when activated.
|
||||
3. Define a new, unique identifying [[Nature]] associated with the AutoPlugin, where a Nature is essentially a String ID.
|
||||
|
||||
For example, the following will automatically add the settings in `projectSettings`
|
||||
to a project that has both the `Web` and `Javascript` natures enabled. It will itself
|
||||
define the `MyStuff` nature. This nature can be explicitly disabled by the user to
|
||||
prevent the plugin from activating.
|
||||
|
||||
object MyPlugin extends AutoPlugin {
|
||||
def select = Web && Javascript
|
||||
def provides = MyStuff
|
||||
override def projectSettings = Seq(...)
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
For example, given natures Web and Javascript (perhaps provided by plugins added with addSbtPlugin),
|
||||
|
||||
<Project>.natures( Web && Javascript )
|
||||
|
||||
will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines
|
||||
|
||||
<Project>.natures( Web && Javascript && !MyStuff)
|
||||
|
||||
then the `MyPlugin` settings (and anything that activates only when `MyStuff` is activated) will not be added.
|
||||
*/
|
||||
abstract class AutoPlugin extends Natures.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`.
|
||||
*
|
||||
* 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
|
||||
|
||||
val label: String = getClass.getName.stripSuffix("$")
|
||||
|
||||
/** The [[Configuration]]s to add to each project that activates this AutoPlugin.*/
|
||||
def projectConfigurations: Seq[Configuration] = Nil
|
||||
|
||||
/** The [[Setting]]s to add in the scope of each project that activates this AutoPlugin. */
|
||||
def projectSettings: Seq[Setting[_]] = Nil
|
||||
|
||||
/** 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
|
||||
* regardless of how many projects for that build activate this AutoPlugin. */
|
||||
def buildSettings: Seq[Setting[_]] = Nil
|
||||
|
||||
/** The [[Setting]]s to add to the global scope exactly once if any project activates this AutoPlugin. */
|
||||
def globalSettings: Seq[Setting[_]] = Nil
|
||||
|
||||
// TODO?: def commands: Seq[Command]
|
||||
}
|
||||
|
||||
/** An error that occurs when auto-plugins aren't configured properly.
|
||||
* It translates the error from the underlying logic system to be targeted at end users. */
|
||||
final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message)
|
||||
{
|
||||
/** Prepends `p` to the error message derived from `origin`. */
|
||||
def withPrefix(p: String) = new AutoPluginException(p + message, origin)
|
||||
}
|
||||
object AutoPluginException
|
||||
{
|
||||
def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None)
|
||||
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Natures.translateMessage(origin), Some(origin))
|
||||
}
|
||||
|
||||
/** An expression that matches `Nature`s. */
|
||||
sealed trait Natures {
|
||||
def && (o: Basic): Natures
|
||||
|
|
@ -26,7 +112,7 @@ object Natures
|
|||
Types.const(Nil)
|
||||
else
|
||||
{
|
||||
val byAtom = defined.map(x => (Atom(x.provides.label), x))
|
||||
val byAtom = defined.map(x => (Atom(x.label), x))
|
||||
val byAtomMap = byAtom.toMap
|
||||
if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom)
|
||||
val clauses = Clauses( defined.map(d => asClause(d)) )
|
||||
|
|
@ -68,8 +154,8 @@ object Natures
|
|||
sealed abstract class Basic extends Natures {
|
||||
def &&(o: Basic): Natures = And(this :: o :: Nil)
|
||||
}
|
||||
private[sbt] final case class Exclude(n: Nature) extends Basic {
|
||||
def unary_! : Nature = n
|
||||
private[sbt] final case class Exclude(n: Basic) extends Basic {
|
||||
def unary_! : Basic = n
|
||||
override def toString = s"!$n"
|
||||
}
|
||||
private[sbt] final case class And(natures: List[Basic]) extends Natures {
|
||||
|
|
@ -84,7 +170,7 @@ object Natures
|
|||
|
||||
/** Defines a clause for `ap` such that the [[Nature]] 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.provides.label)) )
|
||||
Clause( convert(ap.select), Set(Atom(ap.label)) )
|
||||
|
||||
private[this] def flatten(n: Natures): Seq[Literal] = n match {
|
||||
case And(ns) => convertAll(ns)
|
||||
|
|
@ -100,6 +186,7 @@ object Natures
|
|||
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
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ sealed trait ProjectDefinition[PR <: ProjectReference]
|
|||
val agg = ifNonEmpty("aggregate", aggregate)
|
||||
val dep = ifNonEmpty("dependencies", dependencies)
|
||||
val conf = ifNonEmpty("configurations", configurations)
|
||||
val autos = ifNonEmpty("autoPlugins", autoPlugins.map(_.provides))
|
||||
val autos = ifNonEmpty("autoPlugins", autoPlugins.map(_.label))
|
||||
val fields = s"id $id" :: s"base: $base" :: agg ::: dep ::: conf ::: (s"natures: List($natures)" :: autos)
|
||||
s"Project(${fields.mkString(", ")})"
|
||||
}
|
||||
|
|
@ -136,11 +136,17 @@ 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 [[Natures]] of this project.
|
||||
/** 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: Natures): Project = {
|
||||
def addNatures(ns: Nature*): Project = setNatures(Natures.and(natures, Natures.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)))
|
||||
|
||||
private[this] def setNatures(ns: Natures): Project = {
|
||||
// TODO: for 0.14.0, use copy when it has the additional `natures` parameter
|
||||
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, Natures.and(natures, ns), autoPlugins)
|
||||
unresolved(id, base, aggregate = aggregate, dependencies = dependencies, delegates = delegates, settings, configurations, auto, ns, autoPlugins)
|
||||
}
|
||||
|
||||
/** Definitively set the [[AutoPlugin]]s for this project. */
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// !C will exclude C, and thus D, from being auto-added
|
||||
lazy val a = project.addNatures(A && B && !C)
|
||||
// excludePlugins(C) will prevent C, and thus D, from being auto-added
|
||||
lazy val a = project.addNatures(A, B).disablePlugins(Q)
|
||||
|
||||
// without B, C is not added
|
||||
lazy val b = project.addNatures(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.addNatures(A, B)
|
||||
|
||||
// with no natures defined, nothing is auto-added
|
||||
lazy val d = project
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ object AI extends AutoImport
|
|||
{
|
||||
lazy val A = Nature("A")
|
||||
lazy val B = Nature("B")
|
||||
lazy val C = Nature("C")
|
||||
lazy val D = Nature("D")
|
||||
lazy val E = Nature("E")
|
||||
|
||||
lazy val q = config("q")
|
||||
lazy val p = config("p").extend(q)
|
||||
|
|
@ -25,8 +23,6 @@ object Q extends AutoPlugin
|
|||
{
|
||||
def select: Natures = A && B
|
||||
|
||||
def provides = C
|
||||
|
||||
override def projectConfigurations: Seq[Configuration] =
|
||||
p ::
|
||||
q ::
|
||||
|
|
@ -52,9 +48,7 @@ object Q extends AutoPlugin
|
|||
|
||||
object R extends AutoPlugin
|
||||
{
|
||||
def select = C && !D
|
||||
|
||||
def provides = E
|
||||
def select = Q && !D
|
||||
|
||||
override def projectSettings = Seq(
|
||||
// tests proper ordering: R requires C, so C settings should come first
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import Keys._
|
|||
|
||||
|
||||
object C extends AutoImport {
|
||||
lazy val aN = Nature("A")
|
||||
lazy val bN = Nature("B")
|
||||
lazy val check = taskKey[Unit]("Checks that the AutoPlugin and Build are automatically added.")
|
||||
}
|
||||
|
|
@ -11,7 +10,6 @@ object C extends AutoImport {
|
|||
import C._
|
||||
|
||||
object A extends AutoPlugin {
|
||||
override def provides = aN
|
||||
override def select = bN
|
||||
override def projectSettings = Seq(
|
||||
check := {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue