memoize .sbt file loading within a build and only add a Project once

This fixes an infinite loop when defining a Project in a .sbt file
with the build base directory as the project base directory.
This allows the standard behavior for interpreting settings from
.sbt files for both Projects defined in .sbt files as well as in
.scala files. That is, settings in .sbt files in "." go in all
projects with "." as their base directory.

Ref #554.
This commit is contained in:
Mark Harrah 2013-04-10 20:15:27 -04:00
parent cf175612cb
commit 23be591c95
3 changed files with 21 additions and 9 deletions

View File

@ -40,7 +40,7 @@ object EvaluateConfigurations
loader => l(loader).settings
}
private[this] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile =
private[sbt] def evaluateSbtFile(eval: Eval, file: File, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => LoadedSbtFile =
{
val name = file.getPath
val parsed = parseConfiguration(lines, imports, offset)

View File

@ -413,15 +413,15 @@ object Load
lazy val eval = mkEval(plugs.classpath, defDir, Nil)
val initialProjects = defs.flatMap(_.projectDefinitions(normBase).map(resolveBase(normBase)))
val loadedProjects = loadTransitive(initialProjects, imports, plugs, () => eval, config.injectSettings, Nil)
val loadedProjects = loadTransitive(initialProjects, imports, plugs, () => eval, config.injectSettings, Nil, new mutable.HashMap)
val loadedDefs = new sbt.LoadedDefinitions(defDir, Nil, plugs.loader, defs, loadedProjects, defNames)
new sbt.BuildUnit(uri, normBase, loadedDefs, plugs)
}
private[this] def loadTransitive(newProjects: Seq[Project], imports: Seq[String], plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project]): Seq[Project] =
private[this] def loadTransitive(newProjects: Seq[Project], imports: Seq[String], plugins: sbt.LoadedPlugins, eval: () => Eval, injectSettings: InjectSettings, acc: Seq[Project], memoSettings: mutable.Map[File, LoadedSbtFile]): Seq[Project] =
{
val loaded = newProjects map { project =>
val loadedSbtFiles = loadSettings(project.auto, project.base, imports, plugins, eval, injectSettings)
val loadedSbtFiles = loadSettings(project.auto, project.base, imports, plugins, eval, injectSettings, memoSettings)
val transformed = project.copy(settings = (project.settings: Seq[Setting[_]]) ++ loadedSbtFiles.settings)
(transformed, loadedSbtFiles.projects)
}
@ -432,20 +432,31 @@ object Load
if(nextProjects.isEmpty)
loadedProjects
else
loadTransitive(nextProjects, imports, plugins, eval, injectSettings, loadedProjects)
loadTransitive(nextProjects, imports, plugins, eval, injectSettings, loadedProjects, memoSettings)
}
private[this] def loadSettings(auto: AddSettings, projectBase: File, buildImports: Seq[String], loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings): LoadedSbtFile =
private[this] def loadSettings(auto: AddSettings, projectBase: File, buildImports: Seq[String], loadedPlugins: sbt.LoadedPlugins, eval: ()=>Eval, injectSettings: InjectSettings, memoSettings: mutable.Map[File, LoadedSbtFile]): LoadedSbtFile =
{
lazy val defaultSbtFiles = configurationSources(projectBase)
def settings(ss: Seq[Setting[_]]) = new LoadedSbtFile(ss, Nil, Nil)
val loader = loadedPlugins.loader
import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence}
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
}
def loadSettingsFile(src: File): LoadedSbtFile =
EvaluateConfigurations.evaluateSbtFile(eval(), src, IO.readLines(src), buildImports, 0)(loader)
import AddSettings.{User,SbtFiles,DefaultSbtFiles,Plugins,Sequence}
def expand(auto: AddSettings): LoadedSbtFile = auto match {
case User => settings(injectSettings.projectLoaded(loader))
case sf: SbtFiles => configurations( sf.files.map(f => IO.resolve(projectBase, f)), eval, buildImports )(loader)
case sf: DefaultSbtFiles => configurations( defaultSbtFiles.filter(sf.include), eval, buildImports )(loader)
case sf: SbtFiles => loadSettings( sf.files.map(f => IO.resolve(projectBase, f)))
case sf: DefaultSbtFiles => loadSettings( defaultSbtFiles.filter(sf.include))
case f: Plugins => settings(loadedPlugins.plugins.filter(f.include).flatMap(p => p.settings.filter(isProjectThis) ++ p.projectSettings))
case q: Sequence => (LoadedSbtFile.empty /: q.sequence) { (b,add) => b.merge( expand(add) ) }
}

View File

@ -8,6 +8,7 @@ private[sbt] final class LoadedSbtFile(val settings: Seq[Setting[_]], val projec
{
def merge(o: LoadedSbtFile): LoadedSbtFile =
new LoadedSbtFile(settings ++ o.settings, projects ++ o.projects, importedDefs ++ o.importedDefs)
def clearProjects = new LoadedSbtFile(settings, Nil, importedDefs)
}
private[sbt] object LoadedSbtFile
{