diff --git a/main/Load.scala b/main/Load.scala index 51d9a0b3e..2937ef349 100644 --- a/main/Load.scala +++ b/main/Load.scala @@ -22,7 +22,7 @@ object Load import BuildPaths._ import BuildStreams._ - // note that there is State is passed in but not pulled out + // note that there is State passed in but not pulled out def defaultLoad(state: State, baseDirectory: File, log: Logger): (() => Eval, BuildStructure) = { val provider = state.configuration.provider @@ -33,7 +33,7 @@ object Load val classpath = provider.mainClasspath ++ scalaProvider.jars val compilers = Compiler.compilers(state.configuration, log) val evalPluginDef = EvaluateTask.evalPluginDef(log) _ - val delegates = memo(defaultDelegates) + val delegates = defaultDelegates val inject: Seq[Project.Setting[_]] = ((appConfiguration in GlobalScope) :== state.configuration) +: EvaluateTask.injectSettings val rawConfig = new LoadBuildConfiguration(stagingDirectory, Nil, classpath, loader, compilers, evalPluginDef, delegates, EvaluateTask.injectStreams, inject, log) val commonPlugins = buildGlobalPlugins(defaultGlobalPlugins, state, rawConfig) @@ -46,7 +46,9 @@ object Load val rootProject = getRootProject(lb.units) def resolveRef(project: Reference): ResolvedReference = Scope.resolveReference(lb.root, rootProject, project) Scope.delegates( + lb.allProjectRefs, resolveRef, + rootProject, project => projectInherit(lb, project), (project, config) => configInherit(lb, project, config, rootProject), (project, task) => Nil, @@ -62,13 +64,7 @@ object Load def configInheritRef(lb: LoadedBuild, ref: ProjectRef, config: ConfigKey): Seq[ConfigKey] = configurationOpt(lb.units, ref.build, ref.project, config).toList.flatMap(_.extendsConfigs).map(c => ConfigKey(c.name)) - def projectInherit(lb: LoadedBuild, ref: ResolvedReference): Seq[ProjectRef] = - ref match - { - case pr: ProjectRef => projectInheritRef(lb, pr) - case BuildRef(uri) => Nil - } - def projectInheritRef(lb: LoadedBuild, ref: ProjectRef): Seq[ProjectRef] = + def projectInherit(lb: LoadedBuild, ref: ProjectRef): Seq[ProjectRef] = getProject(lb.units, ref.build, ref.project).delegates // build, load, and evaluate all units. @@ -414,6 +410,21 @@ object Load } final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit]) + { + checkCycles(units) + def allProjectRefs: Seq[(ProjectRef, ResolvedProject)] = for( (uri, unit) <- units.toSeq; (id, proj) <- unit.defined ) yield ProjectRef(uri, id) -> proj + } + def checkCycles(units: Map[URI, LoadedBuildUnit]) + { + def getRef(pref: ProjectRef) = units(pref.build).defined(pref.project) + def deps(proj: ResolvedProject)(base: ResolvedProject => Seq[ProjectRef]): Seq[ResolvedProject] = Dag.topologicalSort(proj)(p => base(p) map getRef) + // check for cycles + for( (_, lbu) <- units; proj <- lbu.defined.values) { + deps(proj)(_.dependencies.map(_.project)) + deps(proj)(_.delegates) + deps(proj)(_.aggregate) + } + } final class PartBuild(val root: URI, val units: Map[URI, PartBuildUnit]) sealed trait BuildUnitBase { def rootProjects: Seq[String]; def buildSettings: Seq[Setting[_]] } final class PartBuildUnit(val unit: BuildUnit, val defined: Map[String, Project], val rootProjects: Seq[String], val buildSettings: Seq[Setting[_]]) extends BuildUnitBase diff --git a/main/Project.scala b/main/Project.scala index de86988c4..45762fb3b 100644 --- a/main/Project.scala +++ b/main/Project.scala @@ -94,6 +94,8 @@ object Project extends Init[Scope] with ProjectExtra lazy val aggregate = aggregate0 lazy val dependencies = dependencies0 lazy val delegates = delegates0 + + Dag.topologicalSort(configurations)(_.extendsConfigs) // checks for cyclic references here instead of having to do it in Scope.delegates } def apply(id: String, base: File, aggregate: => Seq[ProjectReference] = Nil, dependencies: => Seq[ClasspathDep[ProjectReference]] = Nil, delegates: => Seq[ProjectReference] = Nil, @@ -158,7 +160,7 @@ object Project extends Init[Scope] with ProjectExtra def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap = vopt match { case Some(v) => attributes.put(key, v); case None => attributes.remove(key) } def makeSettings(settings: Seq[Setting[_]], delegates: Scope => Seq[Scope], scopeLocal: ScopedKey[_] => Seq[Setting[_]]) = - translateUninitialized( make(settings)(delegates, scopeLocal) ) + translateCyclic( make(settings)(delegates, scopeLocal) ) def display(scoped: ScopedKey[_]): String = Scope.display(scoped.scope, scoped.key.label) def display(ref: Reference): String = @@ -197,14 +199,8 @@ object Project extends Init[Scope] with ProjectExtra val f = mapScope(g) ss.map(_ mapReferenced f) } - def translateUninitialized[T](f: => T): T = - try { f } catch { - case u: Project.Uninitialized => - val msg = "Uninitialized reference to " + display(u.key) + " from " + display(u.refKey) - throw new Uninitialized(u.key, u.refKey, msg) - case c: Dag.Cyclic => - throw new MessageOnlyException(c.getMessage) - } + def translateCyclic[T](f: => T): T = + try { f } catch { case c: Dag.Cyclic => throw new MessageOnlyException(c.getMessage) } def delegates(structure: BuildStructure, scope: Scope, key: AttributeKey[_]): Seq[ScopedKey[_]] = structure.delegates(scope).map(d => ScopedKey(d, key)) diff --git a/main/Scope.scala b/main/Scope.scala index b881114a4..dc6ecf0f2 100644 --- a/main/Scope.scala +++ b/main/Scope.scala @@ -117,10 +117,23 @@ object Scope // *Inherit functions should be immediate delegates and not include argument itself. Transitivity will be provided by this method def delegates( + refs: Seq[(ProjectRef, ResolvedProject)], resolve: Reference => ResolvedReference, - projectInherit: ResolvedReference => Seq[ResolvedReference], + rootProject: URI => String, + projectInherit: ProjectRef => Seq[ProjectRef], configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey], taskInherit: (ResolvedReference, AttributeKey[_]) => Seq[AttributeKey[_]], + extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap]): Scope => Seq[Scope] = + { + val index = delegates(refs, projectInherit, configInherit) + scope => indexedDelegates(resolve, index, rootProject, taskInherit, extraInherit)(scope) + } + + def indexedDelegates( + resolve: Reference => ResolvedReference, + index: DelegateIndex, + rootProject: URI => String, + taskInherit: (ResolvedReference, AttributeKey[_]) => Seq[AttributeKey[_]], extraInherit: (ResolvedReference, AttributeMap) => Seq[AttributeMap])(rawScope: Scope): Seq[Scope] = { val scope = Scope.replaceThis(GlobalScope)(rawScope) @@ -128,9 +141,10 @@ object Scope def nonProjectScopes(resolvedProj: ResolvedReference)(px: ScopeAxis[ResolvedReference]) = { val p = px.toOption getOrElse resolvedProj - val cLin = linearize(scope.config)(configInherit(p, _)) - val tLin = linearize(scope.task)(taskInherit(p,_)) - val eLin = linearize(scope.extra)(extraInherit(p,_)) + val configProj = p match { case pr: ProjectRef => pr; case br: BuildRef => ProjectRef(br.build, rootProject(br.build)) } + val cLin = scope.config match { case Select(conf) => index.config(configProj, conf); case _ => withGlobalAxis(scope.config) } + val tLin = withGlobalAxis(scope.task) + val eLin = withGlobalAxis(scope.extra) for(c <- cLin; t <- tLin; e <- eLin) yield Scope(px, c, t, e) } scope.project match @@ -139,24 +153,56 @@ object Scope case This => withGlobalScope(scope.copy(project = Global)) case Select(proj) => val resolvedProj = resolve(proj) - val prod = withRawBuilds(linearize(scope.project map resolve, Nil)(projectInherit)) flatMap nonProjectScopes(resolvedProj) - (prod :+ GlobalScope).distinct + val projAxes: Seq[ScopeAxis[ResolvedReference]] = + resolvedProj match + { + case pr: ProjectRef => index.project(pr) + case br: BuildRef => Select(br) :: Global :: Nil + } + projAxes flatMap nonProjectScopes(resolvedProj) } } + def withGlobalAxis[T](base: ScopeAxis[T]): Seq[ScopeAxis[T]] = if(base.isSelect) base :: Global :: Nil else Global :: Nil def withGlobalScope(base: Scope): Seq[Scope] = if(base == GlobalScope) GlobalScope :: Nil else base :: GlobalScope :: Nil - def withRawBuilds(ps: Seq[ScopeAxis[ResolvedReference]]): Seq[ScopeAxis[ResolvedReference]] = - (ps ++ (ps flatMap rawBuilds).map(Select.apply) :+ Global).distinct + def withRawBuilds(ps: Seq[ScopeAxis[ProjectRef]]): Seq[ScopeAxis[ResolvedReference]] = + ps ++ (ps flatMap rawBuild) :+ Global - def rawBuilds(ps: ScopeAxis[ResolvedReference]): Seq[ResolvedReference] = ps match { case Select(ref) => rawBuilds(ref); case _ => Nil } - def rawBuilds(ps: ResolvedReference): Seq[ResolvedReference] = (Reference.uri(ps) map BuildRef.apply).toList + def rawBuild(ps: ScopeAxis[ProjectRef]): Seq[ScopeAxis[BuildRef]] = ps match { case Select(ref) => Select(BuildRef(ref.build)) :: Nil; case _ => Nil } - def linearize[T](axis: ScopeAxis[T], append: Seq[ScopeAxis[T]] = Global :: Nil)(inherit: T => Seq[T]): Seq[ScopeAxis[T]] = + + def delegates( + refs: Seq[(ProjectRef, ResolvedProject)], + projectInherit: ProjectRef => Seq[ProjectRef], + configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]): DelegateIndex = + { + val pDelegates = refs map { case (ref, project) => + (ref, delegateIndex(ref, project.configurations)(projectInherit, configInherit) ) + } toMap ; + new DelegateIndex0(pDelegates) + } + private[this] def delegateIndex(ref: ProjectRef, confs: Seq[Configuration])(projectInherit: ProjectRef => Seq[ProjectRef], configInherit: (ResolvedReference, ConfigKey) => Seq[ConfigKey]): ProjectDelegates = + { + val refDelegates = withRawBuilds(linearize(Select(ref), false)(projectInherit)) + val configs = confs map { c => axisDelegates(configInherit, ref, ConfigKey(c.name)) } + val tasks = confs map { c => axisDelegates(configInherit, ref, ConfigKey(c.name)) } + new ProjectDelegates(ref, refDelegates, configs.toMap) + } + def axisDelegates[T](direct: (ResolvedReference, T) => Seq[T], ref: ResolvedReference, init: T): (T, Seq[ScopeAxis[T]]) = + ( init, linearize(Select(init))(direct(ref, _)) ) + + def linearize[T](axis: ScopeAxis[T], appendGlobal: Boolean = true)(inherit: T => Seq[T]): Seq[ScopeAxis[T]] = axis match { - case Select(x) => (Dag.topologicalSort(x)(inherit).map(Select.apply).reverse ++ append).distinct - case Global | This => append + case Select(x) => topologicalSort(x, appendGlobal)(inherit) + case Global | This => if(appendGlobal) Global :: Nil else Nil } + + def topologicalSort[T](node: T, appendGlobal: Boolean)(dependencies: T => Seq[T]): Seq[ScopeAxis[T]] = + { + val o = Dag.topologicalSortUnchecked(node)(dependencies).map(Select.apply) + if(appendGlobal) o ::: Global :: Nil else o + } } @@ -186,4 +232,22 @@ final case class ConfigKey(name: String) object ConfigKey { implicit def configurationToKey(c: Configuration): ConfigKey = ConfigKey(c.name) -} \ No newline at end of file +} + +sealed trait DelegateIndex +{ + def project(ref: ProjectRef): Seq[ScopeAxis[ResolvedReference]] + def config(ref: ProjectRef, conf: ConfigKey): Seq[ScopeAxis[ConfigKey]] +// def task(ref: ProjectRef, task: ScopedKey[_]): Seq[ScopeAxis[ScopedKey[_]]] +// def extra(ref: ProjectRef, e: AttributeMap): Seq[ScopeAxis[AttributeMap]] +} +private final class DelegateIndex0(refs: Map[ProjectRef, ProjectDelegates]) extends DelegateIndex +{ + def project(ref: ProjectRef): Seq[ScopeAxis[ResolvedReference]] = refs.get(ref) match { case Some(pd) => pd.refs; case None => Nil } + def config(ref: ProjectRef, conf: ConfigKey): Seq[ScopeAxis[ConfigKey]] = + refs.get(ref) match { + case Some(pd) => pd.confs.get(conf) match { case Some(cs) => cs; case None => Nil } + case None => Nil + } +} +private final class ProjectDelegates(val ref: ProjectRef, val refs: Seq[ScopeAxis[ResolvedReference]], val confs: Map[ConfigKey, Seq[ScopeAxis[ConfigKey]]]) \ No newline at end of file diff --git a/util/collection/Dag.scala b/util/collection/Dag.scala index 29b43038a..8a6d0ba46 100644 --- a/util/collection/Dag.scala +++ b/util/collection/Dag.scala @@ -36,6 +36,24 @@ object Dag finished.toList; } + // doesn't check for cycles + def topologicalSortUnchecked[T](node: T)(dependencies: T => Iterable[T]): List[T] = + { + val discovered = new mutable.HashSet[T] + var finished: List[T] = Nil + + def visitAll(nodes: Iterable[T]) = nodes foreach visit + def visit(node : T){ + if (!discovered(node)) { + discovered(node) = true; + visitAll(dependencies(node)) + finished ::= node; + } + } + + visit(node); + finished; + } final class Cyclic(val value: Any, val all: List[Any], val complete: Boolean) extends Exception( "Cyclic reference involving " + (if(complete) all.mkString(", ") else value) ) { diff --git a/util/collection/PMap.scala b/util/collection/PMap.scala index 95bb1b6b0..febab0286 100644 --- a/util/collection/PMap.scala +++ b/util/collection/PMap.scala @@ -12,6 +12,8 @@ trait RMap[K[_], V[_]] def get[T](k: K[T]): Option[V[T]] def contains[T](k: K[T]): Boolean def toSeq: Seq[(K[_], V[_])] + def keys: Iterable[K[_]] + def values: Iterable[V[_]] } trait IMap[K[_], V[_]] extends (K ~> V) with RMap[K,V] @@ -54,6 +56,8 @@ object IMap def mapValues[V2[_]](f: V ~> V2) = new IMap0[K,V2](backing.mapValues(x => f(x)).toMap) def toSeq = backing.toSeq + def keys = backing.keys + def values = backing.values override def toString = backing.toString } @@ -83,6 +87,9 @@ class DelegatingPMap[K[_], V[_]](backing: mutable.Map[K[_], V[_]]) extends Abstr v } def toSeq = backing.toSeq + def keys = backing.keys + def values = backing.values + private[this] def cast[T](v: V[_]): V[T] = v.asInstanceOf[V[T]] private[this] def cast[T](o: Option[V[_]]): Option[V[T]] = o map cast[T] diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index aabb4c955..40d54de0d 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -68,7 +68,7 @@ trait Init[Scope] def asTransform(s: Settings[Scope]): ScopedKey ~> Id = new (ScopedKey ~> Id) { def apply[T](k: ScopedKey[T]): T = getValue(s, k) } - def getValue[T](s: Settings[Scope], k: ScopedKey[T]) = s.get(k.scope, k.key).get + def getValue[T](s: Settings[Scope], k: ScopedKey[T]) = s.get(k.scope, k.key) getOrElse error("Internal settings error: invalid reference to " + display(k)) def asFunction[T](s: Settings[Scope]): ScopedKey[T] => T = k => getValue(s, k) def compiled(init: Seq[Setting[_]], actual: Boolean = true)(implicit delegates: Scope => Seq[Scope], scopeLocal: ScopeLocal): CompiledMap = @@ -124,18 +124,16 @@ trait Init[Scope] } private[this] def delegateForKey[T](sMap: ScopedMap, k: ScopedKey[T], scopes: Seq[Scope], refKey: ScopedKey[_], isFirst: Boolean): ScopedKey[T] = { - val scache = PMap.empty[ScopedKey, ScopedKey] def resolve(search: Seq[Scope]): ScopedKey[T] = search match { case Seq() => throw Uninitialized(k, refKey) case Seq(x, xs @ _*) => val sk = ScopedKey(x, k.key) - scache.getOrUpdate(sk, if(defines(sMap, sk, refKey, isFirst)) sk else resolve(xs)) + val definesKey = (refKey != sk || !isFirst) && (sMap contains sk) + if(definesKey) sk else resolve(xs) } resolve(scopes) } - private[this] def defines(map: ScopedMap, key: ScopedKey[_], refKey: ScopedKey[_], isFirst: Boolean): Boolean = - (map get key) match { case Some(Seq(x, _*)) => (refKey != key) || !isFirst; case _ => false } private[this] def applyInits(ordered: Seq[Compiled])(implicit delegates: Scope => Seq[Scope]): Settings[Scope] = (empty /: ordered){ (m, comp) => comp.eval(m) } @@ -149,7 +147,7 @@ trait Init[Scope] final class Uninitialized(val key: ScopedKey[_], val refKey: ScopedKey[_], msg: String) extends Exception(msg) def Uninitialized(key: ScopedKey[_], refKey: ScopedKey[_]): Uninitialized = - new Uninitialized(key, refKey, "Reference to uninitialized setting " + key.key.label + " (in " + key.scope + ") from " + refKey.key.label +" (in " + refKey.scope + ")") + new Uninitialized(key, refKey, "Reference to uninitialized setting " + display(key) + " from " + display(refKey)) final class Compiled(val key: ScopedKey[_], val dependencies: Iterable[ScopedKey[_]], val eval: Settings[Scope] => Settings[Scope]) { override def toString = display(key)