From cbb953279ccac742867c9d76acee3ae5a46e0455 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 31 Mar 2018 20:28:09 +0200 Subject: [PATCH 1/6] Intial implementation of CompositeProject --- main/src/main/scala/sbt/Project.scala | 8 +++++++- main/src/main/scala/sbt/internal/BuildDef.scala | 3 ++- .../sbt/internal/EvaluateConfigurations.scala | 10 +++++----- .../project/sbt-composite-projects/a/A.scala | 1 + .../project/sbt-composite-projects/a/a.sbt | 2 ++ .../project/sbt-composite-projects/b/build.sbt | 2 ++ .../project/sbt-composite-projects/build.sbt | 13 +++++++++++++ .../sbt-composite-projects/changes/basic.sbt | 1 + .../project/sbt-composite-projects/other.sbt | 1 + .../project/sbt-composite-projects/test | 17 +++++++++++++++++ 10 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/build.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/other.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/test diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index f292ca0e1..0fe82b457 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -119,7 +119,13 @@ sealed trait ProjectDefinition[PR <: ProjectReference] { if (ts.isEmpty) Nil else s"$label: $ts" :: Nil } -sealed trait Project extends ProjectDefinition[ProjectReference] { +trait CompositeProject { + def componentProjects: Seq[Project] +} + +sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject { + def componentProjects: Seq[Project] = this :: Nil + private[sbt] def copy( id: String = id, base: File = base, diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 96c4641f6..4194613ae 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -17,7 +17,8 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(baseDirectory: File): Seq[Project] = projects - def projects: Seq[Project] = ReflectUtilities.allVals[Project](this).values.toSeq + def projects: Seq[Project] = + ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects) // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 6b4e77f91..872b8e33f 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -123,10 +123,10 @@ private[sbt] object EvaluateConfigurations { // Tracks all the files we generated from evaluating the sbt file. val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated)) loader => { - val projects = - definitions.values(loader).collect { - case p: Project => resolveBase(file.getParentFile, p) - } + val projects = definitions.values(loader).flatMap { + case p: CompositeProject => p.componentProjects.map(resolveBase(file.getParentFile, _)) + case _ => Nil + } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { case DslEntry.ProjectSettings(_) => true @@ -234,7 +234,7 @@ private[sbt] object EvaluateConfigurations { } private[this] def extractedValTypes: Seq[String] = - Seq(classOf[Project], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) + Seq(classOf[CompositeProject], classOf[InputKey[_]], classOf[TaskKey[_]], classOf[SettingKey[_]]).map(_.getName) private[this] def evaluateDefinitions(eval: Eval, name: String, imports: Seq[(String, Int)], definitions: Seq[(String, LineRange)], file: Option[File]): compiler.EvalDefinitions = { diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala b/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala new file mode 100644 index 000000000..528ffce71 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala @@ -0,0 +1 @@ +object A \ No newline at end of file diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt new file mode 100644 index 000000000..67966f6d2 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt @@ -0,0 +1,2 @@ +val aa = taskKey[Unit]("A task in the 'a' project") +aa := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt new file mode 100644 index 000000000..3c7dc5056 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt @@ -0,0 +1,2 @@ +val h = taskKey[Unit]("A task in project 'b'") +h := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt new file mode 100644 index 000000000..cd56ca9e2 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -0,0 +1,13 @@ +import sbt.internal.AddSettings +import sbt.CompositeProject + +// Based on sbt-file-projects test +val cross = new CompositeProject +{ + val p1 = Project.apply("a", new File("a")) + val p2 = Project.apply("b", new File("b")) + def componentProjects: Seq[Project] = Seq(p1, p2) +} + +val g = taskKey[Unit]("A task in the root project") +g := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt new file mode 100644 index 000000000..c128b140e --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/basic.sbt @@ -0,0 +1 @@ +lazy val root = (project in file(".")) diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/other.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/other.sbt new file mode 100644 index 000000000..94e5f2363 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/other.sbt @@ -0,0 +1 @@ +val c = project diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test new file mode 100644 index 000000000..dbf2e5289 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -0,0 +1,17 @@ +> g +-> root/compile +> a/compile +> a/aa +> b/compile +> b/h +> c/compile + +$ copy-file changes/basic.sbt basic.sbt +> reload +> g +> root/compile +> a/compile +> a/aa +> b/compile +> b/h +> c/compile From 25bbc5f488f608e102907cc4f222f26257c74254 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Mon, 2 Apr 2018 20:46:07 +0200 Subject: [PATCH 2/6] Add shadow tests for CompositeProjects --- .../project/sbt-composite-projects/build.sbt | 13 +++++- .../sbt-composite-projects/changes/shadow.sbt | 40 +++++++++++++++++++ .../project/sbt-composite-projects/test | 5 +++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt index cd56ca9e2..7bd373aa4 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -1,8 +1,10 @@ import sbt.internal.AddSettings import sbt.CompositeProject +lazy val check = taskKey[Unit]("check") + // Based on sbt-file-projects test -val cross = new CompositeProject +lazy val cross = new CompositeProject { val p1 = Project.apply("a", new File("a")) val p2 = Project.apply("b", new File("b")) @@ -11,3 +13,12 @@ val cross = new CompositeProject val g = taskKey[Unit]("A task in the root project") g := println("Hello.") + + +check := { + val verP1 = (version in cross.p1).?.value + assert (verP1 == Some("0.1.0-SNAPSHOT")) + + val verP2 = (version in cross.p2).?.value + assert (verP2 == Some("0.1.0-SNAPSHOT")) +} diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt new file mode 100644 index 000000000..9bf54ab9f --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt @@ -0,0 +1,40 @@ +import sbt.internal.AddSettings +import sbt.CompositeProject + +lazy val check = taskKey[Unit]("check") + +lazy val a = (project in file("a")) + .settings( + version := "0.2.0" + ) + +// Based on sbt-file-projects test +lazy val cross = new CompositeProject +{ + val p1 = Project.apply("a", new File("a")) + val p2 = Project.apply("b", new File("b")) + def componentProjects: Seq[Project] = Seq(p1, p2) +} + +lazy val b = (project in file("b")) + .settings( + version := "0.2.0" + ) + +val g = taskKey[Unit]("A task in the root project") +g := println("Hello.") + + +check := { + val verP1 = (version in cross.p1).?.value + assert (verP1 == Some("0.2.0"))//Some("0.1.0-SNAPSHOT")) + + val verP2 = (version in cross.p2).?.value + assert (verP2 == Some("0.1.0-SNAPSHOT")) + + val verA = (version in a).?.value + assert (verA == Some("0.2.0")) + + val verB = (version in b).?.value + assert (verA == Some("0.2.0")) +} diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test index dbf2e5289..868ecac69 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/test +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -15,3 +15,8 @@ $ copy-file changes/basic.sbt basic.sbt > b/compile > b/h > c/compile +> check + +$ copy-file changes/shadow.sbt build.sbt +> reload +> check From 03fc4ac68640cebdfcc8f7ca8f04efa0aea57e22 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Wed, 4 Apr 2018 21:01:05 +0200 Subject: [PATCH 3/6] Ensure unique project Id in composite projects --- main/src/main/scala/sbt/Project.scala | 21 +++++++++++ .../main/scala/sbt/internal/BuildDef.scala | 4 ++- .../sbt/internal/EvaluateConfigurations.scala | 6 ++-- .../project/sbt-composite-projects/a/a.sbt | 2 -- .../sbt-composite-projects/b/build.sbt | 2 -- .../project/sbt-composite-projects/build.sbt | 24 ++++++++----- .../sbt-composite-projects/changes/shadow.sbt | 36 ++++++++----------- .../sbt-composite-projects/js/build.sbt | 2 ++ .../sbt-composite-projects/{a => jvm}/A.scala | 0 .../project/sbt-composite-projects/jvm/a.sbt | 2 ++ .../project/sbt-composite-projects/test | 24 ++++++++----- 11 files changed, 78 insertions(+), 45 deletions(-) delete mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt delete mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt rename sbt/src/sbt-test/project/sbt-composite-projects/{a => jvm}/A.scala (100%) create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 2571b011e..1342cce77 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -125,6 +125,27 @@ trait CompositeProject { def componentProjects: Seq[Project] } +object CompositeProject { + + /** + * If two projects with the same id appear in `compositeProjects` and + * in `compositeProjects.map(_.componentProjects)`, the one in + * `compositeProjects` wins. + * This is necessary for backward compatibility with the idiom: + * lazy val foo = crossProject + * lazy val fooJVM = foo.jvm.setting + */ + def uniqueId(compositeProjects: Seq[Project]): Seq[Project] = { + for (p <- compositeProjects.flatMap(_.componentProjects)) yield { + compositeProjects.find(_.id == p.id) match { + case Some(overridingProject) => overridingProject + case None => p + } + } + + } +} + sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject { def componentProjects: Seq[Project] = this :: Nil diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 52a9059d9..03c03a40e 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -18,7 +18,9 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects def projects: Seq[Project] = - ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects) + CompositeProject.uniqueId( + ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects)) + // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index cfb599a20..c7e6781e9 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -151,11 +151,13 @@ private[sbt] object EvaluateConfigurations { val allGeneratedFiles = (definitions.generated ++ dslEntries.flatMap(_.generated)) loader => { - val projects = - definitions.values(loader).flatMap { + val projects = { + val compositeProjects = definitions.values(loader).flatMap { case p: CompositeProject => p.componentProjects.map(resolveBase(file.getParentFile, _)) case _ => Nil } + CompositeProject.uniqueId(compositeProjects) + } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { case DslEntry.ProjectSettings(_) => true diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt deleted file mode 100644 index 67966f6d2..000000000 --- a/sbt/src/sbt-test/project/sbt-composite-projects/a/a.sbt +++ /dev/null @@ -1,2 +0,0 @@ -val aa = taskKey[Unit]("A task in the 'a' project") -aa := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt deleted file mode 100644 index 3c7dc5056..000000000 --- a/sbt/src/sbt-test/project/sbt-composite-projects/b/build.sbt +++ /dev/null @@ -1,2 +0,0 @@ -val h = taskKey[Unit]("A task in project 'b'") -h := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt index 7bd373aa4..de8f1c134 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -4,21 +4,29 @@ import sbt.CompositeProject lazy val check = taskKey[Unit]("check") // Based on sbt-file-projects test -lazy val cross = new CompositeProject +lazy val foo = new CompositeProject { - val p1 = Project.apply("a", new File("a")) - val p2 = Project.apply("b", new File("b")) - def componentProjects: Seq[Project] = Seq(p1, p2) + val jvm = Project.apply("jvm", new File("jvm")) + val js = Project.apply("js", new File("js")) + def componentProjects: Seq[Project] = Seq(jvm, js) } +lazy val fooJVM = foo.jvm + +lazy val bar = project + .dependsOn(foo.jvm) + val g = taskKey[Unit]("A task in the root project") g := println("Hello.") check := { - val verP1 = (version in cross.p1).?.value - assert (verP1 == Some("0.1.0-SNAPSHOT")) + val verJvm = (version in foo.jvm).?.value + assert (verJvm == Some("0.1.0-SNAPSHOT")) - val verP2 = (version in cross.p2).?.value - assert (verP2 == Some("0.1.0-SNAPSHOT")) + val verFooJvm = (version in fooJVM).?.value + assert (verFooJvm == Some("0.1.0-SNAPSHOT")) + + val verJs = (version in foo.js).?.value + assert (verJs == Some("0.1.0-SNAPSHOT")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt index 9bf54ab9f..47334ba0f 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt @@ -3,38 +3,30 @@ import sbt.CompositeProject lazy val check = taskKey[Unit]("check") -lazy val a = (project in file("a")) - .settings( - version := "0.2.0" - ) - // Based on sbt-file-projects test -lazy val cross = new CompositeProject +lazy val foo = new CompositeProject { - val p1 = Project.apply("a", new File("a")) - val p2 = Project.apply("b", new File("b")) - def componentProjects: Seq[Project] = Seq(p1, p2) + val jvm = Project.apply("jvm", new File("jvm")) + val js = Project.apply("js", new File("js")) + def componentProjects: Seq[Project] = Seq(jvm, js) } -lazy val b = (project in file("b")) - .settings( - version := "0.2.0" - ) +lazy val fooJVM = foo.jvm.settings(version := "0.2.0") // this one needs to win + +lazy val bar = project + .dependsOn(foo.jvm) val g = taskKey[Unit]("A task in the root project") g := println("Hello.") check := { - val verP1 = (version in cross.p1).?.value - assert (verP1 == Some("0.2.0"))//Some("0.1.0-SNAPSHOT")) + val verJvm = (version in foo.jvm).?.value + assert (verJvm == Some("0.2.0")) - val verP2 = (version in cross.p2).?.value - assert (verP2 == Some("0.1.0-SNAPSHOT")) + val verFooJvm = (version in fooJVM).?.value + assert (verFooJvm == Some("0.2.0")) - val verA = (version in a).?.value - assert (verA == Some("0.2.0")) - - val verB = (version in b).?.value - assert (verA == Some("0.2.0")) + val verJs = (version in foo.js).?.value + assert (verJs == Some("0.1.0-SNAPSHOT")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt new file mode 100644 index 000000000..0ddd49e30 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/js/build.sbt @@ -0,0 +1,2 @@ +val h = taskKey[Unit]("A task in project 'js'") +h := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala b/sbt/src/sbt-test/project/sbt-composite-projects/jvm/A.scala similarity index 100% rename from sbt/src/sbt-test/project/sbt-composite-projects/a/A.scala rename to sbt/src/sbt-test/project/sbt-composite-projects/jvm/A.scala diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt new file mode 100644 index 000000000..9b73a55bf --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/jvm/a.sbt @@ -0,0 +1,2 @@ +val aa = taskKey[Unit]("A task in the 'jvm' project") +aa := println("Hello.") diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test index 868ecac69..5bb98d747 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/test +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -1,22 +1,30 @@ > g -> root/compile -> a/compile -> a/aa -> b/compile -> b/h +> jvm/compile +> jvm/aa +> js/compile +> js/h > c/compile +> bar/compile $ copy-file changes/basic.sbt basic.sbt > reload > g > root/compile -> a/compile -> a/aa -> b/compile -> b/h +> jvm/compile +> jvm/aa +> js/compile +> js/h > c/compile +> bar/compile > check $ copy-file changes/shadow.sbt build.sbt > reload +> jvm/compile +> jvm/aa +> js/compile +> js/h +> c/compile +> bar/compile > check From 68c005e4b58a968a3c0ead2b342076d3e8054684 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 7 Apr 2018 01:04:45 +0200 Subject: [PATCH 4/6] Ensure precedence of top level Projects over ComponentProjects --- main/src/main/scala/sbt/Project.scala | 28 +++++++++----- .../main/scala/sbt/internal/BuildDef.scala | 9 +++-- .../sbt/internal/EvaluateConfigurations.scala | 15 ++++++-- notes/1.2.0/introduce-CompositeProject.md | 9 +++++ .../project/sbt-composite-projects/build.sbt | 14 ++++--- .../sbt-composite-projects/changes/shadow.sbt | 10 +++-- .../changes/shadowLazy.sbt | 37 +++++++++++++++++++ .../project/sbt-composite-projects/test | 10 +++++ 8 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 notes/1.2.0/introduce-CompositeProject.md create mode 100644 sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 1342cce77..789cb709f 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -125,25 +125,35 @@ trait CompositeProject { def componentProjects: Seq[Project] } -object CompositeProject { +private[sbt] object CompositeProject { /** - * If two projects with the same id appear in `compositeProjects` and - * in `compositeProjects.map(_.componentProjects)`, the one in - * `compositeProjects` wins. - * This is necessary for backward compatibility with the idiom: + * Expand user defined `projects` with the component projects of `compositeProjects`. + * + * If two projects with the same id appear in the user defined `projects` and + * in `compositeProjects.componentProjects`, the one in `projects` wins. + * This is necessary for backward compatibility with the idioms: + * {{{ * lazy val foo = crossProject - * lazy val fooJVM = foo.jvm.setting + * lazy val fooJS = foo.js.settings(...) + * lazy val fooJVM = foo.jvm.settings(...) + * }}} + * and the rarer: + * {{{ + * lazy val fooJS = foo.js.settings(...) + * lazy val foo = crossProject + * lazy val fooJVM = foo.jvm.settings(...) + * }}} */ - def uniqueId(compositeProjects: Seq[Project]): Seq[Project] = { + def expand(projects: Seq[Project], compositeProjects: Seq[CompositeProject]): Seq[Project] = { for (p <- compositeProjects.flatMap(_.componentProjects)) yield { - compositeProjects.find(_.id == p.id) match { + projects.find(_.id == p.id) match { case Some(overridingProject) => overridingProject case None => p } } - } + } sealed trait Project extends ProjectDefinition[ProjectReference] with CompositeProject { diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 03c03a40e..5a916acb5 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -17,10 +17,11 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects - def projects: Seq[Project] = - CompositeProject.uniqueId( - ReflectUtilities.allVals[CompositeProject](this).values.toSeq.flatMap(_.componentProjects)) - + def projects: Seq[Project] = { + val projects = ReflectUtilities.allVals[Project](this).values.toSeq + val compositeProjects = ReflectUtilities.allVals[CompositeProject](this).values.toSeq + CompositeProject.expand(projects, compositeProjects) + } // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index c7e6781e9..241b8c6b1 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -152,11 +152,18 @@ private[sbt] object EvaluateConfigurations { loader => { val projects = { - val compositeProjects = definitions.values(loader).flatMap { - case p: CompositeProject => p.componentProjects.map(resolveBase(file.getParentFile, _)) - case _ => Nil + + val projects = definitions.values(loader).collect { + case p: Project => p } - CompositeProject.uniqueId(compositeProjects) + + val compositeProjects = definitions.values(loader).collect { + case p: CompositeProject => p + } + + CompositeProject + .expand(projects, compositeProjects) + .map(resolveBase(file.getParentFile, _)) } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition { diff --git a/notes/1.2.0/introduce-CompositeProject.md b/notes/1.2.0/introduce-CompositeProject.md new file mode 100644 index 000000000..759d75a71 --- /dev/null +++ b/notes/1.2.0/introduce-CompositeProject.md @@ -0,0 +1,9 @@ + +[#3042]: https://github.com/sbt/sbt/issues/3042 +[#4056]: https://github.com/sbt/sbt/pull/4056 + +### Introduce CompositeProject + +### Improvements + +- Support for: `lazy val foo = someCompositeProject` (e.g.`CrossProject`) [#3042][]/[#4056][] diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt index de8f1c134..1537b2610 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/build.sbt @@ -6,11 +6,12 @@ lazy val check = taskKey[Unit]("check") // Based on sbt-file-projects test lazy val foo = new CompositeProject { - val jvm = Project.apply("jvm", new File("jvm")) - val js = Project.apply("js", new File("js")) + val jvm = Project.apply("jvm", new File("jvm")).settings(version := "0.1.0") // this one needs to win + val js = Project.apply("js", new File("js")).settings(version := "0.1.0") // this one needs to win def componentProjects: Seq[Project] = Seq(jvm, js) } +lazy val fooJS = foo.js lazy val fooJVM = foo.jvm lazy val bar = project @@ -22,11 +23,14 @@ g := println("Hello.") check := { val verJvm = (version in foo.jvm).?.value - assert (verJvm == Some("0.1.0-SNAPSHOT")) + assert (verJvm == Some("0.1.0")) val verFooJvm = (version in fooJVM).?.value - assert (verFooJvm == Some("0.1.0-SNAPSHOT")) + assert (verFooJvm == Some("0.1.0")) val verJs = (version in foo.js).?.value - assert (verJs == Some("0.1.0-SNAPSHOT")) + assert (verJs == Some("0.1.0")) + + val verFooJs = (version in fooJS).?.value + assert (verFooJs == Some("0.1.0")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt index 47334ba0f..0713e7f4b 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadow.sbt @@ -6,11 +6,12 @@ lazy val check = taskKey[Unit]("check") // Based on sbt-file-projects test lazy val foo = new CompositeProject { - val jvm = Project.apply("jvm", new File("jvm")) - val js = Project.apply("js", new File("js")) + val jvm = Project.apply("jvm", new File("jvm")).settings(version := "0.1.0") + val js = Project.apply("js", new File("js")).settings(version := "0.1.0") // this one needs to win def componentProjects: Seq[Project] = Seq(jvm, js) } +lazy val fooJS = foo.js lazy val fooJVM = foo.jvm.settings(version := "0.2.0") // this one needs to win lazy val bar = project @@ -28,5 +29,8 @@ check := { assert (verFooJvm == Some("0.2.0")) val verJs = (version in foo.js).?.value - assert (verJs == Some("0.1.0-SNAPSHOT")) + assert (verJs == Some("0.1.0")) + + val verFooJs = (version in fooJS).?.value + assert (verFooJs == Some("0.1.0")) } diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt new file mode 100644 index 000000000..492fbd621 --- /dev/null +++ b/sbt/src/sbt-test/project/sbt-composite-projects/changes/shadowLazy.sbt @@ -0,0 +1,37 @@ +import sbt.internal.AddSettings +import sbt.CompositeProject + +lazy val check = taskKey[Unit]("check") + +lazy val fooJS = foo.js.settings(version := "0.2.1") // this one needs to win + +// Based on sbt-file-projects test +lazy val foo = new CompositeProject +{ + val jvm = Project.apply("jvm", new File("jvm")).settings(version := "0.1.0") + val js = Project.apply("js", new File("js")).settings(version := "0.1.0") + def componentProjects: Seq[Project] = Seq(jvm, js) +} + +lazy val fooJVM = foo.jvm.settings(version := "0.2.0") // this one needs to win + +lazy val bar = project + .dependsOn(foo.jvm) + +val g = taskKey[Unit]("A task in the root project") +g := println("Hello.") + + +check := { + val verJvm = (version in foo.jvm).?.value + assert (verJvm == Some("0.2.0")) + + val verFooJvm = (version in fooJVM).?.value + assert (verFooJvm == Some("0.2.0")) + + val verJs = (version in foo.js).?.value + assert (verJs == Some("0.2.1")) + + val verFooJs = (version in fooJS).?.value + assert (verFooJs == Some("0.2.1")) +} diff --git a/sbt/src/sbt-test/project/sbt-composite-projects/test b/sbt/src/sbt-test/project/sbt-composite-projects/test index 5bb98d747..34bc8097d 100644 --- a/sbt/src/sbt-test/project/sbt-composite-projects/test +++ b/sbt/src/sbt-test/project/sbt-composite-projects/test @@ -28,3 +28,13 @@ $ copy-file changes/shadow.sbt build.sbt > c/compile > bar/compile > check + +$ copy-file changes/shadowLazy.sbt build.sbt +> reload +> jvm/compile +> jvm/aa +> js/compile +> js/h +> c/compile +> bar/compile +> check \ No newline at end of file From 6cce4f6fd980d308b7ada79721c3c5d85ca86cb5 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 7 Apr 2018 01:26:40 +0200 Subject: [PATCH 5/6] Remove duplicate Projects --- main/src/main/scala/sbt/Project.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 789cb709f..5f867dd94 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -152,7 +152,7 @@ private[sbt] object CompositeProject { case None => p } } - } + }.distinct } From b0ad1a44c06a36c4697a922b6135c73351f5d9ee Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sat, 7 Apr 2018 15:56:31 +0200 Subject: [PATCH 6/6] Remove projects parameter from CompositeProject.expand --- main/src/main/scala/sbt/Project.scala | 15 ++++++++------- main/src/main/scala/sbt/internal/BuildDef.scala | 7 ++----- .../sbt/internal/EvaluateConfigurations.scala | 10 +--------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 5f867dd94..bac52a877 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -128,10 +128,10 @@ trait CompositeProject { private[sbt] object CompositeProject { /** - * Expand user defined `projects` with the component projects of `compositeProjects`. + * Expand user defined projects with the component projects of `compositeProjects`. * - * If two projects with the same id appear in the user defined `projects` and - * in `compositeProjects.componentProjects`, the one in `projects` wins. + * If two projects with the same id appear in the user defined projects and + * in `compositeProjects.componentProjects`, the user defined project wins. * This is necessary for backward compatibility with the idioms: * {{{ * lazy val foo = crossProject @@ -145,11 +145,12 @@ private[sbt] object CompositeProject { * lazy val fooJVM = foo.jvm.settings(...) * }}} */ - def expand(projects: Seq[Project], compositeProjects: Seq[CompositeProject]): Seq[Project] = { + def expand(compositeProjects: Seq[CompositeProject]): Seq[Project] = { + val userProjects = compositeProjects.collect { case p: Project => p } for (p <- compositeProjects.flatMap(_.componentProjects)) yield { - projects.find(_.id == p.id) match { - case Some(overridingProject) => overridingProject - case None => p + userProjects.find(_.id == p.id) match { + case Some(userProject) => userProject + case None => p } } }.distinct diff --git a/main/src/main/scala/sbt/internal/BuildDef.scala b/main/src/main/scala/sbt/internal/BuildDef.scala index 5a916acb5..d4a435ab5 100644 --- a/main/src/main/scala/sbt/internal/BuildDef.scala +++ b/main/src/main/scala/sbt/internal/BuildDef.scala @@ -17,11 +17,8 @@ import sbt.internal.inc.ReflectUtilities trait BuildDef { def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects - def projects: Seq[Project] = { - val projects = ReflectUtilities.allVals[Project](this).values.toSeq - val compositeProjects = ReflectUtilities.allVals[CompositeProject](this).values.toSeq - CompositeProject.expand(projects, compositeProjects) - } + def projects: Seq[Project] = + CompositeProject.expand(ReflectUtilities.allVals[CompositeProject](this).values.toSeq) // TODO: Should we grab the build core settings here or in a plugin? def settings: Seq[Setting[_]] = Defaults.buildCore def buildLoaders: Seq[BuildLoader.Components] = Nil diff --git a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 241b8c6b1..26e5348ff 100644 --- a/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/main/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -152,18 +152,10 @@ private[sbt] object EvaluateConfigurations { loader => { val projects = { - - val projects = definitions.values(loader).collect { - case p: Project => p - } - val compositeProjects = definitions.values(loader).collect { case p: CompositeProject => p } - - CompositeProject - .expand(projects, compositeProjects) - .map(resolveBase(file.getParentFile, _)) + CompositeProject.expand(compositeProjects).map(resolveBase(file.getParentFile, _)) } val (settingsRaw, manipulationsRaw) = dslEntries map (_.result apply loader) partition {