Rework sbt's loading mechanism to allow `ProjectManipulation` DSL entries to take effect.

This does the following:

* Fragments loading into two stages:  Discovery + Resolution
* Discovery just looks for .sbt files and Projects, while
  loading/compiling them.
* Resolution is responsible for taking discovered projects and
  loaded sbt files and globbing everything together.  This includes
  feeding the project through various manipulations, applying
  AutoPlugin settings/configurations and ordering all the settings.
* Add a bunch of docs
* Add direct DSL `enablePlugins` and test
* Add direct DSL `disablePlugins` and test.
This commit is contained in:
Josh Suereth 2014-05-06 17:03:19 -04:00
parent 746583e718
commit af1c581cbb
8 changed files with 352 additions and 89 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
disablePlugins(plugins.IvyPlugin)

View File

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

View File

@ -1 +1,2 @@
> plugins
> check