From 1b9b2481f4cf5c6b62b185034afb041d218cd46c Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 25 Jan 2011 22:14:02 -0500 Subject: [PATCH] split load-time project structure scope resolution into two phases first phase resolves referenced build URIs as each build is loaded second phase resolves references without an explicit project ID (this requires the whole structure to be known and this isn't available during the first phase) setting resolution is unchanged (done after both phases) --- main/Build.scala | 62 ++++++++++++++++++++++++---------- main/Scope.scala | 16 +++++---- util/collection/Settings.scala | 17 ++++------ util/io/IO.scala | 2 +- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/main/Build.scala b/main/Build.scala index 748c30d6d..406fbf2e7 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -9,6 +9,7 @@ package sbt import classpath.ClasspathUtilities import inc.Analysis import scala.annotation.tailrec + import collection.mutable import Compile.{Compilers,Inputs} import Project.{ScopedKey, Setting} import TypeFunctions.{Endo,Id} @@ -191,11 +192,13 @@ object Load val classpath = provider.mainClasspath ++ provider.scalaProvider.jars val compilers = Compile.compilers(state.configuration, log) val evalPluginDef = EvaluateTask.evalPluginDef(state, log) _ - val config = new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, defaultDelegates, EvaluateTask.injectSettings, log) + val delegates = memo(defaultDelegates) + val config = new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, delegates, EvaluateTask.injectSettings, log) apply(base, config) } def defaultDelegates: LoadedBuild => Scope => Seq[Scope] = (lb: LoadedBuild) => { - def resolveRef(project: ProjectRef) = Scope.resolveRef(lb.root, getRootProject(lb.units), project) + val rootProject = getRootProject(lb.units) + def resolveRef(project: ProjectRef) = Scope.resolveRef(lb.root, rootProject, project) Scope.delegates( project => projectInherit(lb, resolveRef(project)), (project, config) => configInherit(lb, resolveRef(project), config), @@ -220,7 +223,8 @@ object Load // 8) Evaluate settings def apply(rootBase: File, config: LoadBuildConfiguration): (() => Eval, BuildStructure) = { - val loaded = load(rootBase, config) + // load, which includes some resolution, but can't fill in project IDs yet, so follow with full resolution + val loaded = resolveProjects(load(rootBase, config)) val projects = loaded.units lazy val rootEval = lazyEval(loaded.units(loaded.root).unit) val settings = buildConfigurations(loaded, getRootProject(projects), config.injectSettings, rootEval) @@ -290,11 +294,13 @@ object Load } def loaded(unit: BuildUnit): (LoadedBuildUnit, List[ProjectRef]) = { - // since everything should be resolved at this point, we can compare Files instead of converting to URIs - def isRoot(p: Project) = p.base == unit.localBase - val defined = projects(unit) if(defined.isEmpty) error("No projects defined in build unit " + unit) + + // since base directories are resolved at this point (after 'projects'), + // we can compare Files instead of converting to URIs + def isRoot(p: Project) = p.base == unit.localBase + val externals = referenced(defined).toList val projectsInRoot = defined.filter(isRoot).map(_.id) val rootProjects = if(projectsInRoot.isEmpty) defined.head.id :: Nil else projectsInRoot @@ -331,33 +337,49 @@ object Load val rootProject = getRootProject(builds) for( (uri, refs) <- referenced; ref <- refs) { - // mapRef guarantees each component is defined - val ProjectRef(Some(refURI), Some(refID)) = Scope.mapRef(uri, rootProject, ref) + val (refURI, refID) = Scope.resolveRef(uri, rootProject, ref) val loadedUnit = builds(refURI) if(! (loadedUnit.defined contains refID) ) error("No project '" + refID + "' in '" + refURI + "'") } } - def resolveBase(origin: URI, against: File): Project => Project = + def resolveBase(against: File): Project => Project = { - assert(origin.isAbsolute, "Origin not absolute: " + origin) - def resolveRefs(prs: Seq[ProjectRef]) = prs map resolveRef - def resolveRef(pr: ProjectRef) = pr match { case ProjectRef(Some(uri), id) => ProjectRef(Some(resolveURI(uri)), id); case _ => pr } - def resolveDeps(ds: Seq[Project.ClasspathDependency]) = ds map resolveDep - def resolveDep(d: Project.ClasspathDependency) = d.copy(project = resolveRef(d.project)) - def resolveURI(u: URI): URI = IO.directoryURI(origin resolve u) def resolve(f: File) = { val fResolved = new File(IO.directoryURI(IO.resolve(against, f))) checkProjectBase(against, fResolved) fResolved } - p => p.copy(base = resolve(p.base), aggregate = resolveRefs(p.aggregate), dependencies = resolveDeps(p.dependencies), inherits = resolveRefs(p.inherits)) + p => p.copy(base = resolve(p.base)) + } + def resolveProjects(loaded: LoadedBuild): LoadedBuild = + { + val rootProject = getRootProject(loaded.units) + new LoadedBuild(loaded.root, loaded.units.map { case (uri, unit) => + IO.assertAbsolute(uri) + (uri, resolveProjects(uri, unit, rootProject)) + }) + } + def resolveProjects(uri: URI, unit: LoadedBuildUnit, rootProject: URI => String): LoadedBuildUnit = + { + IO.assertAbsolute(uri) + val resolve = resolveProject(ref => Scope.mapRef(uri, rootProject, ref)) + new LoadedBuildUnit(unit.unit, unit.defined mapValues resolve, unit.rootProjects) + } + def resolveProject(resolveRef: ProjectRef => ProjectRef): Project => Project = + { + def resolveRefs(prs: Seq[ProjectRef]) = prs map resolveRef + def resolveDeps(ds: Seq[Project.ClasspathDependency]) = ds map resolveDep + def resolveDep(d: Project.ClasspathDependency) = d.copy(project = resolveRef(d.project)) + p => p.copy(aggregate = resolveRefs(p.aggregate), dependencies = resolveDeps(p.dependencies), inherits = resolveRefs(p.inherits)) } def projects(unit: BuildUnit): Seq[Project] = { - val resolve = resolveBase(unit.uri, unit.localBase) + // we don't have the complete build graph loaded, so we don't have the rootProject function yet. + // Therefore, we use mapRefBuild instead of mapRef. After all builds are loaded, we can fully resolve ProjectRefs. + val resolve = resolveProject(ref => Scope.mapRefBuild(unit.uri, ref)) compose resolveBase(unit.localBase) unit.definitions.builds.flatMap(_.projects map resolve) } def getRootProject(map: Map[URI, LoadedBuildUnit]): URI => String = @@ -480,6 +502,12 @@ object Load final class LoadBuildConfiguration(val stagingDirectory: File, val classpath: Seq[File], val loader: ClassLoader, val compilers: Compilers, val evalPluginDef: BuildStructure => (Seq[File], Analysis), val delegates: LoadedBuild => Scope => Seq[Scope], val injectSettings: Seq[Setting[_]], val log: Logger) // information that is not original, but can be reconstructed from the rest of BuildStructure final class StructureIndex(val keyMap: Map[String, AttributeKey[_]], val taskToKey: Map[Task[_], ScopedKey[Task[_]]]) + + private[this] def memo[A,B](implicit f: A => B): A => B = + { + val dcache = new mutable.HashMap[A,B] + (a: A) => dcache.getOrElseUpdate(a, f(a)) + } } object BuildStreams { diff --git a/main/Scope.scala b/main/Scope.scala index b7cf694aa..ba7fb2db0 100644 --- a/main/Scope.scala +++ b/main/Scope.scala @@ -13,7 +13,7 @@ object Scope val GlobalScope = Scope(Global, Global, Global, Global) def resolveScope(thisScope: Scope, current: URI, rootProject: URI => String): Scope => Scope = - replaceThis(thisScope) compose resolveProject(current, rootProject) + resolveProject(current, rootProject) compose replaceThis(thisScope) def replaceThis(thisScope: Scope): Scope => Scope = (scope: Scope) => Scope(subThis(thisScope.project, scope.project), subThis(thisScope.config, scope.config), subThis(thisScope.task, scope.task), subThis(thisScope.extra, scope.extra)) @@ -40,11 +40,15 @@ object Scope val (uri, id) = resolveRef(current, rootProject, ref) ProjectRef(Some(uri), Some(id)) } + def mapRefBuild(current: URI, ref: ProjectRef): ProjectRef = ProjectRef(Some(resolveBuild(current, ref)), ref.id) + + def resolveBuild(current: URI, ref: ProjectRef): URI = + ( ref.uri match { case Some(u) => IO.directoryURI(current resolve u); case None => current } ).normalize + def resolveRef(current: URI, rootProject: URI => String, ref: ProjectRef): (URI, String) = { - val unURI = ref.uri match { case Some(u) => current resolve u; case None => current } - val uri = unURI.normalize - (uri, ref.id getOrElse rootProject(uri)) + val uri = resolveBuild(current, ref) + (uri, ref.id getOrElse rootProject(uri) ) } def display(config: ConfigKey): String = if(config.name == "compile") "" else config.name + "-" @@ -106,8 +110,8 @@ object Scope sealed trait ScopeAxis[+S] -object This extends ScopeAxis[Nothing] -object Global extends ScopeAxis[Nothing] +case object This extends ScopeAxis[Nothing] +case object Global extends ScopeAxis[Nothing] final case class Select[S](s: S) extends ScopeAxis[S] final case class ConfigKey(name: String) diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index c779274e6..6c9532a0d 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -4,8 +4,6 @@ package sbt import Types._ - import annotation.tailrec - import collection.mutable sealed trait Settings[Scope] { @@ -98,9 +96,12 @@ trait Init[Scope] def delegate(sMap: ScopedMap)(implicit delegates: Scope => Seq[Scope]): ScopedMap = { - val md = memoDelegates(delegates) - def refMap(refKey: ScopedKey[_]) = new (ScopedKey ~> ScopedKey) { def apply[T](k: ScopedKey[T]) = delegateForKey(sMap, k, md(k.scope), refKey) } - val f = new (SettingSeq ~> SettingSeq) { def apply[T](ks: Seq[Setting[T]]) = ks.map{ s => s mapReferenced refMap(s.key) } } + def refMap(refKey: ScopedKey[_]) = new (ScopedKey ~> ScopedKey) { def apply[T](k: ScopedKey[T]) = + delegateForKey(sMap, k, delegates(k.scope), refKey) + } + val f = new (SettingSeq ~> SettingSeq) { def apply[T](ks: Seq[Setting[T]]) = + ks.map{ s => s mapReferenced refMap(s.key) } + } sMap mapValues f } private[this] def delegateForKey[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], refKey: ScopedKey[_]): ScopedKey[T] = @@ -121,12 +122,6 @@ trait Init[Scope] private[this] def applyInits(ordered: Seq[Compiled])(implicit delegates: Scope => Seq[Scope]): Settings[Scope] = (empty /: ordered){ (m, comp) => comp.eval(m) } - private[this] def memoDelegates(implicit delegates: Scope => Seq[Scope]): Scope => Seq[Scope] = - { - val dcache = new mutable.HashMap[Scope, Seq[Scope]] - (scope: Scope) => dcache.getOrElseUpdate(scope, delegates(scope)) - } - private[this] def applySetting[T](map: Settings[Scope], setting: Setting[T]): Settings[Scope] = { def execK[HL <: HList, M[_]](a: KApply[HL, M, T]) = diff --git a/util/io/IO.scala b/util/io/IO.scala index 4ac2898e9..927f5fea0 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -607,7 +607,7 @@ object IO { assertAbsolute(uri) val str = uri.toASCIIString - val dirStr = if(str.endsWith("/")) str else str + "/" + val dirStr = if(str.endsWith("/") || uri.getScheme != "file") str else str + "/" (new URI(dirStr)).normalize } /** Converts the given File to a URI. If the File is relative, the URI is relative, unlike File.toURI*/