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