Merge pull request #1165 from sbt/wip/auto-plugins-defaults

Use AutoPlugins for default project settings.
This commit is contained in:
Havoc Pennington 2014-03-10 17:11:07 -04:00
commit 37cc659518
29 changed files with 749 additions and 280 deletions

View File

@ -12,11 +12,28 @@ 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 autoplugins. */
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
/** All plugins that aren't auto plugins. */
val nonAutoPlugins: AddSettings = plugins(const(true))
/** Adds all settings from a plugin to a project. */
val allPlugins: AddSettings = plugins(const(true))
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)
@ -33,7 +50,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 {

View File

@ -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 : _*)

View File

@ -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.

View File

@ -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) {

View File

@ -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.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)
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,18 +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}
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)

View File

@ -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
}

View File

@ -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`. */

View File

@ -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),
<Project>.natures( Web && Javascript )
<Project>.plugins( Web && Javascript )
will activate `MyPlugin` defined above and have its settings automatically added. If the user instead defines
<Project>.natures( Web && Javascript && !MyPlugin)
<Project>.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("$")
@ -72,6 +72,24 @@ abstract class AutoPlugin extends Natures.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 `addPlugins` method
* on a project. However, RootAutoPlugins represent the "root" of a tree of dependent auto-plugins.
*/
abstract class RootAutoPlugin extends AutoPlugin {
final def select: Plugins = this
}
/** An error that occurs when auto-plugins aren't configured properly.
@ -84,26 +102,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
@ -111,9 +122,12 @@ object Natures
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)) )
requestedNatures =>
Logic.reduce(clauses, flattenConvert(requestedNatures).toSet) match {
// 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)
case Right(results) =>
// results includes the originally requested (positive) atoms,
@ -123,8 +137,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 +149,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 = "<none>"
}
/** 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 +186,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)
}
}

View File

@ -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 plugins, 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 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 [[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 =

View File

@ -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 [[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. */
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,12 @@ 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
// 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, Natures.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
@ -228,21 +229,22 @@ 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
}
@deprecated("0.13.2", "Use Defaults.coreDefaultSettings instead, combined with AutoPlugins.")
def defaultSettings: Seq[Def.Setting[_]] = Defaults.defaultSettings
final class Constructor(p: ProjectReference) {

View File

@ -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
}

View File

@ -0,0 +1,25 @@
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 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[_]] =
Classpaths.ivyPublishSettings ++ Classpaths.ivyBaseSettings
override lazy val globalSettings: Seq[Setting[_]] =
Defaults.globalIvyCore
}

View File

@ -0,0 +1,36 @@
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 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[_]] =
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
}

View File

@ -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

View File

@ -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 ::

View File

@ -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)
}

View File

@ -0,0 +1,5 @@
=================
Command Engine
=================
Placeholder for command engine details.

View File

@ -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 <Command-Engine>`.
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<String,Any>``. 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[_]``

View File

@ -0,0 +1,131 @@
======================
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:
.. 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:
1. Compile the user-level project (``~/.sbt/<version>/``)
a. Load any plugins defined by this project (``~/.sbt/<version>/plugins/*.sbt`` and ``~/.sbt/<version>/plugins/project/*.scala``)
b. Load all settings defined (``~/.sbt/<version>/*.sbt`` and ``~/.sbt/<version>/plugins/*.scala``)
2. Compile the current project (``<working-directory/project``)
a. Load all defined plugins (``project/plugins.sbt`` and ``project/project/*.scala``)
b. Load/Compile the project (``project/*.scala``)
3. Load project *.sbt files (``build.sbt`` and friends).
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`` 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 = Seq(foo := "hi")
}
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`` ::
object MyBuild extends Build {
val test = project.in(file(".")).settings(...)
}
After loading/compiling all the build definitions, sbt has a series of Seq[Setting[_]] that it must order. As shown in the diagram,
the default inclusion order for sbt is:
1. All AutoPlugin settings
2. All settings defined in ``project/Build.scala``
3. All settings defined in the user directory (``~/.sbt/<verison>/*.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/<verison>``)
* 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/<version>/``.
``defaultSbtFiles``
Include all local ``*.sbt`` file settings.
*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.

View File

@ -0,0 +1,5 @@
=================
Task Engine
=================
Placeholder for task engine design details.

View File

@ -0,0 +1,16 @@
==============
Architecture
==============
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.
.. toctree::
:maxdepth: 2
Core-Principles
Setting-Initialization
Task-Engine
Command-Engine

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -19,4 +19,5 @@ Other resources include the :doc:`Examples </Examples/index>` and
Tasks-and-Commands
Plugins-and-Best-Practices
Advanced-Index
/Architecture/index
/Launcher/index

View File

@ -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,

View File

@ -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
-------------------

View File

@ -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
----------------------

View File

@ -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:

View File

@ -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 <Multi-Project>` 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 <Multi-Project>` 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 </Community/Community-Plugins>` available for open source plugins.
For more details, including ways of developing plugins, see :doc:`/Extending/Plugins`.

View File

@ -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 </Getting-Started/Scopes>`.
::
// 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"))
)