diff --git a/main/src/main/scala/sbt/AddSettings.scala b/main/src/main/scala/sbt/AddSettings.scala index 2d698b874..9677af329 100644 --- a/main/src/main/scala/sbt/AddSettings.scala +++ b/main/src/main/scala/sbt/AddSettings.scala @@ -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 { 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/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/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/Load.scala b/main/src/main/scala/sbt/Load.scala index 2a00e7329..a275c907a 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.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) 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/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/Natures.scala b/main/src/main/scala/sbt/Plugins.scala similarity index 64% rename from main/src/main/scala/sbt/Natures.scala rename to main/src/main/scala/sbt/Plugins.scala index 06b0a0e2f..926defd11 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("$") @@ -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 = "" } - /** 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) } } \ 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..a24546c23 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 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 = diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 647013bed..afcc29826 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 [[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) { 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..a0e361503 --- /dev/null +++ b/main/src/main/scala/sbt/plugins/IvyModule.scala @@ -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 +} \ 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..0a7219c26 --- /dev/null +++ b/main/src/main/scala/sbt/plugins/JvmModule.scala @@ -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 +} \ No newline at end of file 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) } 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 new file mode 100644 index 000000000..e2e54fd47 --- /dev/null +++ b/src/sphinx/Architecture/Setting-Initialization.rst @@ -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//``) + 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.* + +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 new file mode 100644 index 000000000..ea16cae7a --- /dev/null +++ b/src/sphinx/Architecture/index.rst @@ -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 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 000000000..82055d7d7 Binary files /dev/null and b/src/sphinx/Architecture/settings-initialization-load-ordering.png differ diff --git a/src/sphinx/Detailed-Topics/index.rst b/src/sphinx/Detailed-Topics/index.rst index 7b551dd7d..498ef5b65 100644 --- a/src/sphinx/Detailed-Topics/index.rst +++ b/src/sphinx/Detailed-Topics/index.rst @@ -19,4 +19,5 @@ Other resources include the :doc:`Examples ` and Tasks-and-Commands Plugins-and-Best-Practices Advanced-Index + /Architecture/index /Launcher/index 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/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/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/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`. 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")) )