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)
This commit is contained in:
Mark Harrah 2011-01-25 22:14:02 -05:00
parent f1af2c1cbc
commit 1b9b2481f4
4 changed files with 62 additions and 35 deletions

View File

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

View File

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

View File

@ -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]) =

View File

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