diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index 2d640180b..dfc49c281 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -37,6 +37,7 @@ object EvaluateConfigurations { * return a parsed, compiled + evaluated [[LoadedSbtFile]]. The result has * raw sbt-types that can be accessed and used. */ + @deprecated("We no longer merge build.sbt files together unless they are in the same directory.", "0.13.6") def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): ClassLoader => LoadedSbtFile = { val loadFiles = srcs.sortBy(_.getName) map { src => evaluateSbtFile(eval, src, IO.readLines(src), imports, 0) } @@ -45,6 +46,8 @@ object EvaluateConfigurations { /** * Reads a given .sbt file and evaluates it into a sequence of setting values. + * + * Note: This ignores any non-Setting[_] values in the file. */ def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): ClassLoader => Seq[Setting[_]] = evaluateConfiguration(eval, src, IO.readLines(src), imports, 0) @@ -92,6 +95,8 @@ object EvaluateConfigurations { */ private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile = { + // TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do + // detection for which project project manipulations should be applied. val name = file.getPath val parsed = parseConfiguration(lines, imports, offset) val (importDefs, projects) = if (parsed.definitions.isEmpty) (Nil, (l: ClassLoader) => Nil) else { @@ -101,16 +106,32 @@ object EvaluateConfigurations { (imp, projs) } val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports - val settings = parsed.settings map { - case (settingExpression, range) => - evaluateSetting(eval, name, allImports, settingExpression, range) + val dslEntries = parsed.settings map { + case (dslExpression, range) => + evaluateDslEntry(eval, name, allImports, dslExpression, range) } eval.unlinkDeferred() - val loadSettings = flatten(settings) - loader => new LoadedSbtFile(loadSettings(loader), projects(loader), importDefs) + loader => { + val (settingsRaw, manipulationsRaw) = + dslEntries map (_ apply loader) partition { + case internals.ProjectSettings(_) => true + case _ => false + } + val settings = settingsRaw flatMap { + case internals.ProjectSettings(settings) => settings + case _ => Nil + } + val manipulations = manipulationsRaw map { + case internals.ProjectManipulation(f) => f + } + val ps = projects(loader) + // TODO -get project manipulations. + new LoadedSbtFile(settings, ps, importDefs, manipulations) + } } /** move a project to be relative to this file after we've evaluated it. */ private[this] def resolveBase(f: File, p: Project) = p.copy(base = IO.resolve(f, p.base)) + @deprecated("Will no longer be public.", "0.13.6") def flatten(mksettings: Seq[ClassLoader => Seq[Setting[_]]]): ClassLoader => Seq[Setting[_]] = loader => mksettings.flatMap(_ apply loader) def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] = @@ -125,6 +146,31 @@ object EvaluateConfigurations { val _ = classOf[sbt.internals.DslEntry] // this line exists to try to provide a compile-time error when the following line needs to be changed "sbt.internals.DslEntry" } + + /** + * This actually compiles a scala expression which represents a sbt.internals.DslEntry. + * + * @param eval The mechanism to compile and evaluate Scala expressions. + * @param name The name for the thing we're compiling + * @param imports The scala imports to have in place when we compile the expression + * @param expression The scala expression we're compiling + * @param range The original position in source of the expression, for error messages. + * + * @return A method that given an sbt classloader, can return the actual [[DslEntry]] defined by + * the expression. + */ + private[sbt] def evaluateDslEntry(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): ClassLoader => internals.DslEntry = { + val result = try { + eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some(SettingsDefinitionName), line = range.start) + } catch { + case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage) + } + loader => { + val pos = RangePosition(name, range shift 1) + result.getValue(loader).asInstanceOf[internals.DslEntry].withPos(pos) + } + } + /** * This actually compiles a scala expression which represents a Seq[Setting[_]], although the * expression may be just a single setting. @@ -138,19 +184,12 @@ object EvaluateConfigurations { * @return A method that given an sbt classloader, can return the actual Seq[Setting[_]] defined by * the expression. */ + @deprecated("Build DSL now includes non-Setting[_] type settings.", "0.13.6") def evaluateSetting(eval: Eval, name: String, imports: Seq[(String, Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] = { - val result = try { - eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some(SettingsDefinitionName), line = range.start) - } catch { - case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage) - } - loader => { - val pos = RangePosition(name, range shift 1) - (result.getValue(loader).asInstanceOf[internals.DslEntry] match { - case internals.DslSetting(value) => value.settings - case _ => Nil - }) map (_ withPos pos) + evaluateDslEntry(eval, name, imports, expression, range) andThen { + case internals.ProjectSettings(values) => values + case _ => Nil } } private[this] def isSpace = (c: Char) => Character isWhitespace c diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 14b02f8d6..af49f0d4e 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -411,9 +411,17 @@ object Load { lazy val eval = mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions) val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase)) + val hasRootAlreadyDefined = defsScala.exists(_.rootProject.isDefined) + val memoSettings = new mutable.HashMap[File, LoadedSbtFile] - def loadProjects(ps: Seq[Project]) = loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log) - val loadedProjectsRaw = loadProjects(initialProjects) + def loadProjects(ps: Seq[Project], createRoot: Boolean) = { + loadTransitive(ps, normBase, plugs, () => eval, config.injectSettings, Nil, memoSettings, config.log, createRoot, uri, config.pluginManagement.context) + } + + val loadedProjectsRaw = loadProjects(initialProjects, !hasRootAlreadyDefined) + // TODO - As of sbt 0.13.6 we should always have a default root project from + // here on, so the autogenerated build aggregated can be removed from this code. ( I think) + // We may actually want to move it back here and have different flags in loadTransitive... val hasRoot = loadedProjectsRaw.exists(_.base == normBase) || defsScala.exists(_.rootProject.isDefined) val (loadedProjects, defaultBuildIfNone) = if (hasRoot) @@ -423,7 +431,7 @@ object Load { val refs = existingIDs.map(id => ProjectRef(uri, id)) val defaultID = autoID(normBase, config.pluginManagement.context, existingIDs) val b = Build.defaultAggregated(defaultID, refs) - val defaultProjects = loadProjects(projectsFromBuild(b, normBase)) + val defaultProjects = loadProjects(projectsFromBuild(b, normBase), false) (defaultProjects ++ loadedProjectsRaw, b) } @@ -454,56 +462,153 @@ object Load { private[this] def projectsFromBuild(b: Build, base: File): Seq[Project] = b.projectDefinitions(base).map(resolveBase(base)) - private[this] def loadTransitive(newProjects: Seq[Project], buildBase: File, plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, - acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile], log: Logger): Seq[Project] = + /** + * Loads a new set of projects, including any transitively defined projects underneath this one. + * + * We have two assumptions here: + * + * 1. The first `Project` instance we encounter defines AddSettings and gets to specify where we pull other settings. + * 2. Any project manipulation (enable/disablePlugins) is ok to be added in the order we encounter it. + * + * Any further setting is ignored, as even the SettingSet API should be deprecated/removed with sbt 1.0. + * + * Note: Lots of internal details in here that shouldn't be otherwise exposed. + * + * @param newProjects A sequence of projects we have not yet loaded, but will try to. Must not be Nil + * @param buildBase The `baseDirectory` for the entire build. + * @param plugins A misnomer, this is actually the compiled BuildDefinition (classpath and such) for this project. + * @param eval A mechanism of generating an "Eval" which can compile scala code for us. + * @param injectSettings Settings we need to inject into projects. + * @param acc An accumulated list of loaded projects. TODO - how do these differ from newProjects? + * @param memoSettings A recording of all sbt files that have been loaded so far. + * @param log The logger used for this project. + * @param makeOrDiscoverRoot True if we should autogenerate a root project. + * @param buildUri The URI of the build this is loading + * @param context The plugin management context for autogenerated IDs. + * + * @return The completely resolved/updated sequence of projects defined, with all settings expanded. + */ + private[this] def loadTransitive( + newProjects: Seq[Project], + buildBase: File, + plugins: sbt.LoadedPlugins, + eval: () => Eval, + injectSettings: InjectSettings, + acc: Seq[Project], + memoSettings: mutable.Map[File, LoadedSbtFile], + log: Logger, + makeOrDiscoverRoot: Boolean, + buildUri: URI, + context: PluginManagement.Context): Seq[Project] = { - 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.deducePlugins(project.plugins, log) - catch { case e: AutoPluginException => throw translateAutoPluginException(e, project) } - val autoConfigs = autoPlugins.flatMap(_.projectConfigurations) - 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 = loadedSbtFiles.settings).setAutoPlugins(autoPlugins).prefixConfigs(autoConfigs: _*) - (transformed, loadedSbtFiles.projects) + // load all relevant configuration files (.sbt, as .scala already exists at this point) + def discover(auto: AddSettings, base: File): DiscoveredProjects = discoverProjects(auto, base, plugins, eval, memoSettings) + // Step two, Finalize a project with all its settings/configuration. + def finalizeProject(p: Project, configFiles: Seq[File]): Project = { + val loadedFiles = configFiles flatMap { f => memoSettings.get(f) } + resolveProject(p, loadedFiles, plugins, injectSettings, memoSettings, log) } - 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) - else { - val (transformed, np) = loadForProjects.unzip - (np.flatten, transformed ++ acc) + // Discover any new project definition for the base directory of this project, and load all settings. + // Also return any newly discovered project instances. + def discoverAndLoad(p: Project): (Project, Seq[Project]) = { + val (root, discovered, files) = discover(p.auto, p.base) match { + case DiscoveredProjects(Some(root), rest, files) => + // TODO - We assume here the project defined in a build.sbt WINS because the original was + // a phony. However, we may want to 'merge' the two, or only do this if the original was a default + // generated project. + (root, rest, files) + case DiscoveredProjects(None, rest, files) => (p, rest, files) } - - if (nextProjects.isEmpty) - loadedProjects - else - loadTransitive(nextProjects, buildBase, plugins, eval, injectSettings, loadedProjects, memoSettings, log) + val finalRoot = finalizeProject(root, files) + finalRoot -> discovered + } + // Load all config files AND finalize the project at the root directory, if it exists. + // Continue loading if we find any more. + newProjects match { + case Seq(next, rest @ _*) => + log.debug(s"[Loading] Loading project ${next.id} @ ${next.base}") + val (finished, discovered) = discoverAndLoad(next) + loadTransitive(rest ++ discovered, buildBase, plugins, eval, injectSettings, acc :+ finished, memoSettings, log, false, buildUri, context) + case Nil if makeOrDiscoverRoot => + log.debug(s"[Loading] Scanning directory ${buildBase}") + // TODO - Here we want to fully discover everything and make a default build... + discover(AddSettings.defaultSbtFiles, buildBase) match { + case DiscoveredProjects(Some(root), discovered, files) => + log.debug(s"[Loading] Found root project ${root.id} w/ remaining ${discovered.map(_.id).mkString(",")}") + val finalRoot = finalizeProject(root, files) + loadTransitive(discovered, buildBase, plugins, eval, injectSettings, acc :+ finalRoot, memoSettings, log, false, buildUri, context) + // Here we need to create a root project... + case DiscoveredProjects(None, discovered, files) => + log.debug(s"[Loading] Found non-root projects ${discovered.map(_.id).mkString(",")}") + // Here we do something interesting... We need to create an aggregate root project + val otherProjects = loadTransitive(discovered, buildBase, plugins, eval, injectSettings, acc, memoSettings, log, false, buildUri, context) + val existingIds = otherProjects map (_.id) + val refs = existingIds map (id => ProjectRef(buildUri, id)) + val defaultID = autoID(buildBase, context, existingIds) + val root = finalizeProject(Build.defaultAggregatedProject(defaultID, buildBase, refs), files) + val result = (acc ++ otherProjects) :+ root + log.debug(s"[Loading] Done in ${buildBase}, returning: ${result.map(_.id).mkString("(", ", ", ")")}") + result + } + case Nil => + log.debug(s"[Loading] Done in ${buildBase}, returning: ${acc.map(_.id).mkString("(", ", ", ")")}") + acc + } } + 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], buildScalaFiles: Seq[Setting[_]]): LoadedSbtFile = - { - lazy val defaultSbtFiles = configurationSources(projectBase) - def settings(ss: Seq[Setting[_]]) = new LoadedSbtFile(ss, Nil, Nil) - val loader = loadedPlugins.loader + /** + * Represents the results of flushing out a directory and discovering all the projects underneath it. + * THis will return one completely loaded project, and any newly discovered (and unloaded) projects. + * + * @param root The project at "root" directory we were looking, or non if non was defined. + * @param nonRoot Any sub-projects discovered from this directory + * @param sbtFiles Any sbt file loaded during this discovery (used later to complete the project). + */ + private[this] case class DiscoveredProjects(root: Option[Project], nonRoot: Seq[Project], sbtFiles: Seq[File]) - def merge(ls: Seq[LoadedSbtFile]): LoadedSbtFile = (LoadedSbtFile.empty /: ls) { _ merge _ } - def loadSettings(fs: Seq[File]): LoadedSbtFile = - merge(fs.sortBy(_.getName).map(memoLoadSettingsFile)) - def memoLoadSettingsFile(src: File): LoadedSbtFile = memoSettings.get(src) getOrElse { - val lf = loadSettingsFile(src) - memoSettings.put(src, lf.clearProjects) // don't load projects twice - lf + /** + * This method attempts to resolve/apply all configuration loaded for a project. It is responsible for the following: + * + * 1. Apply any manipulations defined in .sbt files. + * 2. Detecting which autoPlugins are enabled for the project. + * 3. Ordering all Setting[_]s for the project + * + * + * @param rawProject The original project, with nothing manipulated since it was evaluated/discovered. + * @param configFiles All configuration files loaded for this project. Used to discover project manipulations + * @param loadedPlugins The project definition (and classloader) of the build. + * @param globalUserSettings All the settings contributed from the ~/.sbt/ directory + * @param memoSettings A recording of all loaded files (our files should reside in there). We should need not load any + * sbt file to resolve a project. + * @param log A logger to report auto-plugin issues to. + */ + private[this] def resolveProject( + rawProject: Project, + configFiles: Seq[LoadedSbtFile], + loadedPlugins: sbt.LoadedPlugins, + globalUserSettings: InjectSettings, + memoSettings: mutable.Map[File, LoadedSbtFile], + log: Logger): Project = { + import AddSettings._ + // 1. Apply all the project manipulations from .sbt files in order + val transformedProject = + configFiles.flatMap(_.manipulations).foldLeft(rawProject) { (prev, t) => + t(prev) } - def loadSettingsFile(src: File): LoadedSbtFile = - EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), loadedPlugins.detected.imports, 0)(loader) + // 2. Discover all the autoplugins and contributed configurations. + val autoPlugins = + try loadedPlugins.detected.deducePlugins(transformedProject.plugins, log) + catch { case e: AutoPluginException => throw translateAutoPluginException(e, transformedProject) } + val autoConfigs = autoPlugins.flatMap(_.projectConfigurations) - import AddSettings.{ User, SbtFiles, DefaultSbtFiles, Plugins, AutoPlugins, Sequence, BuildScalaFiles } + // 3. Use AddSettings instance to order all Setting[_]s appropriately + val allSettings = { + // TODO - This mechanism of applying settings could be off... It's in two places now... + lazy val defaultSbtFiles = configurationSources(transformedProject.base) + // Grabs the plugin settings for old-style sbt plugins. def pluginSettings(f: Plugins) = { val included = loadedPlugins.detected.plugins.values.filter(f.include) // don't apply the filter to AutoPlugins, only Plugins included.flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings) @@ -512,18 +617,76 @@ object Load { // intended in the AddSettings.AutoPlugins filter. def autoPluginSettings(f: AutoPlugins) = autoPlugins.filter(f.include).flatMap(_.projectSettings) - - def expand(auto: AddSettings): LoadedSbtFile = auto match { - case BuildScalaFiles => settings(buildScalaFiles) - 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)) } + // Grab all the settigns we already loaded from sbt files + def settings(files: Seq[File]): Seq[Setting[_]] = + for { + file <- files + config <- (memoSettings get file).toSeq + setting <- config.settings + } yield setting + // Expand the AddSettings instance into a real Seq[Setting[_]] we'll use on the project + def expandSettings(auto: AddSettings): Seq[Setting[_]] = auto match { + case BuildScalaFiles => rawProject.settings + case User => globalUserSettings.projectLoaded(loadedPlugins.loader) + case sf: SbtFiles => settings(sf.files.map(f => IO.resolve(rawProject.base, f))) + case sf: DefaultSbtFiles => settings(defaultSbtFiles.filter(sf.include)) + case p: Plugins => pluginSettings(p) + case p: AutoPlugins => autoPluginSettings(p) + case q: Sequence => (Seq.empty[Setting[_]] /: q.sequence) { (b, add) => b ++ expandSettings(add) } } - expand(auto) + expandSettings(transformedProject.auto) } + // Finally, a project we can use in buildStructure. + transformedProject.copy(settings = allSettings).setAutoPlugins(autoPlugins).prefixConfigs(autoConfigs: _*) + } + + /** + * This method attempts to discover all Project/settings it can using the configured AddSettings and project base. + * + * @param auto The AddSettings of the defining project (or default) we use to determine which build.sbt files to read. + * @param projectBase The directory we're currently loading projects/definitions from. + * @param eval A mechanism of executing/running scala code. + * @param memoSettings A recording of all files we've parsed. + */ + private[this] def discoverProjects( + auto: AddSettings, + projectBase: File, + loadedPlugins: sbt.LoadedPlugins, + eval: () => Eval, + memoSettings: mutable.Map[File, LoadedSbtFile]): DiscoveredProjects = { + // Default sbt files to read, if needed + lazy val defaultSbtFiles = configurationSources(projectBase) + // Classloader of the build + val loader = loadedPlugins.loader + // How to load an individual file for use later. + def loadSettingsFile(src: File): LoadedSbtFile = + EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), loadedPlugins.detected.imports, 0)(loader) + // How to merge SbtFiles we read into one thing + def merge(ls: Seq[LoadedSbtFile]): LoadedSbtFile = (LoadedSbtFile.empty /: ls) { _ merge _ } + // Loads a given file, or pulls from the cache. + def memoLoadSettingsFile(src: File): LoadedSbtFile = memoSettings.get(src) getOrElse { + val lf = loadSettingsFile(src) + memoSettings.put(src, lf.clearProjects) // don't load projects twice + lf + } + // Loads a set of sbt files, sorted by their lexical name (current behavior of sbt). + def loadFiles(fs: Seq[File]): LoadedSbtFile = + merge(fs.sortBy(_.getName).map(memoLoadSettingsFile)) + + // Finds all the build files associated with this project + import AddSettings.{ User, SbtFiles, DefaultSbtFiles, Plugins, AutoPlugins, Sequence, BuildScalaFiles } + def associatedFiles(auto: AddSettings): Seq[File] = auto match { + case sf: SbtFiles => sf.files.map(f => IO.resolve(projectBase, f)) + case sf: DefaultSbtFiles => defaultSbtFiles.filter(sf.include) + case q: Sequence => (Seq.empty[File] /: q.sequence) { (b, add) => b ++ associatedFiles(add) } + case _ => Seq.empty + } + val rawFiles = associatedFiles(auto) + val rawProjects = loadFiles(rawFiles).projects + val (root, nonRoot) = rawProjects.partition(_.base == projectBase) + // TODO - good error message if more than one root project + DiscoveredProjects(root.headOption, nonRoot, rawFiles) + } @deprecated("No longer used.", "0.13.0") def globalPluginClasspath(globalPlugin: Option[GlobalPlugin]): Seq[Attributed[File]] = @@ -531,6 +694,7 @@ object Load { case Some(cp) => cp.data.fullClasspath case None => Nil } + /** These are the settings defined when loading a project "meta" build. */ val autoPluginSettings: Seq[Setting[_]] = inScope(GlobalScope in LocalRootProject)(Seq( sbtPlugin :== true, pluginData := { @@ -725,10 +889,19 @@ object Load { def buildUtil(root: URI, units: Map[URI, sbt.LoadedBuildUnit], keyIndex: KeyIndex, data: Settings[Scope]): BuildUtil[ResolvedProject] = BuildUtil(root, units, keyIndex, data) } -final case class LoadBuildConfiguration(stagingDirectory: File, classpath: Seq[Attributed[File]], loader: ClassLoader, - compilers: Compilers, evalPluginDef: (sbt.BuildStructure, State) => PluginData, definesClass: DefinesClass, - delegates: sbt.LoadedBuild => Scope => Seq[Scope], scopeLocal: ScopeLocal, - pluginManagement: PluginManagement, injectSettings: Load.InjectSettings, globalPlugin: Option[GlobalPlugin], extraBuilds: Seq[URI], +final case class LoadBuildConfiguration( + stagingDirectory: File, + classpath: Seq[Attributed[File]], + loader: ClassLoader, + compilers: Compilers, + evalPluginDef: (sbt.BuildStructure, State) => PluginData, + definesClass: DefinesClass, + delegates: sbt.LoadedBuild => Scope => Seq[Scope], + scopeLocal: ScopeLocal, + pluginManagement: PluginManagement, + injectSettings: Load.InjectSettings, + globalPlugin: Option[GlobalPlugin], + extraBuilds: Seq[URI], log: Logger) { @deprecated("Use `classpath`.", "0.13.0") lazy val globalPluginClasspath = classpath diff --git a/main/src/main/scala/sbt/LoadedSbtFile.scala b/main/src/main/scala/sbt/LoadedSbtFile.scala index a4830b893..238bcdcb9 100644 --- a/main/src/main/scala/sbt/LoadedSbtFile.scala +++ b/main/src/main/scala/sbt/LoadedSbtFile.scala @@ -6,13 +6,19 @@ import Def.Setting * Represents the exported contents of a .sbt file. Currently, that includes the list of settings, * the values of Project vals, and the import statements for all defined vals/defs. */ -private[sbt] final class LoadedSbtFile(val settings: Seq[Setting[_]], val projects: Seq[Project], val importedDefs: Seq[String]) { +private[sbt] final class LoadedSbtFile( + val settings: Seq[Setting[_]], + val projects: Seq[Project], + val importedDefs: Seq[String], + val manipulations: Seq[Project => Project]) { + @deprecated("LoadedSbtFiles are no longer directly merged.", "0.13.6") def merge(o: LoadedSbtFile): LoadedSbtFile = - new LoadedSbtFile(settings ++ o.settings, projects ++ o.projects, importedDefs ++ o.importedDefs) - def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs) + new LoadedSbtFile(settings ++ o.settings, projects ++ o.projects, importedDefs ++ o.importedDefs, manipulations) + + def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs, manipulations) } private[sbt] object LoadedSbtFile { /** Represents an empty .sbt file: no Projects, imports, or settings.*/ - def empty = new LoadedSbtFile(Nil, Nil, Nil) + def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil) } diff --git a/main/src/main/scala/sbt/internals/DslAst.scala b/main/src/main/scala/sbt/internals/DslAst.scala index 9f8412f4a..2476cf7a5 100644 --- a/main/src/main/scala/sbt/internals/DslAst.scala +++ b/main/src/main/scala/sbt/internals/DslAst.scala @@ -4,17 +4,54 @@ package internals import Def._ /** This reprsents a `Setting` expression configured by the sbt DSL. */ -sealed trait DslEntry +sealed trait DslEntry { + /** Called by the parser. Sets the position where this entry was defined in the build.sbt file. */ + def withPos(pos: RangePosition): DslEntry +} object DslEntry { implicit def fromSettingsDef(inc: SettingsDefinition): DslEntry = DslSetting(inc) implicit def fromSettingsDef(inc: Seq[Setting[_]]): DslEntry = DslSetting(inc) } -/** this represents an actually Setting[_] or Seq[Setting[_]] configured by the sbt DSL. */ -case class DslSetting(settings: SettingsDefinition) extends DslEntry -/** this represents an `enablePlugins()` in the sbt DSL */ -case class DslEnablePlugins(plugins: Seq[AutoPlugin]) extends DslEntry -/** this represents an `disablePlugins()` in the sbt DSL */ -case class DslDisablePlugins(plugins: Seq[AutoPlugin]) extends DslEntry + +/** Represents a DSL entry which adds settings to the current project. */ +sealed trait ProjectSettings extends DslEntry { + def toSettings: Seq[Setting[_]] +} +object ProjectSettings { + def unapply(e: DslEntry): Option[Seq[Setting[_]]] = + e match { + case e: ProjectSettings => Some(e.toSettings) + case _ => None + } +} + +/** Represents a DSL entry which manipulates the current project. */ +sealed trait ProjectManipulation extends DslEntry { + def toFunction: Project => Project + // TODO - Should we store this? + final def withPos(pos: RangePosition): DslEntry = this +} +object ProjectManipulation { + def unapply(e: DslEntry): Option[Project => Project] = + e match { + case e: ProjectManipulation => Some(e.toFunction) + case _ => None + } +} + +/** this represents an actually Setting[_] or Seq[Setting[_]] configured by the sbt DSL. */ +case class DslSetting(settings: SettingsDefinition) extends ProjectSettings { + def toSettings = settings.settings + final def withPos(pos: RangePosition): DslEntry = DslSetting(settings.settings.map(_.withPos(pos))) +} +/** this represents an `enablePlugins()` in the sbt DSL */ +case class DslEnablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation { + override val toFunction: Project => Project = _.enablePlugins(plugins: _*) +} +/** this represents an `disablePlugins()` in the sbt DSL */ +case class DslDisablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation { + override val toFunction: Project => Project = _.disablePlugins(plugins: _*) +} diff --git a/sbt/src/sbt-test/project/auto-plugins/build.sbt b/sbt/src/sbt-test/project/auto-plugins/build.sbt index 675d32362..779580b53 100644 --- a/sbt/src/sbt-test/project/auto-plugins/build.sbt +++ b/sbt/src/sbt-test/project/auto-plugins/build.sbt @@ -16,6 +16,12 @@ lazy val projE = project.enablePlugins(S) disablePlugins(plugins.IvyPlugin) check := { + // TODO - this will pass when the raw disablePlugin works. + val dversion = (projectID in projD).?.value // Should be None + same(dversion, None, "projectID in projD") + val rversion = projectID.?.value // Should be None + same(rversion, None, "projectID") +// val adel = (del in projA).?.value // should be None same(adel, None, "del in projA") val bdel = (del in projB).?.value // should be None @@ -27,10 +33,10 @@ check := { same(buildValue, "build 0", "demo in ThisBuild") val globalValue = (demo in Global).value same(globalValue, "global 0", "demo in Global") - val projValue = (demo in projC).value - same(projValue, "project projC Q R", "demo in projC") - val qValue = (del in projC in q).value - same(qValue, " Q R", "del in projC in q") + val projValue = (demo in projC).?.value + same(projValue, Some("project projC Q R"), "demo in projC") + val qValue = (del in projC in q).?.value + same(qValue, Some(" Q R"), "del in projC in q") val optInValue = (del in projE in q).value same(optInValue, " Q S R", "del in projE in q") } diff --git a/sbt/src/sbt-test/project/auto-plugins/projD/build.sbt b/sbt/src/sbt-test/project/auto-plugins/projD/build.sbt new file mode 100644 index 000000000..e11af099d --- /dev/null +++ b/sbt/src/sbt-test/project/auto-plugins/projD/build.sbt @@ -0,0 +1 @@ +disablePlugins(plugins.IvyPlugin) \ No newline at end of file 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 f135d444b..40a4598ff 100644 --- a/sbt/src/sbt-test/project/auto-plugins/project/Q.scala +++ b/sbt/src/sbt-test/project/auto-plugins/project/Q.scala @@ -16,7 +16,7 @@ object Imports lazy val demo = settingKey[String]("A demo setting.") lazy val del = settingKey[String]("Another demo setting.") - lazy val check = settingKey[Unit]("Verifies settings are as they should be.") + lazy val check = taskKey[Unit]("Verifies settings are as they should be.") } object X extends AutoPlugin { diff --git a/sbt/src/sbt-test/project/auto-plugins/test b/sbt/src/sbt-test/project/auto-plugins/test index 15675b169..b1ab83bf7 100644 --- a/sbt/src/sbt-test/project/auto-plugins/test +++ b/sbt/src/sbt-test/project/auto-plugins/test @@ -1 +1,2 @@ +> plugins > check