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)
val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), config.injectSettings))
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 streams = mkStreams(projects, loaded.root, data)
(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 =
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.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 =
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 =
{
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 keyMap = keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[_]]
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.
- 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 now enforces that each project must have a unique ``target`` directory.
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 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
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]
{