diff --git a/main/src/main/scala/sbt/AutoPlugin.scala b/main/src/main/scala/sbt/AutoPlugin.scala deleted file mode 100644 index 4cf06bfe3..000000000 --- a/main/src/main/scala/sbt/AutoPlugin.scala +++ /dev/null @@ -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), - - .natures( Web && Javascript ) - -will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines - - .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)) -} diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 462d5a49b..c582426ae 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -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: ", ", ", "") } diff --git a/main/src/main/scala/sbt/Natures.scala b/main/src/main/scala/sbt/Natures.scala index 4d8f7095b..b121df408 100644 --- a/main/src/main/scala/sbt/Natures.scala +++ b/main/src/main/scala/sbt/Natures.scala @@ -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), + + .natures( Web && Javascript ) + +will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines + + .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 } \ No newline at end of file diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 8baa06997..647013bed 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -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. */ diff --git a/sbt/src/sbt-test/project/auto-plugins/build.sbt b/sbt/src/sbt-test/project/auto-plugins/build.sbt index d9543939b..f48a1f0e5 100644 --- a/sbt/src/sbt-test/project/auto-plugins/build.sbt +++ b/sbt/src/sbt-test/project/auto-plugins/build.sbt @@ -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 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 73dd5211b..db51922cf 100644 --- a/sbt/src/sbt-test/project/auto-plugins/project/Q.scala +++ b/sbt/src/sbt-test/project/auto-plugins/project/Q.scala @@ -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 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 5e4a3930e..c38558d4f 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 @@ -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 := {}