mirror of https://github.com/sbt/sbt.git
Merge pull request #1312 from sbt/wip/dsl-enable-plugins
Expand the DSL abilities with Project manipulations
This commit is contained in:
commit
3b1d63d0c9
|
|
@ -9,7 +9,8 @@ env:
|
|||
- SCRIPTED_TEST="scripted dependency-management/*2of2"
|
||||
- SCRIPTED_TEST="scripted java/*"
|
||||
- SCRIPTED_TEST="scripted package/*"
|
||||
- SCRIPTED_TEST="scripted project/*"
|
||||
- SCRIPTED_TEST="scripted project/*1of2"
|
||||
- SCRIPTED_TEST="scripted project/*2of2"
|
||||
- SCRIPTED_TEST="scripted reporter/*"
|
||||
- SCRIPTED_TEST="scripted run/*"
|
||||
- SCRIPTED_TEST="scripted source-dependencies/*1of3"
|
||||
|
|
@ -21,6 +22,9 @@ env:
|
|||
# TODO - we'd like to actually test everything, but the process library has a deadlock right now
|
||||
jdk:
|
||||
- openjdk6
|
||||
# - oraclejdk7
|
||||
notifications:
|
||||
email:
|
||||
- qbranch@typesafe.com
|
||||
before_script:
|
||||
- export JVM_OPTS="-Xms1024m -Xmx1024m -XX:ReservedCodeCacheSize=128m -XX:MaxPermSize=256m"
|
||||
|
|
@ -68,7 +68,8 @@ object BuildUtil {
|
|||
deps(proj)(_.aggregate)
|
||||
}
|
||||
}
|
||||
def baseImports: Seq[String] = "import sbt._, Keys._" :: Nil
|
||||
|
||||
def baseImports: Seq[String] = "import sbt._, Keys._, dsl._" :: Nil
|
||||
|
||||
def getImports(unit: BuildUnit): Seq[String] = unit.plugins.detected.imports
|
||||
|
||||
|
|
|
|||
|
|
@ -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)] =
|
||||
|
|
@ -122,9 +143,34 @@ object EvaluateConfigurations {
|
|||
* The name of the class we cast DSL "setting" (vs. definition) lines to.
|
||||
*/
|
||||
val SettingsDefinitionName = {
|
||||
val _ = classOf[sbt.Def.SettingsDefinition] // this line exists to try to provide a compile-time error when the following line needs to be changed
|
||||
"sbt.Def.SettingsDefinition"
|
||||
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,16 +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[SettingsDefinition].settings 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package sbt
|
||||
|
||||
import internals.{
|
||||
DslEntry,
|
||||
DslSetting,
|
||||
DslEnablePlugins,
|
||||
DslDisablePlugins
|
||||
}
|
||||
|
||||
package object dsl {
|
||||
def enablePlugins(ps: AutoPlugin*): DslEntry = DslEnablePlugins(ps)
|
||||
def disablePlugins(ps: AutoPlugin*): DslEntry = DslDisablePlugins(ps)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package sbt
|
||||
package internals
|
||||
|
||||
import Def._
|
||||
|
||||
/** This reprsents a `Setting` expression configured by the sbt DSL. */
|
||||
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)
|
||||
}
|
||||
|
||||
/** 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: _*)
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +13,20 @@ lazy val projD = project
|
|||
// with S selected, Q is loaded automatically, which in turn selects R
|
||||
lazy val projE = project.enablePlugins(S)
|
||||
|
||||
lazy val projF = project
|
||||
|
||||
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")
|
||||
// Ensure with multiple .sbt files that disabling/enabling works across them
|
||||
val fDel = (del in q in projF).?.value
|
||||
same(fDel, Some(" Q"), "del in q in projF")
|
||||
//
|
||||
val adel = (del in projA).?.value // should be None
|
||||
same(adel, None, "del in projA")
|
||||
val bdel = (del in projB).?.value // should be None
|
||||
|
|
@ -25,10 +38,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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
disablePlugins(plugins.IvyPlugin)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import sbttest.Imports._
|
||||
|
||||
enablePlugins(A, B)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import sbttest.S
|
||||
|
||||
disablePlugins(R)
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
> plugins
|
||||
> check
|
||||
|
|
|
|||
Loading…
Reference in New Issue