Require projects to have unique target directories.

Configuring projects so that target directories overlap is usually
unintentional and the error message that results is usually unrelated
to the cause.
This commit is contained in:
Mark Harrah 2013-06-18 18:29:01 -04:00
parent 52f1ed1819
commit 2f9d68e869
9 changed files with 38 additions and 3 deletions

View File

@ -138,7 +138,8 @@ object Load
lazy val rootEval = lazyEval(loaded.units(loaded.root).unit) lazy val rootEval = lazyEval(loaded.units(loaded.root).unit)
val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), config.injectSettings)) val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), config.injectSettings))
val delegates = config.delegates(loaded) val delegates = config.delegates(loaded)
val data = makeSettings(settings, delegates, config.scopeLocal)( Project.showLoadingKey( loaded ) ) val data = Def.make(settings)(delegates, config.scopeLocal, Project.showLoadingKey( loaded ) )
Project.checkTargets(data) foreach error
val index = structureIndex(data, settings, loaded.extra(data)) val index = structureIndex(data, settings, loaded.extra(data))
val streams = mkStreams(projects, loaded.root, data) val streams = mkStreams(projects, loaded.root, data)
(rootEval, new sbt.BuildStructure(projects, loaded.root, settings, data, index, streams, delegates, config.scopeLocal)) (rootEval, new sbt.BuildStructure(projects, loaded.root, settings, data, index, streams, delegates, config.scopeLocal))

View File

@ -227,9 +227,33 @@ object Project extends ProjectExtra
} }
def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap = 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) } vopt match { case Some(v) => attributes.put(key, v); case None => attributes.remove(key) }
@deprecated("Use Def.make", "0.13.0")
def makeSettings(settings: Seq[Def.Setting[_]], delegates: Scope => Seq[Scope], scopeLocal: ScopedKey[_] => Seq[Def.Setting[_]])(implicit display: Show[ScopedKey[_]]) = def makeSettings(settings: Seq[Def.Setting[_]], delegates: Scope => Seq[Scope], scopeLocal: ScopedKey[_] => Seq[Def.Setting[_]])(implicit display: Show[ScopedKey[_]]) =
Def.make(settings)(delegates, scopeLocal, display) Def.make(settings)(delegates, scopeLocal, display)
private[sbt] def checkTargets(data: Settings[Scope]): Option[String] =
{
val dups = overlappingTargets(allTargets(data))
if(dups.isEmpty)
None
else {
val dupStrs = dups map { case (dir, scopes) =>
s"${dir.getAbsolutePath}:\n\t${scopes.mkString("\n\t")}"
}
Some( s"Overlapping output directories:${dupStrs.mkString}" )
}
}
private[this] def overlappingTargets(targets: Seq[(ProjectRef,File)]): Map[File, Seq[ProjectRef]] =
targets.groupBy(_._2).filter(_._2.size > 1).mapValues(_.map(_._1))
private[this] def allTargets(data: Settings[Scope]): Seq[(ProjectRef,File)] =
{
import ScopeFilter._
val allProjects = ScopeFilter(Make.inAnyProject)
val targetAndRef = Def.setting { (Keys.thisProjectRef.value, Keys.target.value) }
new SettingKeyAll(targetAndRef).all(allProjects) evaluate data
}
def equal(a: ScopedKey[_], b: ScopedKey[_], mask: ScopeMask): Boolean = def equal(a: ScopedKey[_], b: ScopedKey[_], mask: ScopeMask): Boolean =
a.key == b.key && Scope.equal(a.scope, b.scope, mask) a.key == b.key && Scope.equal(a.scope, b.scope, mask)

View File

@ -185,7 +185,7 @@ object TestBuild
def structure(env: Env, settings: Seq[Setting[_]], current: ProjectRef): Structure = def structure(env: Env, settings: Seq[Setting[_]], current: ProjectRef): Structure =
{ {
implicit val display = Def.showRelativeKey(current, env.allProjects.size > 1) implicit val display = Def.showRelativeKey(current, env.allProjects.size > 1)
val data = Project.makeSettings(settings, env.delegates, const(Nil)) val data = Def.make(settings)(env.delegates, const(Nil), display)
val keys = data.allKeys( (s, key) => ScopedKey(s, key)) val keys = data.allKeys( (s, key) => ScopedKey(s, key))
val keyMap = keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[_]] val keyMap = keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[_]]
new Structure(env, current, data, KeyIndex(keys), keyMap) new Structure(env, current, data, KeyIndex(keys), keyMap)

View File

@ -0,0 +1,3 @@
lazy val x = project in file("x")
lazy val y = project in file(IO.read(file("ydir")).trim)

View File

@ -0,0 +1 @@
x

View File

@ -0,0 +1,4 @@
$ copy-file changes/xdir ydir
# should detect collision between x and y both having a target directory of 'x/target'
-> reload

View File

@ -0,0 +1 @@
y

View File

@ -17,6 +17,7 @@ Features, fixes, changes with compatibility implications (incomplete, please hel
- Fixed the default classifier for tests to be ``tests`` for proper Maven compatibility. - Fixed the default classifier for tests to be ``tests`` for proper Maven compatibility.
- The global settings and plugins directories are now versioned. Global settings go in ``~/.sbt/0.13/`` and global plugins in ``~/.sbt/0.13/plugins/`` by default. Explicit overrides, such as via the ``sbt.global.base`` system property, are still respected. (gh-735) - The global settings and plugins directories are now versioned. Global settings go in ``~/.sbt/0.13/`` and global plugins in ``~/.sbt/0.13/plugins/`` by default. Explicit overrides, such as via the ``sbt.global.base`` system property, are still respected. (gh-735)
- sbt no longer canonicalizes files passed to scalac. (gh-723) - sbt no longer canonicalizes files passed to scalac. (gh-723)
- sbt now enforces that each project must have a unique ``target`` directory.
Features Features
-------- --------

View File

@ -496,7 +496,7 @@ trait Init[Scope]
def mapConstant(g: MapConstant): Initialize[T] = new Optional(a map mapConstantT(g).fn, f) def mapConstant(g: MapConstant): Initialize[T] = new Optional(a map mapConstantT(g).fn, f)
def evaluate(ss: Settings[Scope]): T = f( a.flatMap( i => trapBadRef(evaluateT(ss)(i)) ) ) def evaluate(ss: Settings[Scope]): T = f( a.flatMap( i => trapBadRef(evaluateT(ss)(i)) ) )
// proper solution is for evaluate to be deprecated or for external use only and a new internal method returning Either be used // proper solution is for evaluate to be deprecated or for external use only and a new internal method returning Either be used
private[this] def trapBadRef[A](run: => A): Option[A] = try Some(run) catch { case e: InvalidReferenceException => None } private[this] def trapBadRef[A](run: => A): Option[A] = try Some(run) catch { case e: InvalidReference => None }
} }
private[sbt] final class Value[T](val value: () => T) extends Initialize[T] private[sbt] final class Value[T](val value: () => T) extends Initialize[T]
{ {