From 7f8d21c2f1a54d9180eecc82baaacedbfd7b7790 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Mar 2014 17:56:34 -0500 Subject: [PATCH 01/10] 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. --- main/src/main/scala/sbt/BuildStructure.scala | 6 +- main/src/main/scala/sbt/Load.scala | 2 +- main/src/main/scala/sbt/Main.scala | 6 +- .../sbt/{Natures.scala => Plugins.scala} | 95 ++++++++-------- ...{NaturesDebug.scala => PluginsDebug.scala} | 102 +++++++++--------- main/src/main/scala/sbt/Project.scala | 50 ++++----- .../sbt-test/project/auto-plugins/build.sbt | 6 +- .../project/auto-plugins/project/Q.scala | 13 ++- .../binary-plugin/changes/define/A.scala | 6 +- 9 files changed, 140 insertions(+), 146 deletions(-) rename main/src/main/scala/sbt/{Natures.scala => Plugins.scala} (69%) rename main/src/main/scala/sbt/{NaturesDebug.scala => PluginsDebug.scala} (82%) diff --git a/main/src/main/scala/sbt/BuildStructure.scala b/main/src/main/scala/sbt/BuildStructure.scala index 1fddbf2a0..d63752d87 100644 --- a/main/src/main/scala/sbt/BuildStructure.scala +++ b/main/src/main/scala/sbt/BuildStructure.scala @@ -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. diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 2a00e7329..d6c9eff85 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index ad5291ec2..c44bd5a1b 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -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 } diff --git a/main/src/main/scala/sbt/Natures.scala b/main/src/main/scala/sbt/Plugins.scala similarity index 69% rename from main/src/main/scala/sbt/Natures.scala rename to main/src/main/scala/sbt/Plugins.scala index 06b0a0e2f..5e814d082 100644 --- a/main/src/main/scala/sbt/Natures.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -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), - .natures( Web && Javascript ) + .plugins( Web && Javascript ) will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines - .natures( Web && Javascript && !MyPlugin) + .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 = "" } - /** 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) } } \ No newline at end of file diff --git a/main/src/main/scala/sbt/NaturesDebug.scala b/main/src/main/scala/sbt/PluginsDebug.scala similarity index 82% rename from main/src/main/scala/sbt/NaturesDebug.scala rename to main/src/main/scala/sbt/PluginsDebug.scala index d0e27a9dd..e130b2c8b 100644 --- a/main/src/main/scala/sbt/NaturesDebug.scala +++ b/main/src/main/scala/sbt/PluginsDebug.scala @@ -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 = diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 647013bed..a44598e94 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -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 diff --git a/sbt/src/sbt-test/project/auto-plugins/build.sbt b/sbt/src/sbt-test/project/auto-plugins/build.sbt index f48a1f0e5..57e1394c8 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 @@ // 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 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 e092e0fd1..c6dea7ba8 100644 --- a/sbt/src/sbt-test/project/auto-plugins/project/Q.scala +++ b/sbt/src/sbt-test/project/auto-plugins/project/Q.scala @@ -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 :: 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 c38558d4f..a9f71c928 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,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) } From ac9391066b572321f8b183b96367dc476bff5d32 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Mar 2014 17:59:29 -0500 Subject: [PATCH 02/10] Allow Build.scala project settings to be ordered via AddSettings. * Create new AddSettings.ProjectSettings that can be used in the Addsettings order. * Update Load.scala to correctly abide by AddSettings orderings. --- main/src/main/scala/sbt/AddSettings.scala | 14 +++++++++++++- main/src/main/scala/sbt/Load.scala | 16 ++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/main/src/main/scala/sbt/AddSettings.scala b/main/src/main/scala/sbt/AddSettings.scala index 2d698b874..ab90c8d8f 100644 --- a/main/src/main/scala/sbt/AddSettings.scala +++ b/main/src/main/scala/sbt/AddSettings.scala @@ -14,10 +14,21 @@ object AddSettings private[sbt] final class Plugins(val include: Plugin => Boolean) extends AddSettings private[sbt] final class DefaultSbtFiles(val include: File => Boolean) extends AddSettings private[sbt] final class SbtFiles(val files: Seq[File]) extends AddSettings + // Settings created with the Project().settings() commands in build.scala files. + private[sbt] final object ProjectSettings extends AddSettings /** Adds all settings from a plugin to a project. */ val allPlugins: AddSettings = plugins(const(true)) + /** Adds all settings from autoplugins. */ + val autoPlugins: AddSettings = plugins(_.isInstanceOf[AutoPlugin]) + + /** Settings specified in Build.scala `Project` constructors. */ + val projectSettings: AddSettings = ProjectSettings + + /** All plugins that aren't auto plugins. */ + val nonAutoPlugins: AddSettings = plugins(!_.isInstanceOf[AutoPlugin]) + /** Allows the plugins whose names match the `names` filter to automatically add settings to a project. */ def plugins(include: Plugin => Boolean): AddSettings = new Plugins(include) @@ -33,7 +44,8 @@ object AddSettings /** Includes settings automatically*/ def seq(autos: AddSettings*): AddSettings = new Sequence(autos) - val allDefaults: AddSettings = seq(userSettings, allPlugins, defaultSbtFiles) + /** The default inclusion of settings. */ + val allDefaults: AddSettings = seq(autoPlugins, projectSettings, userSettings, nonAutoPlugins, defaultSbtFiles) /** Combines two automatic setting configurations. */ def append(a: AddSettings, b: AddSettings): AddSettings = (a,b) match { diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index d6c9eff85..b040ee0ae 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -459,20 +459,19 @@ object Load 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] = { - def loadSbtFiles(auto: AddSettings, base: File, autoPlugins: Seq[AutoPlugin]): LoadedSbtFile = - loadSettings(auto, base, plugins, eval, injectSettings, memoSettings, autoPlugins) + 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) catch { case e: AutoPluginException => throw translateAutoPluginException(e, project) } val autoConfigs = autoPlugins.flatMap(_.projectConfigurations) - val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins) - val newSettings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings + val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins, project.settings) // add the automatically selected settings, record the selected AutoPlugins, and register the automatically selected configurations - val transformed = project.copy(settings = newSettings).setAutoPlugins(autoPlugins).overrideConfigs(autoConfigs : _*) + val transformed = project.copy(settings = loadedSbtFiles.settings).setAutoPlugins(autoPlugins).overrideConfigs(autoConfigs : _*) (transformed, loadedSbtFiles.projects) } - def defaultLoad = loadSbtFiles(AddSettings.defaultSbtFiles, buildBase, Nil).projects + def defaultLoad = loadSbtFiles(AddSettings.defaultSbtFiles, buildBase, Nil, Nil).projects val (nextProjects, loadedProjects) = if(newProjects.isEmpty) // load the .sbt files in the root directory to look for Projects (defaultLoad, acc) @@ -489,7 +488,7 @@ object Load private[this] def translateAutoPluginException(e: AutoPluginException, project: Project): AutoPluginException = e.withPrefix(s"Error determining plugins for project '${project.id}' in ${project.base}:\n") - private[this] def loadSettings(auto: AddSettings, projectBase: File, loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], autoPlugins: Seq[AutoPlugin]): LoadedSbtFile = + private[this] def loadSettings(auto: AddSettings, projectBase: File, loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile], autoPlugins: Seq[AutoPlugin], projectSettings: Seq[Setting[_]]): LoadedSbtFile = { lazy val defaultSbtFiles = configurationSources(projectBase) def settings(ss: Seq[Setting[_]]) = new LoadedSbtFile(ss, Nil, Nil) @@ -506,7 +505,7 @@ object Load def loadSettingsFile(src: File): LoadedSbtFile = EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), loadedPlugins.detected.imports, 0)(loader) - import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence} + import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence, ProjectSettings} def pluginSettings(f: Plugins) = { val included = loadedPlugins.detected.plugins.values.filter(f.include) // don't apply the filter to AutoPlugins, only Plugins val oldStyle = included.flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings) @@ -514,6 +513,7 @@ object Load oldStyle ++ autoStyle } def expand(auto: AddSettings): LoadedSbtFile = auto match { + case ProjectSettings => settings(projectSettings) case User => settings(injectSettings.projectLoaded(loader)) case sf: SbtFiles => loadSettings( sf.files.map(f => IO.resolve(projectBase, f))) case sf: DefaultSbtFiles => loadSettings( defaultSbtFiles.filter(sf.include)) From a44a14f2c838745a1ab7c741dafccdd8aa3faa88 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Mar 2014 18:03:00 -0500 Subject: [PATCH 03/10] AutoPlugins appropriately participate in AddSettings. * Add new AutoPlugins type to AddSettings. * Ensure any Plugins filter doesn't just automatically always add autoplugins every time. * Load.scala can now adjust AutoPlugins ordering Note: Adjusting autoplugin ordering is dangerous BUT doing a glob of "put autoplugin settings here" is generally ok. --- main/src/main/scala/sbt/AddSettings.scala | 11 ++++++----- main/src/main/scala/sbt/Load.scala | 12 ++++++++---- main/src/main/scala/sbt/Plugins.scala | 23 ++++++++++++++++++++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/main/src/main/scala/sbt/AddSettings.scala b/main/src/main/scala/sbt/AddSettings.scala index ab90c8d8f..ceb0bc751 100644 --- a/main/src/main/scala/sbt/AddSettings.scala +++ b/main/src/main/scala/sbt/AddSettings.scala @@ -12,22 +12,23 @@ object AddSettings private[sbt] final class Sequence(val sequence: Seq[AddSettings]) extends AddSettings private[sbt] final object User extends AddSettings private[sbt] final class Plugins(val include: Plugin => Boolean) extends AddSettings + private[sbt] final class AutoPlugins(val include: AutoPlugin => Boolean) extends AddSettings private[sbt] final class DefaultSbtFiles(val include: File => Boolean) extends AddSettings private[sbt] final class SbtFiles(val files: Seq[File]) extends AddSettings // Settings created with the Project().settings() commands in build.scala files. private[sbt] final object ProjectSettings extends AddSettings - /** Adds all settings from a plugin to a project. */ - val allPlugins: AddSettings = plugins(const(true)) - /** Adds all settings from autoplugins. */ - val autoPlugins: AddSettings = plugins(_.isInstanceOf[AutoPlugin]) + val autoPlugins: AddSettings = new AutoPlugins(const(true)) /** Settings specified in Build.scala `Project` constructors. */ val projectSettings: AddSettings = ProjectSettings /** All plugins that aren't auto plugins. */ - val nonAutoPlugins: AddSettings = plugins(!_.isInstanceOf[AutoPlugin]) + val nonAutoPlugins: AddSettings = plugins(const(true)) + + /** Adds all settings from a plugin to a project. */ + val allPlugins: AddSettings = seq(autoPlugins, nonAutoPlugins) /** Allows the plugins whose names match the `names` filter to automatically add settings to a project. */ def plugins(include: Plugin => Boolean): AddSettings = new Plugins(include) diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index b040ee0ae..a275c907a 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -505,19 +505,23 @@ object Load def loadSettingsFile(src: File): LoadedSbtFile = EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), loadedPlugins.detected.imports, 0)(loader) - import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence, ProjectSettings} + import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,AutoPlugins,Sequence, ProjectSettings} def pluginSettings(f: Plugins) = { val included = loadedPlugins.detected.plugins.values.filter(f.include) // don't apply the filter to AutoPlugins, only Plugins - val oldStyle = included.flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings) - val autoStyle = autoPlugins.flatMap(_.projectSettings) - oldStyle ++ autoStyle + included.flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings) } + // Filter the AutoPlugin settings we included based on which ones are + // intended in the AddSettings.AutoPlugins filter. + def autoPluginSettings(f: AutoPlugins) = + autoPlugins.filter(f.include).flatMap(_.projectSettings) + def expand(auto: AddSettings): LoadedSbtFile = auto match { case ProjectSettings => settings(projectSettings) case User => settings(injectSettings.projectLoaded(loader)) case sf: SbtFiles => loadSettings( sf.files.map(f => IO.resolve(projectBase, f))) case sf: DefaultSbtFiles => loadSettings( defaultSbtFiles.filter(sf.include)) case p: Plugins => settings(pluginSettings(p)) + case p: AutoPlugins => settings(autoPluginSettings(p)) case q: Sequence => (LoadedSbtFile.empty /: q.sequence) { (b,add) => b.merge( expand(add) ) } } expand(auto) diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index 5e814d082..a7ada9b6e 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -72,6 +72,24 @@ abstract class AutoPlugin extends Plugins.Basic // TODO?: def commands: Seq[Command] 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 + case _ => false + } +} +/** + * A root AutoPlugin is a plugin which must be explicitly enabled by users in their `setPlugins` 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 } /** An error that occurs when auto-plugins aren't configured properly. @@ -104,7 +122,10 @@ object Plugins 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)) ) + // Ignore clauses for plugins that just require themselves be specified. + // 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 { case Left(problem) => throw AutoPluginException(problem) From 3576baa76c8d0ac5e559e7a69ae4b89f94bbe057 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Mar 2014 17:43:49 -0500 Subject: [PATCH 04/10] Split Defaults.scala into three plugins: Global,Ivy,Jvm. * GlobalPlugin has defaults for controlling parallelism on tasks, basic command stuff. * IvyModule has the configuration for resolving/publishing modules to ivy, assuming each project is a single module. * JvmModule has the configuration for compiling/running/testing/packaging Java/Scala projects. --- main/src/main/scala/sbt/Build.scala | 11 +- main/src/main/scala/sbt/Defaults.scala | 195 +++++++++++------- main/src/main/scala/sbt/PluginDiscovery.scala | 10 +- main/src/main/scala/sbt/Project.scala | 6 +- .../main/scala/sbt/plugins/GlobalModule.scala | 19 ++ .../main/scala/sbt/plugins/IvyModule.scala | 24 +++ .../main/scala/sbt/plugins/JvmModule.scala | 35 ++++ 7 files changed, 218 insertions(+), 82 deletions(-) create mode 100644 main/src/main/scala/sbt/plugins/GlobalModule.scala create mode 100644 main/src/main/scala/sbt/plugins/IvyModule.scala create mode 100644 main/src/main/scala/sbt/plugins/JvmModule.scala diff --git a/main/src/main/scala/sbt/Build.scala b/main/src/main/scala/sbt/Build.scala index 7bcb704ec..030e54dfb 100644 --- a/main/src/main/scala/sbt/Build.scala +++ b/main/src/main/scala/sbt/Build.scala @@ -12,6 +12,7 @@ trait Build { def projectDefinitions(baseDirectory: File): Seq[Project] = projects def projects: Seq[Project] = ReflectUtilities.allVals[Project](this).values.toSeq + // TODO: Should we grab the build core setting shere or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil /** Explicitly defines the root project. @@ -46,8 +47,16 @@ object Build @deprecated("Explicitly specify the ID", "0.13.0") def defaultProject(base: File): Project = defaultProject(defaultID(base), base) def defaultProject(id: String, base: File): Project = Project(id, base).settings( + // TODO - Can we move this somewhere else? ordering of settings is causing this to get borked. // if the user has overridden the name, use the normal organization that is derived from the name. - organization <<= (thisProject, organization, name) { (p, o, n) => if(p.id == n) "default" else o } + organization := { + val overridden = thisProject.value.id == name.value + organization.?.value match { + case Some(o) if !overridden => o + case _ => "default" + } + //(thisProject, organization, name) { (p, o, n) => if(p.id == n) "default" else o } + } ) def defaultAggregatedProject(id: String, base: File, agg: Seq[ProjectRef]): Project = defaultProject(id, base).aggregate(agg : _*) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 9657c3644..e63ba2ed3 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -56,94 +56,107 @@ object Defaults extends BuildCommon def thisBuildCore: Seq[Setting[_]] = inScope(GlobalScope.copy(project = Select(ThisBuild)))(Seq( managedDirectory := baseDirectory.value / "lib_managed" )) + @deprecated("0.13.2", "Use AutoPlugins and globalSbtCore instead.") lazy val globalCore: Seq[Setting[_]] = globalDefaults(defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq( + excludeFilter :== HiddenFileFilter + ) ++ globalIvyCore ++ globalJvmCore) ++ globalSbtCore + + private[sbt] lazy val globalJvmCore: Seq[Setting[_]] = + Seq( compilerCache := state.value get Keys.stateCompilerCache getOrElse compiler.CompilerCache.fresh, - crossVersion :== CrossVersion.Disabled, + sourcesInBase :== true, + autoAPIMappings := false, + apiMappings := Map.empty, + autoScalaLibrary :== true, + managedScalaInstance :== true, + definesClass :== FileValueCache(Locate.definesClass _ ).get, + traceLevel in run :== 0, + traceLevel in runMain :== 0, + traceLevel in console :== Int.MaxValue, + traceLevel in consoleProject :== Int.MaxValue, + autoCompilerPlugins :== true, + scalaHome :== None, + apiURL := None, + javaHome :== None, + testForkedParallel :== false, + javaOptions :== Nil, + sbtPlugin :== false, + crossPaths :== true, + sourcePositionMappers :== Nil, + artifactClassifier in packageSrc :== Some(SourceClassifier), + artifactClassifier in packageDoc :== Some(DocClassifier), + includeFilter :== NothingFilter, + includeFilter in unmanagedSources :== "*.java" | "*.scala", + includeFilter in unmanagedJars :== "*.jar" | "*.so" | "*.dll" | "*.jnilib" | "*.zip", + includeFilter in unmanagedResources :== AllPassFilter + ) + + private[sbt] lazy val globalIvyCore: Seq[Setting[_]] = + Seq( + internalConfigurationMap :== Configurations.internalMap _, + credentials :== Nil, + exportJars :== false, + retrieveManaged :== false, scalaOrganization :== ScalaArtifacts.Organization, + sbtResolver := { if(sbtVersion.value endsWith "-SNAPSHOT") Classpaths.typesafeSnapshots else Classpaths.typesafeReleases }, + crossVersion :== CrossVersion.Disabled, buildDependencies <<= Classpaths.constructBuildDependencies, + version :== "0.1-SNAPSHOT", + classpathTypes :== Set("jar", "bundle") ++ CustomPomParser.JarPackagings, + artifactClassifier :== None, + checksums := Classpaths.bootChecksums(appConfiguration.value), + conflictManager := ConflictManager.default, + pomExtra :== NodeSeq.Empty, + pomPostProcess :== idFun, + pomAllRepositories :== false, + pomIncludeRepository :== Classpaths.defaultRepositoryFilter + ) + + /** Core non-plugin settings for sbt builds. These *must* be on every build or the sbt engine will fail to run at all. */ + private[sbt] lazy val globalSbtCore: Seq[Setting[_]] = globalDefaults(Seq( + outputStrategy :== None, // TODO - This might belong elsewhere. + buildStructure := Project.structure(state.value), + settingsData := buildStructure.value.data, + trapExit :== true, + connectInput :== false, + cancelable :== false, + envVars :== Map.empty, + sbtVersion := appConfiguration.value.provider.id.version, + sbtBinaryVersion := binarySbtVersion(sbtVersion.value), + watchingMessage := Watched.defaultWatchingMessage, + triggeredMessage := Watched.defaultTriggeredMessage, + onLoad := idFun[State], + onUnload := idFun[State], + onUnload := { s => try onUnload.value(s) finally IO.delete(taskTemporaryDirectory.value) }, + extraLoggers :== { _ => Nil }, + watchSources :== Nil, + skip :== false, taskTemporaryDirectory := { val dir = IO.createTemporaryDirectory; dir.deleteOnExit(); dir }, onComplete := { val dir = taskTemporaryDirectory.value; () => {IO.delete(dir); IO.createDirectory(dir) }}, Previous.cache <<= Previous.cacheSetting, Previous.references :== new Previous.References, concurrentRestrictions <<= defaultRestrictions, parallelExecution :== true, - sbtVersion := appConfiguration.value.provider.id.version, - sbtBinaryVersion := binarySbtVersion(sbtVersion.value), - sbtResolver := { if(sbtVersion.value endsWith "-SNAPSHOT") Classpaths.typesafeSnapshots else Classpaths.typesafeReleases }, pollInterval :== 500, logBuffered :== false, - connectInput :== false, - cancelable :== false, - envVars :== Map.empty, - sourcesInBase :== true, - autoAPIMappings := false, - apiMappings := Map.empty, - autoScalaLibrary :== true, - managedScalaInstance :== true, - onLoad := idFun[State], - onUnload := idFun[State], - onUnload := { s => try onUnload.value(s) finally IO.delete(taskTemporaryDirectory.value) }, - watchingMessage := Watched.defaultWatchingMessage, - triggeredMessage := Watched.defaultTriggeredMessage, - definesClass :== FileValueCache(Locate.definesClass _ ).get, - trapExit :== true, - traceLevel in run :== 0, - traceLevel in runMain :== 0, - traceLevel in console :== Int.MaxValue, - traceLevel in consoleProject :== Int.MaxValue, - autoCompilerPlugins :== true, - internalConfigurationMap :== Configurations.internalMap _, - initialize :== {}, - credentials :== Nil, - scalaHome :== None, - apiURL := None, - javaHome :== None, - extraLoggers :== { _ => Nil }, - skip :== false, - watchSources :== Nil, - version :== "0.1-SNAPSHOT", - outputStrategy :== None, - exportJars :== false, - fork :== false, - testForkedParallel :== false, - javaOptions :== Nil, - sbtPlugin :== false, - crossPaths :== true, - classpathTypes :== Set("jar", "bundle") ++ CustomPomParser.JarPackagings, - aggregate :== true, - maxErrors :== 100, - sourcePositionMappers :== Nil, + commands :== Nil, + showSuccess :== true, showTiming :== true, timingFormat :== Aggregation.defaultFormat, - showSuccess :== true, - commands :== Nil, - retrieveManaged :== false, - buildStructure := Project.structure(state.value), - settingsData := buildStructure.value.data, - artifactClassifier :== None, - artifactClassifier in packageSrc :== Some(SourceClassifier), - artifactClassifier in packageDoc :== Some(DocClassifier), - checksums := Classpaths.bootChecksums(appConfiguration.value), - conflictManager := ConflictManager.default, - pomExtra :== NodeSeq.Empty, - pomPostProcess :== idFun, - pomAllRepositories :== false, - includeFilter :== NothingFilter, - includeFilter in unmanagedSources :== "*.java" | "*.scala", - includeFilter in unmanagedJars :== "*.jar" | "*.so" | "*.dll" | "*.jnilib" | "*.zip", - includeFilter in unmanagedResources :== AllPassFilter, - excludeFilter :== HiddenFileFilter, - pomIncludeRepository :== Classpaths.defaultRepositoryFilter + aggregate :== true, + maxErrors :== 100, + fork :== false, + initialize :== {} )) def defaultTestTasks(key: Scoped): Seq[Setting[_]] = inTask(key)(Seq( tags := Seq(Tags.Test -> 1), logBuffered := true )) + // TODO: This should be on the new default settings for a project. def projectCore: Seq[Setting[_]] = Seq( name := thisProject.value.id, logManager := LogManager.defaults(extraLoggers.value, StandardMain.console), - onLoadMessage <<= onLoadMessage or (name, thisProjectRef)("Set current project to " + _ + " (in build " + _.build +")"), - runnerTask + onLoadMessage <<= onLoadMessage or (name, thisProjectRef)("Set current project to " + _ + " (in build " + _.build +")") ) def paths = Seq( baseDirectory := thisProject.value.base, @@ -852,6 +865,7 @@ object Defaults extends BuildCommon lazy val disableAggregation = Defaults.globalDefaults( noAggregation map disableAggregate ) def disableAggregate(k: Scoped) = aggregate in k :== false + lazy val runnerSettings: Seq[Setting[_]] = Seq(runnerTask) lazy val baseTasks: Seq[Setting[_]] = projectTasks ++ packageBase lazy val baseClasspaths: Seq[Setting[_]] = Classpaths.publishSettings ++ Classpaths.baseSettings @@ -865,7 +879,12 @@ object Defaults extends BuildCommon // settings that are not specific to a configuration - lazy val projectBaseSettings: Seq[Setting[_]] = projectCore ++ paths ++ baseClasspaths ++ baseTasks ++ compileBase ++ disableAggregation + @deprecated("0.13.2", "Settings now split into AutoPlugins.") + lazy val projectBaseSettings: Seq[Setting[_]] = projectCore ++ runnerSettings ++ paths ++ baseClasspaths ++ baseTasks ++ compileBase ++ disableAggregation + + // These are project level settings that MUST be on every project. + lazy val coreDefaultSettings: Seq[Setting[_]] = projectCore ++ disableAggregation + @deprecated("0.13.2", "Default settings split into `coreDefaultSettings` and IvyModule/JvmModule plugins.") lazy val defaultSettings: Seq[Setting[_]] = projectBaseSettings ++ defaultConfigs } object Classpaths @@ -935,9 +954,14 @@ object Classpaths publishArtifact in Test:== false )) - val publishSettings: Seq[Setting[_]] = publishGlobalDefaults ++ Seq( - artifacts <<= artifactDefs(defaultArtifactTasks), - packagedArtifacts <<= packaged(defaultArtifactTasks), + val jvmPublishSettings: Seq[Setting[_]] = Seq( + artifacts <<= artifactDefs(defaultArtifactTasks), + packagedArtifacts <<= packaged(defaultArtifactTasks) + ) + + val ivyPublishSettings: Seq[Setting[_]] = publishGlobalDefaults ++ Seq( + artifacts :== Nil, + packagedArtifacts :== Map.empty, makePom := { val config = makePomConfiguration.value; IvyActions.makePom(ivyModule.value, config, streams.value.log); config.file }, packagedArtifact in makePom := (artifact in makePom value, makePom value), deliver <<= deliverTask(deliverConfiguration), @@ -946,6 +970,8 @@ object Classpaths publishLocal <<= publishTask(publishLocalConfiguration, deliverLocal), publishM2 <<= publishTask(publishM2Configuration, deliverLocal) ) + @deprecated("0.13.2", "This has been split into jvmIvySettings and ivyPublishSettings.") + val publishSettings: Seq[Setting[_]] = jvmPublishSettings ++ ivyPublishSettings private[this] def baseGlobalDefaults = Defaults.globalDefaults(Seq( conflictWarning :== ConflictWarning.default("global"), @@ -976,7 +1002,7 @@ object Classpaths } )) - val baseSettings: Seq[Setting[_]] = baseGlobalDefaults ++ sbtClassifiersTasks ++ Seq( + val ivyBaseSettings: Seq[Setting[_]] = baseGlobalDefaults ++ sbtClassifiersTasks ++ Seq( conflictWarning := conflictWarning.value.copy(label = Reference.display(thisProjectRef.value)), unmanagedBase := baseDirectory.value / "lib", normalizedName := Project.normalizeModuleID(name.value), @@ -1007,14 +1033,11 @@ object Classpaths otherResolvers := Resolver.publishMavenLocal :: publishTo.value.toList, projectResolver <<= projectResolverTask, projectDependencies <<= projectDependenciesTask, - libraryDependencies ++= autoLibraryDependency(autoScalaLibrary.value && !scalaHome.value.isDefined && managedScalaInstance.value, sbtPlugin.value, scalaOrganization.value, scalaVersion.value), + // TODO - Is this the appropriate split? Ivy defines this simply as + // just project + library, while the JVM plugin will define it as + // having the additional sbtPlugin + autoScala magikz. allDependencies := { - val base = projectDependencies.value ++ libraryDependencies.value - val pluginAdjust = if(sbtPlugin.value) sbtDependency.value.copy(configurations = Some(Provided.name)) +: base else base - if(scalaHome.value.isDefined || ivyScala.value.isEmpty || !managedScalaInstance.value) - pluginAdjust - else - ScalaArtifacts.toolDependencies(scalaOrganization.value, scalaVersion.value) ++ pluginAdjust + projectDependencies.value ++ libraryDependencies.value }, ivyScala <<= ivyScala or (scalaHome, scalaVersion in update, scalaBinaryVersion in update, scalaOrganization) { (sh,fv,bv,so) => Some(new IvyScala(fv, bv, Nil, filterImplicit = false, checkExplicit = true, overrideScalaVersion = false, scalaOrganization = so)) @@ -1054,6 +1077,22 @@ object Classpaths } } tag(Tags.Update, Tags.Network) ) + + val jvmBaseSettings: Seq[Setting[_]] = Seq( + libraryDependencies ++= autoLibraryDependency(autoScalaLibrary.value && !scalaHome.value.isDefined && managedScalaInstance.value, sbtPlugin.value, scalaOrganization.value, scalaVersion.value), + // Override the default to handle mixing in the sbtPlugin + scala dependencies. + allDependencies := { + val base = projectDependencies.value ++ libraryDependencies.value + val pluginAdjust = if(sbtPlugin.value) sbtDependency.value.copy(configurations = Some(Provided.name)) +: base else base + if(scalaHome.value.isDefined || ivyScala.value.isEmpty || !managedScalaInstance.value) + pluginAdjust + else + ScalaArtifacts.toolDependencies(scalaOrganization.value, scalaVersion.value) ++ pluginAdjust + } + ) + @deprecated("0.13.2", "Split into ivyBaseSettings and jvmBaseSettings.") + val baseSettings: Seq[Setting[_]] = ivyBaseSettings ++ jvmBaseSettings + def warnResolversConflict(ress: Seq[Resolver], log: Logger) { val resset = ress.toSet for ((name, r) <- resset groupBy (_.name) if r.size > 1) { diff --git a/main/src/main/scala/sbt/PluginDiscovery.scala b/main/src/main/scala/sbt/PluginDiscovery.scala index 0d49e6fd7..ae945f78a 100644 --- a/main/src/main/scala/sbt/PluginDiscovery.scala +++ b/main/src/main/scala/sbt/PluginDiscovery.scala @@ -28,7 +28,15 @@ object PluginDiscovery def discover[T](resource: String)(implicit mf: reflect.ClassManifest[T]) = binarySourceModules[T](data, loader, resource) import Paths._ - new DetectedPlugins(discover[Plugin](Plugins), discover[AutoImport](AutoImports), discover[AutoPlugin](AutoPlugins), discover[Build](Builds)) + // TODO - Fix this once we can autodetect AutoPlugins defined by sbt itself. + val defaultAutoPlugins = Seq( + "sbt.plugins.IvyModule" -> sbt.plugins.IvyModule, + "sbt.plugins.JvmModule" -> sbt.plugins.JvmModule, + "sbt.plugins.GlobalModule" -> sbt.plugins.GlobalModule + ) + val detectedAutoPugins = discover[AutoPlugin](AutoPlugins) + val allAutoPlugins = new DetectedModules(defaultAutoPlugins ++ detectedAutoPugins.modules) + new DetectedPlugins(discover[Plugin](Plugins), discover[AutoImport](AutoImports), allAutoPlugins, discover[Build](Builds)) } /** Discovers the sbt-plugin-related top-level modules from the provided source `analysis`. */ diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index a44598e94..7604c9d27 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -203,10 +203,11 @@ object Project extends ProjectExtra } // TODO: add parameter for plugins in 0.14.0 + // TODO: Modify default settings to be the core settings, and automatically add the IvyModule + JvmPlugins. 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, + delegates: => Seq[ProjectReference] = Nil, settings: => Seq[Def.Setting[_]] = Nil, configurations: Seq[Configuration] = Nil, auto: AddSettings = AddSettings.allDefaults): Project = - unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil) + unresolved(id, base, aggregate, dependencies, delegates, settings, configurations, auto, Plugins.empty, Nil) // Note: JvmModule/IvyModule auto included... /** 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 @@ -243,6 +244,7 @@ object Project extends ProjectExtra new ProjectDef[ProjectReference](id, base, aggregate, dependencies, delegates, settings, configurations, auto, plugins, autoPlugins) with Project } + @deprecated("0.13.2", "Use Defaults.coreDefaultSettings instead, combined with AutoPlugins.") def defaultSettings: Seq[Def.Setting[_]] = Defaults.defaultSettings final class Constructor(p: ProjectReference) { diff --git a/main/src/main/scala/sbt/plugins/GlobalModule.scala b/main/src/main/scala/sbt/plugins/GlobalModule.scala new file mode 100644 index 000000000..570cbc80f --- /dev/null +++ b/main/src/main/scala/sbt/plugins/GlobalModule.scala @@ -0,0 +1,19 @@ +package sbt +package plugins + +import Def.Setting + +/** + * Plugin for core sbt-isms. + * + * Can control task-level paralleism, logging, etc. + */ +object GlobalModule extends AutoPlugin { + // We must be explicitly enabled + def select = Plugins.empty + + override lazy val projectSettings: Seq[Setting[_]] = + Defaults.coreDefaultSettings + override lazy val globalSettings: Seq[Setting[_]] = + Defaults.globalSbtCore +} \ No newline at end of file diff --git a/main/src/main/scala/sbt/plugins/IvyModule.scala b/main/src/main/scala/sbt/plugins/IvyModule.scala new file mode 100644 index 000000000..6ce0d9a9d --- /dev/null +++ b/main/src/main/scala/sbt/plugins/IvyModule.scala @@ -0,0 +1,24 @@ +package sbt +package plugins + +import Def.Setting + +/** + * Plugin that enables resolving artifacts via ivy. + * + * Core Tasks + * - `update` + * - `makePom` + * - `publish` + * - `artifacts` + * - `publishedArtifacts` + */ +object IvyModule extends AutoPlugin { + // We must be explicitly enabled + def select = GlobalModule + + override lazy val projectSettings: Seq[Setting[_]] = + Classpaths.ivyPublishSettings ++ Classpaths.ivyBaseSettings + override lazy val globalSettings: Seq[Setting[_]] = + Defaults.globalIvyCore +} \ No newline at end of file diff --git a/main/src/main/scala/sbt/plugins/JvmModule.scala b/main/src/main/scala/sbt/plugins/JvmModule.scala new file mode 100644 index 000000000..6dd95d9c0 --- /dev/null +++ b/main/src/main/scala/sbt/plugins/JvmModule.scala @@ -0,0 +1,35 @@ +package sbt +package plugins + +import Def.Setting + +/** A plugin representing the ability to build a JVM project. + * + * Core tasks/keys: + * - `run` + * - `test` + * - `compile` + * - `fullClasspath` + * Core configurations + * - `Test` + * - `Compile` + */ +object JvmModule extends AutoPlugin { + // We must be explicitly enabled + def select = IvyModule + + override lazy val projectSettings: Seq[Setting[_]] = + Defaults.runnerSettings ++ + Defaults.paths ++ + Classpaths.jvmPublishSettings ++ + Classpaths.jvmBaseSettings ++ + Defaults.projectTasks ++ + Defaults.packageBase ++ + Defaults.compileBase ++ + Defaults.defaultConfigs + override lazy val globalSettings: Seq[Setting[_]] = + Defaults.globalJvmCore + + override def projectConfigurations: Seq[Configuration] = + Configurations.default +} \ No newline at end of file From 01bb7ce2fdb3cb73742ca4988e4a2414a48b8ff2 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 5 Mar 2014 22:59:30 -0500 Subject: [PATCH 05/10] Remove defaultSettings usage from the docs. Given the addition/promotion of AutoPlugins, remove the references to the previously necessary Defaults.defaultSettings method. --- .../Examples/Full-Configuration-Example.rst | 2 +- src/sphinx/Getting-Started/Full-Def.rst | 2 +- src/sphinx/faq.rst | 18 +++++++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/sphinx/Examples/Full-Configuration-Example.rst b/src/sphinx/Examples/Full-Configuration-Example.rst index c84bb5a49..132dad7e0 100644 --- a/src/sphinx/Examples/Full-Configuration-Example.rst +++ b/src/sphinx/Examples/Full-Configuration-Example.rst @@ -16,7 +16,7 @@ into multiple files. val buildVersion = "2.0.29" val buildScalaVersion = "2.9.0-1" - val buildSettings = Defaults.defaultSettings ++ Seq ( + val buildSettings = Seq ( organization := buildOrganization, version := buildVersion, scalaVersion := buildScalaVersion, diff --git a/src/sphinx/Getting-Started/Full-Def.rst b/src/sphinx/Getting-Started/Full-Def.rst index 5c104e2b4..0d1ac25bd 100644 --- a/src/sphinx/Getting-Started/Full-Def.rst +++ b/src/sphinx/Getting-Started/Full-Def.rst @@ -113,7 +113,7 @@ The following two files illustrate. First, if your project is in lazy val root = Project(id = "hello", base = file("."), - settings = Project.defaultSettings ++ Seq(sampleKeyB := "B: in the root project settings in Build.scala")) + settings = Seq(sampleKeyB := "B: in the root project settings in Build.scala")) } Now, create `hello/build.sbt` as follows: diff --git a/src/sphinx/faq.rst b/src/sphinx/faq.rst index 29738dab8..4ce8645f8 100644 --- a/src/sphinx/faq.rst +++ b/src/sphinx/faq.rst @@ -444,24 +444,28 @@ before it is initialized with an empty sequence. settings = Seq( libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test" ) - ) + ).disablePlugins(plugins.IvyModule) } -To correct this, include the default settings, which includes -`libraryDependencies := Seq()`. +To correct this, include the IvyModule plugin settings, which includes +`libraryDependencies := Seq()`. So, we just drop the explicit disabling. :: - settings = Defaults.defaultSettings ++ Seq( - libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test" - ) + object MyBuild extends Build { + val root = Project(id = "root", base = file("."), + settings = Seq( + libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test" + ) + ) + } A more subtle variation of this error occurs when using :doc:`scoped settings `. :: // error: Reference to uninitialized setting - settings = Defaults.defaultSettings ++ Seq( + settings = Seq( libraryDependencies += "commons-io" % "commons-io" % "1.2" % "test", fullClasspath := fullClasspath.value.filterNot(_.data.name.contains("commons-io")) ) From ea8c0b32a71d2d63a55809bd98666c5cf1bd05c4 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 6 Mar 2014 13:57:07 -0500 Subject: [PATCH 06/10] Update documentation for AutoPlugins. * Add notes about AutoPlugins vs. RootPlugins in Plugins section * Modify best practices section to recomend using AutoPlugins. * Modify Using-Plugins section in getting started to denote auto plugins. --- .../Extending/Plugins-Best-Practices.rst | 18 ++--- src/sphinx/Extending/Plugins.rst | 67 +++++++++++++++---- src/sphinx/Getting-Started/Using-Plugins.rst | 37 ++++++++-- 3 files changed, 92 insertions(+), 30 deletions(-) diff --git a/src/sphinx/Extending/Plugins-Best-Practices.rst b/src/sphinx/Extending/Plugins-Best-Practices.rst index 6b4d80564..c7adb5655 100644 --- a/src/sphinx/Extending/Plugins-Best-Practices.rst +++ b/src/sphinx/Extending/Plugins-Best-Practices.rst @@ -22,20 +22,12 @@ Don't use default package Users who have their build files in some package will not be able to use your plugin if it's defined in default (no-name) package. -Avoid overriding `settings` ------------------------------ +Avoid older `sbt.Plugin` mechanism +---------------------------------- -sbt will automatically load your plugin's `settings` into the build. -Overriding `val settings` should only be done by plugins intending to -provide commands. Regular plugins defining tasks and settings should -provide a sequence named after the plugin like so: - -:: - - val obfuscateSettings = Seq(...) - -This allows build user to choose which subproject the plugin would be -used. See later section for how the settings should be scoped. +sbt has deprecated the old `sbt.Plugin` mechanism in favor of `sbt.AutoPlugin`. +The new mechanism features a set of user-level controls and dependency declarations +that cleans up a lot of long-standing issues with plugins. Reuse existing keys ------------------- diff --git a/src/sphinx/Extending/Plugins.rst b/src/sphinx/Extending/Plugins.rst index 18cae5ee6..3d510a8ba 100644 --- a/src/sphinx/Extending/Plugins.rst +++ b/src/sphinx/Extending/Plugins.rst @@ -176,6 +176,10 @@ It is recommended to explicitly specify the commit or tag by appending it to the lazy val assemblyPlugin = uri("git://github.com/sbt/sbt-assembly#0.9.1") +One caveat to using this method is that the local sbt will try to run the remote plugin's build. It +is quite possible that the plugin's own build uses a different sbt version, as many plugins cross-publish for +several sbt versions. As such, it is recommended to stick with binary artifacts when possible. + 2) Use the library ~~~~~~~~~~~~~~~~~~ @@ -221,22 +225,25 @@ To make a plugin, create a project and configure `sbtPlugin` to `true`. Then, write the plugin code and publish your project to a repository. The plugin can be used as described in the previous section. -A plugin can implement `sbt.Plugin`. The contents of a Plugin -singleton, declared like `object MyPlugin extends Plugin`, are +A plugin can implement `sbt.AutoImpot`. The contents of an AutoImport +singleton, declared like `object MyPlugin extends AutoImport`, are wildcard imported in `set`, `eval`, and `.sbt` files. Typically, this is used to provide new keys (SettingKey, TaskKey, or InputKey) or core methods without requiring an import or qualification. -In addition, a `Plugin` can implement `projectSettings`, `buildSettings`, and `globalSettings` as appropriate. -The Plugin's `projectSettings` is automatically appended to each project's settings. +In addition, a plugin can implement the `AutoPlugin` class. This has additoinal features, such as + +* Specifying plugin dependencies. +* 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 `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. One main use of this feature is to globally add commands, such as for IDE plugins. Use `globalSettings` to define the default value of a setting. -These automatic features should be used judiciously because the automatic activation generally reduces control for the build author (the user of the plugin). -Some control is returned to them via `Project.autoSettings`, which changes how automatically added settings are added and in what order. Example Plugin -------------- @@ -258,16 +265,18 @@ An example of a typical plugin: :: import sbt._ - object MyPlugin extends Plugin + object MyPlugin extends AutoPlugin { + // Only enable this plugin for projects which are JvmModules. + def select = 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.") val newSetting = settingKey[String]("A new setting.") - // a group of settings ready to be added to a Project - // to automatically add them, do - val newSettings = Seq( + // a group of settings that are automatically added to projects. + val projectSettings = Seq( newSetting := "test", newTask := println(newSetting.value) ) @@ -289,7 +298,17 @@ A build definition that uses the plugin might look like: newSetting := "example" -Example command plugin + +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. + + +Example command root plugin ---------------------- A basic plugin that adds commands looks like: @@ -310,9 +329,9 @@ A basic plugin that adds commands looks like: import sbt._ import Keys._ - object MyPlugin extends Plugin + object MyPlugin extends RootPlugin { - override lazy val settings = Seq(commands += myCommand) + override lazy val projectSettings = Seq(commands += myCommand) lazy val myCommand = Command.command("hello") { (state: State) => @@ -327,6 +346,28 @@ included in one plugin (for example, use `commands ++= Seq(a,b)`). See :doc:`Commands` for defining more useful commands, including ones that accept arguments and affect the execution state. +For a user to consume this plugin, it requires an explicit include via the `Project` instance. +Here's what their local sbt will look like. + +`build.sbt` + +:: + + val root = Project("example-plugin-usage", file(".")).setPlugins(MyPlugin) + + +The `setPlugins` method allows projects to explicitly define the `RootPlugin`s they wish to consume. +`AutoPlugin`s are automatically added to the project as appropriate. + +Projects can also exclude any type of plugin using the `disablePlugins` method. For example, if +we wish to remove the JvmModule settings (`compile`,`test`,`run`), we modify our `build.sbt` as +follows: + +:: + + val root = Project("example-plugin-usage", file(".")).setPlugins(MyPlugin).disablePlugins(plugins.JvmModule) + + Global plugins example ---------------------- diff --git a/src/sphinx/Getting-Started/Using-Plugins.rst b/src/sphinx/Getting-Started/Using-Plugins.rst index 5dfc05db9..57dcc0a1d 100644 --- a/src/sphinx/Getting-Started/Using-Plugins.rst +++ b/src/sphinx/Getting-Started/Using-Plugins.rst @@ -34,8 +34,36 @@ Adding settings for a plugin ---------------------------- A plugin can declare that its settings be automatically added, in which case you don't have to do anything to add them. -However, plugins often avoid this because you wouldn't control which projects in a :doc:`multi-project build ` would use the plugin. -The plugin documentation will indicate how to configure it, but typically it involves adding the base settings for the plugin and customizing as necessary. + +As of sbt 0.13.2, there is a new :doc:`auto-plugins <../DetailedTopics/AutoPlugins>` feature that enables plugins +to automatically, and safely, ensure their settings and dependencies are on a project. Most plugins should have +their default settings automatically, however some may require explicit enablement. + +If you're using a plugin that requires explicit enablement, then you you have to add the following to your +`build.sbt` :: + + lazy val util = project.setPlugins(ThePluginIWant) + +Most plugins document whether they need to explicitly enabled. If you're curious which plugins are enabled +for a given project, just run the `plugins` command on the sbt console. + +For example :: + + > plugins + In file:/home/jsuereth/projects/sbt/test-ivy-issues/ + sbt.plugins.IvyModule: enabled in test-ivy-issues + sbt.plugins.JvmModule: enabled in test-ivy-issues + sbt.plugins.GlobalModule: enabled in test-ivy-issues + + +Here, the plugins output is showing that the sbt default plugins are all enabled. Sbt's default settings are provided via three plugins: + +1. GlobalModule: Provides the core parallelism controls for tasks +2. IvyModule: Provides the mechanisms to publish/resolve modules. +3. JvmModule: Provides the mechanisms to compile/test/run/package Java/Scala projects. + + +However, older plugins often required settings to be added explictly, so that :doc:`multi-project build ` could have different types of projects. The plugin documentation will indicate how to configure it, but typically for older plugins this involves adding the base settings for the plugin and customizing as necessary. For example, for the sbt-site plugin, add :: @@ -91,9 +119,10 @@ 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. (optional) Define an `object` that extends `Plugin`. The contents of this object will be automatically imported in `.sbt` files, so ensure it only contains important API definitions and types. + 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 Plugin's methods to have settings automatically added to user projects. + 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. For more details, including ways of developing plugins, see :doc:`/Extending/Plugins`. From 548b38c7f82232d86928dede7989a96a2197e12f Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 7 Mar 2014 08:52:05 -0500 Subject: [PATCH 07/10] Add note about not exposing fine-grained autoplugin inclusion controlls. AddSettings should only expose coarse-grained features of AutoPlugins or else the Logic we use to ensure safe addition completely breaks down. Leaving it in the code as an escape hatch if we get desparate, but we need an alternative for controlling ordering later. --- main/src/main/scala/sbt/AddSettings.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/AddSettings.scala b/main/src/main/scala/sbt/AddSettings.scala index ceb0bc751..9677af329 100644 --- a/main/src/main/scala/sbt/AddSettings.scala +++ b/main/src/main/scala/sbt/AddSettings.scala @@ -19,7 +19,12 @@ object AddSettings private[sbt] final object ProjectSettings extends AddSettings /** Adds all settings from autoplugins. */ - val autoPlugins: AddSettings = new AutoPlugins(const(true)) + val autoPlugins: AddSettings = new AutoPlugins(const(true)) // Note: We do not expose fine-grained autoplugins because + // it's dangerous to control at that level right now. + // Leaving the hook in place in case we need to expose + // it, but most likely it will remain locked out + // for users with an alternative ordering feature + // in place. /** Settings specified in Build.scala `Project` constructors. */ val projectSettings: AddSettings = ProjectSettings From 041bc4bf01d049e30ac249c62f2039b8f224cedc Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 10 Mar 2014 12:45:35 -0400 Subject: [PATCH 08/10] Add documentation for AddSettigns and load ordering. * Add Architecture setting. * Cover how settings are loaded and ordered * Show basic controls on setting ordreing. --- .../Architecture/Setting-Initialization.rst | 97 ++++++++++++++++++ src/sphinx/Architecture/index.rst | 13 +++ .../settings-initialization-load-ordering.png | Bin 0 -> 83259 bytes src/sphinx/Detailed-Topics/index.rst | 1 + 4 files changed, 111 insertions(+) create mode 100644 src/sphinx/Architecture/Setting-Initialization.rst create mode 100644 src/sphinx/Architecture/index.rst create mode 100644 src/sphinx/Architecture/settings-initialization-load-ordering.png diff --git a/src/sphinx/Architecture/Setting-Initialization.rst b/src/sphinx/Architecture/Setting-Initialization.rst new file mode 100644 index 000000000..d636e598c --- /dev/null +++ b/src/sphinx/Architecture/Setting-Initialization.rst @@ -0,0 +1,97 @@ +====================== +Setting Initialization +====================== + +This page outlines the mechanisms by which sbt loads settings for a particular build, including the hooks where +users can control the ordering of everything. + +As stated elsewhere, sbt constructs its initialization graph and task graph via ``Setting[_]`` objects. A setting +is something which can take the values stored at other Keys in the build state, and generates a new value for +a particular build key. Sbt converts all registered ``Setting[_]`` objects into a giant linear sequence and +*compiles* them into the a task graph. This task graph is then used to execute your build. + +All of sbt's loading semantics are contained within the `Load.scala <../../sxr/sbt/Load.scala.html>` file. It is approximately the following: + +.. image:: settings-initialization-load-ordering.png + +The blue circles represent actions happening when sbt loads a project. We can see that sbt performs the following actions in load: + +1. Compile the user-level project (``~/.sbt//``) + a. Load any plugins defined by this project (``~/.sbt//plugins/*.sbt`` and ``~/.sbt//plugins/project/*.scala``) + b. Load all settings defined (``~/.sbt//*.sbt`` and ``~/.sbt//plugins/*.scala``) +2. Compile the current project (``/*.sbt``) +4. All local configurations (``build.sbt``) + + + +Controlling Initialization +========================== + +The order which sbt uses to load settings is configurable at a *project* level. This means that we can't control +the order of settings added to Build/Global namespace, but we can control how each project loads, e.g. plugins and ``.sbt`` files. +To do so, use the ``AddSettings`` class :: + + + import sbt._ + import Keys._ + + import AddSettings._ + + object MyOwnOrder extends Build { + // here we load config from a txt file. + lazy val root = project.in(file(".")).autoSettings( autoPlugins, projectSettings, sbtFiles(file("silly.txt")) ) + } + +In the above project, we've modified the order of settings to be: + +1. All AutoPlugin settings. +2. All settings defined in the ``project/Build.scala`` file (shown above). +3. All settings found in the ``silly.txt`` file. + +What we've excluded: + +* All settings from the user directory (``~/.sbt/``) +* All ``*.sbt`` settings. + +The AddSettings object provides the following "groups" of settings you can use for ordering: + +``autoPlugins`` + All the ordered settings of plugins after they've gone through dependency resolution +``projectSettings`` + The full sequence of settings defined directly in ``project/*.scala`` builds. +``sbtFiles(*)`` + Specifies the exact setting DSL files to include (files must use the ``.sbt`` file format) +``userSettings`` + All the settings defined in the user directory ``~/.sbt//``. +``defaultSbtFiles`` + Include all local ``*.sbt`` file settings. + + +*Note: Be very careful when reordering settings. It's easy to accidentally remove core functionality.* \ No newline at end of file diff --git a/src/sphinx/Architecture/index.rst b/src/sphinx/Architecture/index.rst new file mode 100644 index 000000000..d20bce232 --- /dev/null +++ b/src/sphinx/Architecture/index.rst @@ -0,0 +1,13 @@ +============== + Architecture +============== + +This is the fledgeling set of documentation about the Architecture of sbt. This will cover all the core components of +sbt as well as the general notion of how they all work together. This documentation is suitable for those who wish to +have a deeper understanding of sbt's core, but already understand the fundamentals of ``Setting[_]``, ``Task[_]`` and +constructing builds. + +.. toctree:: + :maxdepth: 2 + + Setting-Initialization \ No newline at end of file diff --git a/src/sphinx/Architecture/settings-initialization-load-ordering.png b/src/sphinx/Architecture/settings-initialization-load-ordering.png new file mode 100644 index 0000000000000000000000000000000000000000..82055d7d7221bf9577ed747456c8ea1c75de33ac GIT binary patch literal 83259 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 and Tasks-and-Commands Plugins-and-Best-Practices Advanced-Index + /Architecture/index /Launcher/index From af25df1777dd5e64091cdd62983371008cfe5985 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 10 Mar 2014 14:22:56 -0400 Subject: [PATCH 09/10] Fix @retronym's comments * Fill out more details in archiecture section. * Leave sections blank we don't have time to write. * Consolidate on `Sub project` vernacular instead of module. * Add a few examples to make statements concrete. --- src/sphinx/Architecture/Command-Engine.rst | 5 ++ src/sphinx/Architecture/Core-Principles.rst | 70 +++++++++++++++++++ .../Architecture/Setting-Initialization.rst | 42 +++++++++-- src/sphinx/Architecture/Task-Engine.rst | 5 ++ src/sphinx/Architecture/index.rst | 7 +- 5 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/sphinx/Architecture/Command-Engine.rst create mode 100644 src/sphinx/Architecture/Core-Principles.rst create mode 100644 src/sphinx/Architecture/Task-Engine.rst diff --git a/src/sphinx/Architecture/Command-Engine.rst b/src/sphinx/Architecture/Command-Engine.rst new file mode 100644 index 000000000..5388dbbae --- /dev/null +++ b/src/sphinx/Architecture/Command-Engine.rst @@ -0,0 +1,5 @@ +================= + Command Engine +================= + +Placeholder for command engine details. \ No newline at end of file diff --git a/src/sphinx/Architecture/Core-Principles.rst b/src/sphinx/Architecture/Core-Principles.rst new file mode 100644 index 000000000..2f101c6ee --- /dev/null +++ b/src/sphinx/Architecture/Core-Principles.rst @@ -0,0 +1,70 @@ +================= + Core Principles +================= + +This document details the core principles overarching sbt's design and code style. Sbt's core principles can +be stated quite simply: + +1. Everything should have a ``Type``, enforced as much as is practical. +2. Dependencies should be **explicit**. +3. Once learned, a concept should hold throughout **all** parts of sbt. +4. Parallel is the default. + +With these principles in mind, let's walk through the core design of sbt. + + +Introduction to build state +=========================== +This is the first piece you hit when starting sbt. Sbt's command engine is the means by which +it processes user requests using the build state. The command engine is essentially a means of applying +**state transformations** on the build state, to execute user requests. + +In sbt, commands are functions that take the current build state (``sbt.State``) and produce the next state. In +other words, they are essentially functions of ``sbt.State => sbt.State``. However, in reality, Commands are +actually string processors which take some string input and act on it, returning the next build state. + +The details of the command engine are covered in :doc:`the command engine section `. + +So, the entirety of sbt is driven off the ``sbt.State`` class. Since this class needs to be resilient in the +face of custom code and plugins, it needs a mechanism to store the state from any potential client. In +dynamic languages, this can be done directly on objects. + +A naive approach in Scala is to use a ``Map``. However, this vioaltes tennant #1: Everythign should have a `Type`. +So, sbt defines a new type of map called an ``AttributeMap``. An ``AttributeMap`` is a key-value storage mechanism where +keys are both strings *and* expected `Type`s for their value. + +Here is what the typesafe ``AttributeKey`` key looks like :: + + sealed trait AttributeKey[T] { + /** The label is the identifier for the key and is camelCase by convention. */ + def label: String + /** The runtime evidence for `T` */ + def manifest: Manifest[T] + } + +These keys store both a `label` (``string``) and some runtime type information (``manifest``). To put or get something on +the AttributeMap, we first need to construct one of these keys. Let's look at the basic definition of the ``AttributeMap`` :: + + trait AttributeMap { + /** Gets the value of type `T` associated with the key `k` or `None` if no value is associated. + * If a key with the same label but a different type is defined, this method will return `None`. */ + def get[T](k: AttributeKey[T]): Option[T] + + + /** Adds the mapping `k -> value` to this map, replacing any existing mapping for `k`. + * Any mappings for keys with the same label but different types are unaffected. */ + def put[T](k: AttributeKey[T], value: T): AttributeMap + } + + +Now that there's a definition of what build state is, there needs to be a way to dynamically construct it. In sbt, this is +done through the ``Setting[_]`` sequence. + +Introduction to Settings +======================== + +TODO - Discuss ``Setting[_]`` + +TODO - Transition into ``Task[_]`` + +TODO - Transition into ``InputTask[_]`` \ No newline at end of file diff --git a/src/sphinx/Architecture/Setting-Initialization.rst b/src/sphinx/Architecture/Setting-Initialization.rst index d636e598c..e2e54fd47 100644 --- a/src/sphinx/Architecture/Setting-Initialization.rst +++ b/src/sphinx/Architecture/Setting-Initialization.rst @@ -12,6 +12,9 @@ a particular build key. Sbt converts all registered ``Setting[_]`` objects into All of sbt's loading semantics are contained within the `Load.scala <../../sxr/sbt/Load.scala.html>` file. It is approximately the following: +.. Note: This image comes from a google drawing: https://docs.google.com/a/typesafe.com/drawings/d/1Aj_IkOaJpRXJNhrVtVJaS8m-YRcKsympVOj3M2sUz7E/edit +.. Feel free to request access to modify as appropriate. + .. image:: settings-initialization-load-ordering.png The blue circles represent actions happening when sbt loads a project. We can see that sbt performs the following actions in load: @@ -26,14 +29,19 @@ The blue circles represent actions happening when sbt loads a project. We can s Each of these loads defines several sequences of settings. The diagram shows the two most important: -* ``buildSettings`` - These are settings defined to be ``in ThisBuild``. They are initialized *once* for the build. +* ``buildSettings`` - These are settings defined to be ``in ThisBuild`` or directly against the ``Build`` object. They are initialized *once* for the build. You can add these, e.g. in ``project/build.scala`` :: object MyBuild extends Build { - override val settings = ... + override val settings = Seq(foo := "hi") } -* ``projectSettings`` - These are settings specific to a project. They are specific to a *particular submodule* in the build. A + or in a ``build.sbt`` file :: + + foo in ThisBuild := "hi" + + +* ``projectSettings`` - These are settings specific to a project. They are specific to a *particular sub project* in the build. A plugin may be contributing its settings to more than on project, in which case the values are duplicated for each project. You add project specific settings, eg. in ``project/build.scala`` :: @@ -94,4 +102,30 @@ The AddSettings object provides the following "groups" of settings you can use f Include all local ``*.sbt`` file settings. -*Note: Be very careful when reordering settings. It's easy to accidentally remove core functionality.* \ No newline at end of file +*Note: Be very careful when reordering settings. It's easy to accidentally remove core functionality.* + +For example, let's see what happens if we move the ``build.sbt`` files *before* the ``projectSettings``. + +Let's create an example project the following defintiion: + +`project/build.scala` :: + + object MyTestBuild extends Build { + + val testProject = project.in(file(".")).autoSettings(autoPlugins, defaultSbtFiles, projectSettings).settings( + version := scalaBinaryVersion.value match { + case "2.10" => "1.0-SNAPSHOT" + case v => "1.0-for-${v}-SNAPSHOT" + } + ) + } + +This build defines a version string which appends the scala version if the current scala version is not the in the ``2.10.x`` series. +Now, when issuing a release we want to lock down the version. Most tools assume this can happen by writing a ``version.sbt`` file: + +`version.sbt` :: + + version := "1.0.0" + +However, when we load this new build, we find that the ``version`` in ``version.sbt`` has been **overriden** by the one defined +in ``project/Build.scala`` because of the order we defined for settings, so the new ``version.sbt`` file has no effect. \ No newline at end of file diff --git a/src/sphinx/Architecture/Task-Engine.rst b/src/sphinx/Architecture/Task-Engine.rst new file mode 100644 index 000000000..9d7be5da0 --- /dev/null +++ b/src/sphinx/Architecture/Task-Engine.rst @@ -0,0 +1,5 @@ +================= + Task Engine +================= + +Placeholder for task engine design details. \ No newline at end of file diff --git a/src/sphinx/Architecture/index.rst b/src/sphinx/Architecture/index.rst index d20bce232..ea16cae7a 100644 --- a/src/sphinx/Architecture/index.rst +++ b/src/sphinx/Architecture/index.rst @@ -2,7 +2,7 @@ Architecture ============== -This is the fledgeling set of documentation about the Architecture of sbt. This will cover all the core components of +This is the set of documentation about the Architecture of sbt. This covers all the core components of sbt as well as the general notion of how they all work together. This documentation is suitable for those who wish to have a deeper understanding of sbt's core, but already understand the fundamentals of ``Setting[_]``, ``Task[_]`` and constructing builds. @@ -10,4 +10,7 @@ constructing builds. .. toctree:: :maxdepth: 2 - Setting-Initialization \ No newline at end of file + Core-Principles + Setting-Initialization + Task-Engine + Command-Engine From 7413fbe9c097f099b1774a621ae9074ff2acc049 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 10 Mar 2014 16:12:05 -0400 Subject: [PATCH 10/10] Doc fix ups from review. * Fix plugin mispellings * Clarify `select` behavior. --- main/src/main/scala/sbt/Plugins.scala | 2 +- main/src/main/scala/sbt/PluginsDebug.scala | 4 ++-- main/src/main/scala/sbt/Project.scala | 2 +- main/src/main/scala/sbt/plugins/IvyModule.scala | 3 ++- main/src/main/scala/sbt/plugins/JvmModule.scala | 3 ++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/Plugins.scala b/main/src/main/scala/sbt/Plugins.scala index a7ada9b6e..926defd11 100644 --- a/main/src/main/scala/sbt/Plugins.scala +++ b/main/src/main/scala/sbt/Plugins.scala @@ -85,7 +85,7 @@ abstract class AutoPlugin extends Plugins.Basic } } /** - * A root AutoPlugin is a plugin which must be explicitly enabled by users in their `setPlugins` method + * 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 { diff --git a/main/src/main/scala/sbt/PluginsDebug.scala b/main/src/main/scala/sbt/PluginsDebug.scala index e130b2c8b..a24546c23 100644 --- a/main/src/main/scala/sbt/PluginsDebug.scala +++ b/main/src/main/scala/sbt/PluginsDebug.scala @@ -165,9 +165,9 @@ private[sbt] object PluginsDebug 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 plguins, exclusions, and ultimately activated plugins + * @param context The base plugins, exclusions, and ultimately activated plugins * @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped - * @param enablingPlguins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate + * @param enablingPlugins [[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 [[AutoPlugin]].*/ diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 7604c9d27..afcc29826 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -51,7 +51,7 @@ sealed trait ProjectDefinition[PR <: ProjectReference] def auto: AddSettings /** 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. */ + A [[AutoPlugin]] 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. */ diff --git a/main/src/main/scala/sbt/plugins/IvyModule.scala b/main/src/main/scala/sbt/plugins/IvyModule.scala index 6ce0d9a9d..a0e361503 100644 --- a/main/src/main/scala/sbt/plugins/IvyModule.scala +++ b/main/src/main/scala/sbt/plugins/IvyModule.scala @@ -14,7 +14,8 @@ import Def.Setting * - `publishedArtifacts` */ object IvyModule extends AutoPlugin { - // We must be explicitly enabled + // We are automatically included on everything that has the global module, + // which is automatically included on everything. def select = GlobalModule override lazy val projectSettings: Seq[Setting[_]] = diff --git a/main/src/main/scala/sbt/plugins/JvmModule.scala b/main/src/main/scala/sbt/plugins/JvmModule.scala index 6dd95d9c0..0a7219c26 100644 --- a/main/src/main/scala/sbt/plugins/JvmModule.scala +++ b/main/src/main/scala/sbt/plugins/JvmModule.scala @@ -15,7 +15,8 @@ import Def.Setting * - `Compile` */ object JvmModule extends AutoPlugin { - // We must be explicitly enabled + // We are automatically enabled for any IvyModule project. We also require its settings + // for ours to work. def select = IvyModule override lazy val projectSettings: Seq[Setting[_]] =