diff --git a/main/src/main/scala/sbt/BuildStructure.scala b/main/src/main/scala/sbt/BuildStructure.scala index d63752d87..615a80771 100644 --- a/main/src/main/scala/sbt/BuildStructure.scala +++ b/main/src/main/scala/sbt/BuildStructure.scala @@ -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. diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index a275c907a..4b9edb637 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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") diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index 1d1b7b977..63f83397f 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -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 = "" @@ -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) diff --git a/main/src/main/scala/sbt/PluginsDebug.scala b/main/src/main/scala/sbt/PluginsDebug.scala index a24546c23..7d9b2670a 100644 --- a/main/src/main/scala/sbt/PluginsDebug.scala +++ b/main/src/main/scala/sbt/PluginsDebug.scala @@ -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. */ diff --git a/main/src/main/scala/sbt/plugins/GlobalModule.scala b/main/src/main/scala/sbt/plugins/GlobalModule.scala index 570cbc80f..00485a5e0 100644 --- a/main/src/main/scala/sbt/plugins/GlobalModule.scala +++ b/main/src/main/scala/sbt/plugins/GlobalModule.scala @@ -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 diff --git a/main/src/main/scala/sbt/plugins/IvyModule.scala b/main/src/main/scala/sbt/plugins/IvyModule.scala index a0e361503..0b01f4670 100644 --- a/main/src/main/scala/sbt/plugins/IvyModule.scala +++ b/main/src/main/scala/sbt/plugins/IvyModule.scala @@ -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 diff --git a/main/src/main/scala/sbt/plugins/JvmModule.scala b/main/src/main/scala/sbt/plugins/JvmModule.scala index 0a7219c26..f50fb1e7d 100644 --- a/main/src/main/scala/sbt/plugins/JvmModule.scala +++ b/main/src/main/scala/sbt/plugins/JvmModule.scala @@ -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 ++ diff --git a/sbt/src/sbt-test/project/auto-plugins/build.sbt b/sbt/src/sbt-test/project/auto-plugins/build.sbt index 57e1394c8..7039ed235 100644 --- a/sbt/src/sbt-test/project/auto-plugins/build.sbt +++ b/sbt/src/sbt-test/project/auto-plugins/build.sbt @@ -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'") -} \ No newline at end of file +} diff --git a/sbt/src/sbt-test/project/auto-plugins/project/Q.scala b/sbt/src/sbt-test/project/auto-plugins/project/Q.scala index c6dea7ba8..84cef307f 100644 --- a/sbt/src/sbt-test/project/auto-plugins/project/Q.scala +++ b/sbt/src/sbt-test/project/auto-plugins/project/Q.scala @@ -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 ) -} \ No newline at end of file +} + +// 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" + ) +} diff --git a/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala b/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala index a9f71c928..99cd6d527 100644 --- a/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala +++ b/sbt/src/sbt-test/project/binary-plugin/changes/define/A.scala @@ -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 := {} ) diff --git a/src/sphinx/Extending/Plugins.rst b/src/sphinx/Extending/Plugins.rst index 3d510a8ba..77f5da73e 100644 --- a/src/sphinx/Extending/Plugins.rst +++ b/src/sphinx/Extending/Plugins.rst @@ -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 = diff --git a/src/sphinx/Getting-Started/Using-Plugins.rst b/src/sphinx/Getting-Started/Using-Plugins.rst index 57dcc0a1d..4ab353755 100644 --- a/src/sphinx/Getting-Started/Using-Plugins.rst +++ b/src/sphinx/Getting-Started/Using-Plugins.rst @@ -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 ` 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 ` 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`.