From 23be591c95997563b43cac363b18d236bc2b4acd Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Wed, 10 Apr 2013 20:15:27 -0400 Subject: [PATCH] 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. --- .../scala/sbt/EvaluateConfigurations.scala | 2 +- main/src/main/scala/sbt/Load.scala | 27 +++++++++++++------ main/src/main/scala/sbt/LoadedSbtFile.scala | 1 + 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/main/src/main/scala/sbt/EvaluateConfigurations.scala b/main/src/main/scala/sbt/EvaluateConfigurations.scala index e2726f09c..eacc0f569 100644 --- a/main/src/main/scala/sbt/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/EvaluateConfigurations.scala @@ -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) diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index 71e63eaef..489cb9630 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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) ) } } diff --git a/main/src/main/scala/sbt/LoadedSbtFile.scala b/main/src/main/scala/sbt/LoadedSbtFile.scala index b8c86dd2e..0f007c002 100644 --- a/main/src/main/scala/sbt/LoadedSbtFile.scala +++ b/main/src/main/scala/sbt/LoadedSbtFile.scala @@ -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 {