diff --git a/main/src/main/scala/sbt/Load.scala b/main/src/main/scala/sbt/Load.scala index c89683af2..12398a0d0 100755 --- a/main/src/main/scala/sbt/Load.scala +++ b/main/src/main/scala/sbt/Load.scala @@ -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)) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 5fca5d0eb..bd2b753b6 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -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) diff --git a/main/src/test/scala/TestBuild.scala b/main/src/test/scala/TestBuild.scala index b1eeaeeec..e72f13a21 100644 --- a/main/src/test/scala/TestBuild.scala +++ b/main/src/test/scala/TestBuild.scala @@ -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) diff --git a/sbt/src/sbt-test/project/overlap-target/build.sbt b/sbt/src/sbt-test/project/overlap-target/build.sbt new file mode 100644 index 000000000..e96ecc2af --- /dev/null +++ b/sbt/src/sbt-test/project/overlap-target/build.sbt @@ -0,0 +1,3 @@ +lazy val x = project in file("x") + +lazy val y = project in file(IO.read(file("ydir")).trim) diff --git a/sbt/src/sbt-test/project/overlap-target/changes/xdir b/sbt/src/sbt-test/project/overlap-target/changes/xdir new file mode 100644 index 000000000..587be6b4c --- /dev/null +++ b/sbt/src/sbt-test/project/overlap-target/changes/xdir @@ -0,0 +1 @@ +x diff --git a/sbt/src/sbt-test/project/overlap-target/test b/sbt/src/sbt-test/project/overlap-target/test new file mode 100644 index 000000000..03ae9258a --- /dev/null +++ b/sbt/src/sbt-test/project/overlap-target/test @@ -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 diff --git a/sbt/src/sbt-test/project/overlap-target/ydir b/sbt/src/sbt-test/project/overlap-target/ydir new file mode 100644 index 000000000..975fbec82 --- /dev/null +++ b/sbt/src/sbt-test/project/overlap-target/ydir @@ -0,0 +1 @@ +y diff --git a/src/sphinx/Community/ChangeSummary_0.13.0.rst b/src/sphinx/Community/ChangeSummary_0.13.0.rst index dd4bc755f..7023eb3c4 100644 --- a/src/sphinx/Community/ChangeSummary_0.13.0.rst +++ b/src/sphinx/Community/ChangeSummary_0.13.0.rst @@ -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 -------- diff --git a/util/collection/src/main/scala/sbt/Settings.scala b/util/collection/src/main/scala/sbt/Settings.scala index bd025cb5c..722430912 100644 --- a/util/collection/src/main/scala/sbt/Settings.scala +++ b/util/collection/src/main/scala/sbt/Settings.scala @@ -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] {