Unifies AutoPlugin and RootPlugin

* Separates def select into def trigger and def requires.
* Setting trigger = noTrigger and requires = empty makes a plugin a
root.
This commit is contained in:
Eugene Yokota 2014-03-20 07:08:33 -04:00
parent fd860e0d3a
commit f8bedf4012
12 changed files with 184 additions and 101 deletions

View File

@ -100,7 +100,7 @@ final class DetectedPlugins(val plugins: DetectedModules[Plugin], val autoImport
lazy val imports: Seq[String] = BuildUtil.getImports(plugins.names ++ builds.names ++ autoImports.names)
/** A function to select the right [[AutoPlugin]]s from [[autoPlugins]] for a [[Project]]. */
lazy val compilePlugins: Plugins => Seq[AutoPlugin] = Plugins.compile(autoPlugins.values.toList)
lazy val deducePlugins: (Plugins, Logger) => Seq[AutoPlugin] = Plugins.deducer(autoPlugins.values.toList)
}
/** The built and loaded build definition project.

View File

@ -415,7 +415,7 @@ object Load
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase))
val memoSettings = new mutable.HashMap[File, LoadedSbtFile]
def loadProjects(ps: Seq[Project]) = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings)
def loadProjects(ps: Seq[Project]) = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log)
val loadedProjectsRaw = loadProjects(initialProjects)
val hasRoot = loadedProjectsRaw.exists(_.base == normBase) || defsScala.exists(_.rootProject.isDefined)
val (loadedProjects, defaultBuildIfNone) =
@ -457,13 +457,14 @@ object Load
private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] =
b.projectDefinitions(base).map(resolveBase(base))
private[this] def loadTransitive(newProjects: Seq[Project], buildBase: File, plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile]): Seq[Project] =
private[this] def loadTransitive(newProjects: Seq[Project], buildBase: File, plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings,
acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile], log: Logger): Seq[Project] =
{
def loadSbtFiles(auto: AddSettings, base: File, autoPlugins: Seq[AutoPlugin], projectSettings: Seq[Setting[_]]): LoadedSbtFile =
loadSettings(auto, base, plugins, eval, injectSettings, memoSettings, autoPlugins, projectSettings)
def loadForProjects = newProjects map { project =>
val autoPlugins =
try plugins.detected.compilePlugins(project.plugins)
try plugins.detected.deducePlugins(project.plugins, log)
catch { case e: AutoPluginException => throw translateAutoPluginException(e, project) }
val autoConfigs = autoPlugins.flatMap(_.projectConfigurations)
val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins, project.settings)
@ -483,7 +484,7 @@ object Load
if(nextProjects.isEmpty)
loadedProjects
else
loadTransitive(nextProjects, buildBase, plugins, eval, injectSettings, loadedProjects, memoSettings)
loadTransitive(nextProjects, buildBase, plugins, eval, injectSettings, loadedProjects, memoSettings, log)
}
private[this] def translateAutoPluginException(e: AutoPluginException, project: Project): AutoPluginException =
e.withPrefix(s"Error determining plugins for project '${project.id}' in ${project.base}:\n")

View File

@ -9,13 +9,14 @@ TODO:
import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException}
import Def.Setting
import Plugins._
import annotation.tailrec
/** 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 and a method like `projectSettings` defines the settings to add.
The `requires` and `trigger` method defines the conditions and a method like `projectSettings` defines the settings to add.
Steps for plugin authors:
1. Determine the [[AutoPlugins]]s that, when present (or absent), activate the AutoPlugin.
@ -25,7 +26,7 @@ For example, the following will automatically add the settings in `projectSettin
to a project that has both the `Web` and `Javascript` plugins enabled.
object MyPlugin extends AutoPlugin {
def select = Web && Javascript
def requires = Web && Javascript
override def projectSettings = Seq(...)
}
@ -44,14 +45,20 @@ 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.
*/
abstract class AutoPlugin extends Plugins.Basic
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions
{
/** 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` plugins are enabled. */
def select: Plugins
/** 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
* instance will be added automatically if the `Web` and `Javascript` plugins are enbled.
*
* 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`. */
def trigger: PluginTrigger
/** This AutoPlugin requires the plugins the [[Plugins]] matcher returned by this method. See [[trigger]].
*/
def requires: Plugins
val label: String = getClass.getName.stripSuffix("$")
@ -74,22 +81,16 @@ abstract class AutoPlugin extends Plugins.Basic
def unary_! : Exclude = Exclude(this)
/** If this plugin requries itself to be included, it means we're actually a nature,
* not a normal plugin. The user must specifically enable this plugin
* but other plugins can rely on its existence.
*/
final def isRoot: Boolean =
this match {
case _: RootAutoPlugin => true
/** If this plugin does not have any requirements, it means it is actually a root plugin. */
private[sbt] final def isRoot: Boolean =
requires match {
case Empty => true
case _ => false
}
}
/**
* A root AutoPlugin is a plugin which must be explicitly enabled by users in their `addPlugins` method
* on a project. However, RootAutoPlugins represent the "root" of a tree of dependent auto-plugins.
*/
abstract class RootAutoPlugin extends AutoPlugin {
final def select: Plugins = this
/** If this plugin does not have any requirements, it means it is actually a root plugin. */
private[sbt] final def isAlwaysEnabled: Boolean =
isRoot && (trigger == AllRequirements)
}
/** An error that occurs when auto-plugins aren't configured properly.
@ -105,37 +106,83 @@ object AutoPluginException
def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Plugins.translateMessage(origin), Some(origin))
}
sealed trait PluginTrigger
case object AllRequirements extends PluginTrigger
case object NoTrigger extends PluginTrigger
/** An expression that matches `AutoPlugin`s. */
sealed trait Plugins {
def && (o: Basic): Plugins
}
object Plugins
sealed trait PluginsFunctions
{
/** [[Plugins]] instance that doesn't require any [[Plugins]]s. */
def empty: Plugins = Plugins.Empty
/** This plugin is activated when all required plugins are present. */
def allRequirements: PluginTrigger = AllRequirements
/** This plugin is activated only when it is manually activated. */
def noTrigger: PluginTrigger = NoTrigger
}
object Plugins extends PluginsFunctions
{
/** 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]): Plugins => Seq[AutoPlugin] =
if(defined.isEmpty)
Types.const(Nil)
* 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] =
if(defined0.isEmpty) (_, _) => Nil
else
{
val byAtom = defined.map(x => (Atom(x.label), x))
// TODO: defined should return all the plugins
val allReqs = (defined0 flatMap { asRequirements }).toSet
val diff = allReqs diff defined0.toSet
val defined = if (!diff.isEmpty) diff.toList ::: defined0
else defined0
val byAtom = defined map { x => (Atom(x.label), x) }
val byAtomMap = byAtom.toMap
if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom)
// Ignore clauses for plugins that just require themselves be specified.
// Ignore clauses for plugins that does not require anything else.
// Avoids the requirement for pure Nature strings *and* possible
// circular dependencies in the logic.
val clauses = Clauses( defined.filterNot(_.isRoot).map(d => asClause(d)) )
requestedPlugins =>
Logic.reduce(clauses, flattenConvert(requestedPlugins).toSet) match {
val allRequirementsClause = defined.filterNot(_.isRoot).flatMap(d => asRequirementsClauses(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) =>
// results includes the originally requested (positive) atoms,
// which won't have a corresponding AutoPlugin to map back to
results.ordered.flatMap(a => byAtomMap.get(a).toList)
case Right(results0) =>
log.debug(s" :: deduced result: ${results0}")
val plugins = results0.ordered map { a =>
byAtomMap.getOrElse(a, throw AutoPluginException(s"${a} was not found in atom map."))
}
val retval = topologicalSort(plugins, 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 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)}"
@ -153,8 +200,6 @@ object Plugins
throw AutoPluginException(message)
}
/** [[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>"
@ -186,10 +231,18 @@ object Plugins
if(removed.isEmpty) Empty else And(removed)
}
/** Defines a clause for `ap` such that the [[AutoPlugin]] 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)) )
/** Defines enabled-by clauses for `ap`. */
private[sbt] def asEnabledByClauses(ap: AutoPlugin): List[Clause] =
// `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
else Nil
/** Defines requirements clauses for `ap`. */
private[sbt] def asRequirementsClauses(ap: AutoPlugin): List[Clause] =
// required plugin is the head and `ap` is the body.
asRequirements(ap) map { x => Clause( convert(ap), Set(Atom(x.label)) ) }
private[sbt] def asRequirements(ap: AutoPlugin): List[AutoPlugin] = flatten(ap.requires).toList collect {
case x: AutoPlugin => x
}
private[this] def flattenConvert(n: Plugins): Seq[Literal] = n match {
case And(ns) => convertAll(ns)
case b: Basic => convertBasic(b) :: Nil
@ -212,7 +265,7 @@ object Plugins
}
private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic
/** True if the select clause `n` is satisifed by `model`. */
/** True if the trigger clause `n` is satisifed by `model`. */
def satisfied(n: Plugins, model: Set[AutoPlugin]): Boolean =
flatten(n) forall {
case Exclude(a) => !model(a)

View File

@ -118,7 +118,7 @@ private[sbt] object PluginsDebug
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.plugins, currentProject.autoPlugins, Plugins.compile(pluginsThisBuild), pluginsThisBuild)
lazy val context = Context(currentProject.plugins, currentProject.autoPlugins, Plugins.deducer(pluginsThisBuild), pluginsThisBuild, s.log)
lazy val debug = PluginsDebug(context.available)
if(!pluginsThisBuild.contains(plugin)) {
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
@ -152,9 +152,9 @@ private[sbt] object PluginsDebug
/** The context for debugging a plugin (de)activation.
* @param initial The initially defined [[AutoPlugin]]s.
* @param enabled The resulting model.
* @param compile The function used to compute the model.
* @param deducePlugin The function used to compute the model.
* @param available All [[AutoPlugin]]s available for consideration. */
final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], compile: Plugins => Seq[AutoPlugin], available: List[AutoPlugin])
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. */
sealed abstract class PluginEnable
@ -236,10 +236,10 @@ private[sbt] object PluginsDebug
// 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(minRequiredPlugins), excludeAll(minAbsentPlugins)))
val modelForMin = context.deducePlugin(and(includeAll(minRequiredPlugins), excludeAll(minAbsentPlugins)), context.log)
val incrementalInputs = and( includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins))
val incrementalModel = context.compile(incrementalInputs).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`.
// These could be excluded and `plugin` and the user's current plugins would still be activated.
@ -252,7 +252,7 @@ private[sbt] object PluginsDebug
// 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.
// 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.select, incrementalModel))
val minDeactivate = minAbsentPlugins.filter(p => Plugins.satisfied(p.requires, incrementalModel))
val deactivate = for(d <- minDeactivate.toList) yield {
// removing any one of these plugins will deactivate `d`. TODO: This is not an especially efficient implementation.
@ -280,7 +280,7 @@ private[sbt] object PluginsDebug
// 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 => Nil
case ap: AutoPlugin => Plugins.flatten(ap.select)
case ap: AutoPlugin => Plugins.flatten(ap.requires) :+ plugin
}
/** String representation of [[PluginEnable]], intended for end users. */

View File

@ -9,8 +9,9 @@ import Def.Setting
* Can control task-level paralleism, logging, etc.
*/
object GlobalModule extends AutoPlugin {
// We must be explicitly enabled
def select = Plugins.empty
// This is included by default
def requires = empty
def trigger = allRequirements
override lazy val projectSettings: Seq[Setting[_]] =
Defaults.coreDefaultSettings

View File

@ -16,7 +16,8 @@ import Def.Setting
object IvyModule extends AutoPlugin {
// We are automatically included on everything that has the global module,
// which is automatically included on everything.
def select = GlobalModule
def requires = GlobalModule
def trigger = allRequirements
override lazy val projectSettings: Seq[Setting[_]] =
Classpaths.ivyPublishSettings ++ Classpaths.ivyBaseSettings

View File

@ -17,7 +17,8 @@ import Def.Setting
object JvmModule extends AutoPlugin {
// We are automatically enabled for any IvyModule project. We also require its settings
// for ours to work.
def select = IvyModule
def requires = IvyModule
def trigger = allRequirements
override lazy val projectSettings: Seq[Setting[_]] =
Defaults.runnerSettings ++

View File

@ -1,34 +1,38 @@
// excludePlugins(C) will prevent C, and thus D, from being auto-added
lazy val a = project.addPlugins(A, B).disablePlugins(Q)
// disablePlugins(Q) will prevent R from being auto-added
lazy val projA = project.addPlugins(A, B).disablePlugins(Q)
// without B, C is not added
lazy val b = project.addPlugins(A)
// without B, Q is not added
lazy val projB = project.addPlugins(A)
// with both A and B, C is selected, which in turn selects D
lazy val c = project.addPlugins(A, B)
// with both A and B, Q is selected, which in turn selects R, but not S
lazy val projC = project.addPlugins(A, B)
// with no natures defined, nothing is auto-added
lazy val d = project
lazy val projD = project
// with S selected, Q is loaded automatically, which in turn selects R
lazy val projE = project.addPlugins(S)
check := {
val ddel = (del in d).?.value // should be None
same(ddel, None, "del in d")
val bdel = (del in b).?.value // should be None
same(bdel, None, "del in b")
val adel = (del in a).?.value // should be None
same(adel, None, "del in a")
val adel = (del in projA).?.value // should be None
same(adel, None, "del in projA")
val bdel = (del in projB).?.value // should be None
same(bdel, None, "del in projB")
val ddel = (del in projD).?.value // should be None
same(ddel, None, "del in projD")
//
val buildValue = (demo in ThisBuild).value
same(buildValue, "build 0", "demo in ThisBuild")
val globalValue = (demo in Global).value
same(globalValue, "global 0", "demo in Global")
val projValue = (demo in c).value
same(projValue, "project c Q R", "demo in c")
val qValue = (del in c in q).value
same(qValue, " Q R", "del in c in q")
val projValue = (demo in projC).value
same(projValue, "project projC Q R", "demo in projC")
val qValue = (del in projC in q).value
same(qValue, " Q R", "del in projC in q")
val optInValue = (del in projE in q).value
same(optInValue, " Q S R", "del in projE in q")
}
def same[T](actual: T, expected: T, label: String) {
assert(actual == expected, s"Expected '$expected' for `$label`, got '$actual'")
}
}

View File

@ -5,7 +5,8 @@
object AI extends AutoImport
{
trait EmptyAutoPlugin extends AutoPlugin {
def select = Plugins.empty
def requires = empty
def trigger = noTrigger
}
object A extends EmptyAutoPlugin
object B extends EmptyAutoPlugin
@ -23,12 +24,14 @@ object AI extends AutoImport
import AI._
object D extends AutoPlugin {
def select: Plugins = E
def requires: Plugins = E
def trigger = allRequirements
}
object Q extends AutoPlugin
{
def select: Plugins = A && B
def requires: Plugins = A && B
def trigger = allRequirements
override def projectConfigurations: Seq[Configuration] =
p ::
@ -56,12 +59,25 @@ object Q extends AutoPlugin
object R extends AutoPlugin
{
// NOTE - Only plugins themselves support exclusions...
def select = Q && !D
def requires = Q && !D
def trigger = allRequirements
override def projectSettings = Seq(
// tests proper ordering: R requires C, so C settings should come first
// tests proper ordering: R requires Q, so Q settings should come first
del in q += " R",
// tests that configurations are properly registered, enabling delegation from p to q
demo += (del in p).value
)
}
}
// This is an opt-in plugin with a requirement
// Unless explicitly loaded by the build user, this will not be activated.
object S extends AutoPlugin
{
def requires = Q
def trigger = noTrigger
override def projectSettings = Seq(
del in q += " S"
)
}

View File

@ -4,7 +4,8 @@ import Keys._
object C extends AutoImport {
object bN extends AutoPlugin {
def select = Plugins.empty
def requires = empty
def trigger = allRequirements
}
lazy val check = taskKey[Unit]("Checks that the AutoPlugin and Build are automatically added.")
}
@ -12,7 +13,8 @@ object C extends AutoImport {
import C._
object A extends AutoPlugin {
override def select = bN
def requires = bN
def trigger = allRequirements
override def projectSettings = Seq(
check := {}
)

View File

@ -234,10 +234,12 @@ core methods without requiring an import or qualification.
In addition, a plugin can implement the `AutoPlugin` class. This has additoinal features, such as
* Specifying plugin dependencies.
* Automatically activating itself when all dependencies are present.
* Specifying `projectSettings`, `buildSettings`, and `globalSettings` as appropriate.
The AutoPlugin's `projectSettings` is automatically appended to each project's settings, when its dependencies also exist on that project
The `select` method defines the conditions by which this plugin's settings are automatically imported.
The `requires` method defines the dependencies to other plugins.
The `trigger` method defines the conditions by which this plugin's settings are automatically activated.
The `buildSettings` is appended to each build's settings (that is, `in ThisBuild`).
The `globalSettings` is appended once to the global settings (`in Global`).
These allow a plugin to automatically provide new functionality or new defaults.
@ -268,8 +270,9 @@ An example of a typical plugin:
object MyPlugin extends AutoPlugin
{
// Only enable this plugin for projects which are JvmModules.
def select = sbt.plugins.JvmModule
def trigger = allRequirements
def requires = sbt.plugins.JvmModule
// configuration points, like the built in `version`, `libraryDependencies`, or `compile`
// by implementing Plugin, these are automatically imported in a user's `build.sbt`
val newTask = taskKey[Unit]("A new task.")
@ -302,11 +305,8 @@ A build definition that uses the plugin might look like:
Root Plugins
------------
Some plugins should always be explicitly enabled on projects. Sbt calls these "RootPlugins", i.e. plugins
that are "root" nodes in the plugin depdendency graph. To define a root plugin, just extend the `sbt.RootPlugin`
interface. This interface is exactly like the `AutoPlugin` interface except that a `select` method is not
needed.
Some plugins should always be explicitly enabled on projects. Sbt calls these root plugins, i.e. plugins
that are "root" nodes in the plugin depdendency graph. To define a root plugin, set the `trigger` method to `noTrigger` and the `requires` method to `empty`.
Example command root plugin
----------------------
@ -329,8 +329,11 @@ A basic plugin that adds commands looks like:
import sbt._
import Keys._
object MyPlugin extends RootPlugin
object MyPlugin extends AutoPlugin
{
def trigger = noTrigger
def requires = empty
override lazy val projectSettings = Seq(commands += myCommand)
lazy val myCommand =

View File

@ -119,11 +119,12 @@ To create an sbt plugin,
1. Create a new project for the plugin.
2. Set `sbtPlugin := true` for the project in `build.sbt`. This adds a dependency on sbt and will detect and record Plugins that you define.
3. Define an `object` that extends `AutoPlugin` or `RootPlugin`. The contents of this object will be automatically imported in `.sbt` files, so ensure it only contains important API definitions and types.
4. Define any custom tasks or settings (see the next section :doc:`Custom-Settings`).
5. Collect the default settings to apply to a project in a list for the user to add. Optionally override one or more of `AutoPlugin`'s methods to have settings automatically added to user projects.
6. (Optional) For non-root plguins, declare dependencies on other plugins by overriding the `select` method.
6. Publish the project. There is a :doc:`community repository </Community/Community-Plugins>` available for open source plugins.
3. Define another `object` that extends `AutoImport`. The contents of this object will be automatically imported in `.sbt` files, so ensure it only contains important API definitions and types.
4. Define an `object` that extends `AutoPlugin`.
5. Declare dependencies on other plugins by defining the `requires` method.
5. Define any custom tasks or settings (see the next section :doc:`Custom-Settings`).
6. Collect the default settings to apply to a project in a list for the user to add. Optionally override one or more of `AutoPlugin`'s methods to have settings automatically added to user projects.
8. Publish the project. There is a :doc:`community repository </Community/Community-Plugins>` available for open source plugins.
For more details, including ways of developing plugins, see :doc:`/Extending/Plugins`.
For best practices, see :doc:`/Extending/Plugins-Best-Practices`.