diff --git a/main/src/main/scala/sbt/AutoPlugin.scala b/main/src/main/scala/sbt/AutoPlugin.scala index 9f11a6771..d571242bc 100644 --- a/main/src/main/scala/sbt/AutoPlugin.scala +++ b/main/src/main/scala/sbt/AutoPlugin.scala @@ -16,7 +16,7 @@ The `select` method defines the conditions, Steps for plugin authors: 1. Determine the natures that, when present (or absent), activate the AutoPlugin. 2. Determine the settings/configurations to automatically inject when activated. -3. Define a new, unique identifying [[Nature]] (which is a wrapper around a String ID). +3. Define a new, unique identifying [[Nature]], which is a wrapper around a String ID. For example, the following will automatically add the settings in `projectSettings` to a project that has both the `Web` and `Javascript` natures enabled. It will itself @@ -26,7 +26,7 @@ For example, the following will automatically add the settings in `projectSettin object MyPlugin extends AutoPlugin { def select = Web && Javascript def provides = MyStuff - def projectSettings = Seq(...) + override def projectSettings = Seq(...) } Steps for users: @@ -92,6 +92,8 @@ object Natures { // TODO: allow multiple AutoPlugins to provide the same Nature? // TODO: translate error messages + /** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[Nature]]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] = if(defined.isEmpty) Types.const(Nil) @@ -101,10 +103,13 @@ object Natures val clauses = Clauses( defined.map(d => asClause(d)) ) requestedNatures => { val results = Logic.reduce(clauses, flatten(requestedNatures).toSet) + // results includes the originally requested (positive) atoms, + // which won't have a corresponding AutoPlugin to map back to results.ordered.flatMap(a => byAtom.get(a).toList) } } + /** [[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 @@ -129,6 +134,7 @@ object Natures case b: Basic => a && b } + /** Defines a clause for `ap` such that the [[Nature]] provided by `ap` is the head and the selector for `ap` is the body. */ private[sbt] def asClause(ap: AutoPlugin): Clause = Clause( convert(ap.select), Set(Atom(ap.provides.label)) ) diff --git a/main/src/main/scala/sbt/BuildStructure.scala b/main/src/main/scala/sbt/BuildStructure.scala index 7b35d348d..8ddf7bb88 100644 --- a/main/src/main/scala/sbt/BuildStructure.scala +++ b/main/src/main/scala/sbt/BuildStructure.scala @@ -30,30 +30,86 @@ final class StructureIndex( val keyIndex: KeyIndex, val aggregateKeyIndex: KeyIndex ) + +/** A resolved build unit. (`ResolvedBuildUnit` would be a better name to distinguish it from the loaded, but unresolved `BuildUnit`.) +* @param unit The loaded, but unresolved [[BuildUnit]] this was resolved from. +* @param defined The definitive map from project IDs to resolved projects. +* These projects have had [[Reference]]s resolved and [[AutoPlugin]]s evaluated. +* @param rootProjects The list of project IDs for the projects considered roots of this build. +* The first root project is used as the default in several situations where a project is not otherwise selected. +*/ final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, ResolvedProject], val rootProjects: Seq[String], val buildSettings: Seq[Setting[_]]) extends BuildUnitBase { assert(!rootProjects.isEmpty, "No root projects defined for build unit " + unit) + /** The project to use as the default when one is not otherwise selected. + * [[LocalRootProject]] resolves to this from within the same build.*/ val root = rootProjects.head + + /** The base directory of the build unit (not the build definition).*/ def localBase = unit.localBase + + /** The classpath to use when compiling against this build unit's publicly visible code. + * It includes build definition and plugin classes, but not classes for .sbt file statements and expressions. */ def classpath: Seq[File] = unit.definitions.target ++ unit.plugins.classpath + + /** The class loader to use for this build unit's publicly visible code. + * It includes build definition and plugin classes, but not classes for .sbt file statements and expressions. */ def loader = unit.definitions.loader + + /** The imports to use for .sbt files, `consoleProject` and other contexts that use code from the build definition. */ def imports = BuildUtil.getImports(unit) override def toString = unit.toString } // TODO: figure out how to deprecate and drop buildNames +/** The built and loaded build definition, including loaded but unresolved [[Project]]s, for a build unit (for a single URI). +* +* @param base The base directory of the build definition, typically `/project/`. +* @param loader The ClassLoader containing all classes and plugins for the build definition project. +* Note that this does not include classes for .sbt files. +* @param builds The list of [[Build]]s for the build unit. +* 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` +* and their `settings` and `configurations` updated as appropriate. +* @param buildNames No longer used and will be deprecated once feasible. +*/ final class LoadedDefinitions(val base: File, val target: Seq[File], val loader: ClassLoader, val builds: Seq[Build], val projects: Seq[Project], val buildNames: Seq[String]) -final class DetectedModules[T](val modules: Seq[(String, T)]) { +/** Auto-detected top-level modules (as in `object X`) of type `T` paired with their source names. */ +final class DetectedModules[T](val modules: Seq[(String, T)]) +{ + /** The source names of the modules. This is "X" in `object X`, as opposed to the implementation class name "X$". + * The names are returned in a stable order such that `names zip values` pairs a name with the actual module. */ def names: Seq[String] = modules.map(_._1) + + /** The singleton value of the module. + * The values are returned in a stable order such that `names zip values` pairs a name with the actual module. */ def values: Seq[T] = modules.map(_._2) } +/** Auto-discovered modules for the build definition project. These include modules defined in build definition sources +* as well as modules in binary dependencies. +* +* @param builds The [[Build]]s detected in the build definition. This does not include the default [[Build]] that sbt creates if none is defined. +*/ final class DetectedPlugins(val plugins: DetectedModules[Plugin], val autoImports: DetectedModules[AutoImport], val autoPlugins: DetectedModules[AutoPlugin], val builds: DetectedModules[Build]) { + /** 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) } + +/** The built and loaded build definition project. +* @param base The base directory for the build definition project (not the base of the project itself). +* @param pluginData Evaluated tasks/settings from the build definition for later use. +* This is necessary because the build definition project is discarded. +* @param loader The class loader for the build definition project, notably excluding classes used for .sbt files. +* @param detected Auto-detected modules in the build definition. +*/ final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader: ClassLoader, val detected: DetectedPlugins) { /* @@ -71,6 +127,11 @@ final class LoadedPlugins(val base: File, val pluginData: PluginData, val loader def classpath = data(fullClasspath) } +/** The loaded, but unresolved build unit. +* @param uri The uniquely identifying URI for the build. +* @param localBase The working location of the build on the filesystem. +* For local URIs, this is the same as `uri`, but for remote URIs, this is the local copy or workspace allocated for the build. +*/ final class BuildUnit(val uri: URI, val localBase: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins) { override def toString = if(uri.getScheme == "file") localBase.toString else (uri + " (locally: " + localBase +")") diff --git a/main/src/main/scala/sbt/GroupedAutoPlugins.scala b/main/src/main/scala/sbt/GroupedAutoPlugins.scala index 2c99b2d85..d020ad31e 100644 --- a/main/src/main/scala/sbt/GroupedAutoPlugins.scala +++ b/main/src/main/scala/sbt/GroupedAutoPlugins.scala @@ -3,13 +3,13 @@ package sbt import Def.Setting import java.net.URI -final class GroupedAutoPlugins(val all: Seq[AutoPlugin], val byBuild: Map[URI, Seq[AutoPlugin]]) +private[sbt] final class GroupedAutoPlugins(val all: Seq[AutoPlugin], val byBuild: Map[URI, Seq[AutoPlugin]]) { def globalSettings: Seq[Setting[_]] = all.flatMap(_.globalSettings) def buildSettings(uri: URI): Seq[Setting[_]] = byBuild.getOrElse(uri, Nil).flatMap(_.buildSettings) } -object GroupedAutoPlugins +private[sbt] object GroupedAutoPlugins { private[sbt] def apply(units: Map[URI, LoadedBuildUnit]): GroupedAutoPlugins = { diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index f68ae1878..8141b0c7a 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -469,6 +469,7 @@ object Load val autoConfigs = autoPlugins.flatMap(_.projectConfigurations) val loadedSbtFiles = loadSbtFiles(project.auto, project.base, autoPlugins) val newSettings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.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 : _*) (transformed, loadedSbtFiles.projects) } @@ -621,6 +622,9 @@ object Load */ def loadPlugins(dir: File, data: PluginData, loader: ClassLoader): sbt.LoadedPlugins = + new sbt.LoadedPlugins(dir, data, loader, autoDetect(data, loader)) + + private[this] def autoDetect(data: PluginData, loader: ClassLoader): DetectedPlugins = { // TODO: binary detection for builds, autoImports, autoPlugins import AutoBinaryResource._ @@ -628,8 +632,7 @@ object Load val builds = detectModules[Build](data, loader, Builds) val autoImports = detectModules[AutoImport](data, loader, AutoImports) val autoPlugins = detectModules[AutoPlugin](data, loader, AutoPlugins) - val detected = new DetectedPlugins(plugins, autoImports, autoPlugins, builds) - new sbt.LoadedPlugins(dir, data, loader, detected) + new DetectedPlugins(plugins, autoImports, autoPlugins, builds) } private[this] def detectModules[T](data: PluginData, loader: ClassLoader, resourceName: String)(implicit mf: reflect.ClassManifest[T]): DetectedModules[T] = { @@ -677,6 +680,8 @@ TODO: UNCOMMENT BEFORE COMMIT binaryPlugins(classpath, loader, AutoBinaryResource.Plugins) */ + /** Relative paths of resources that list top-level modules that are available. + * Normally, the classes for those modules will be in the same classpath entry as the resource. */ object AutoBinaryResource { final val AutoPlugins = "sbt/sbt.autoplugins" final val Plugins = "sbt/sbt.plugins"