From 81edea48bc495f175325c1794d6c4816ea78c9f3 Mon Sep 17 00:00:00 2001 From: Daniil Sivak Date: Tue, 3 Mar 2026 22:09:14 +0300 Subject: [PATCH] [2.x] fix: Undefined per-project settings order (#8862) Closes #7173 --- main/src/main/scala/sbt/internal/Load.scala | 36 +++++++++++++------ .../i7173-cross-project-setting/build.sbt | 27 ++++++++++++++ .../project/i7173-cross-project-setting/test | 3 ++ 3 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 sbt-app/src/sbt-test/project/i7173-cross-project-setting/build.sbt create mode 100644 sbt-app/src/sbt-test/project/i7173-cross-project-setting/test diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 235d80a9e..2c98145e1 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -417,17 +417,31 @@ private[sbt] object Load { inScope(GlobalScope)(loaded.autos.globalSettings) ++ loaded.units.toSeq.flatMap { (uri, build) => val pluginBuildSettings = loaded.autos.buildSettings(uri) - val projectSettings = build.defined flatMap { (id, project) => - val ref = ProjectRef(uri, id) - val defineConfig: Seq[Setting[?]] = - for (c <- project.configurations) - yield ((ref / ConfigKey(c.name) / configuration) :== c) - val builtin: Seq[Setting[?]] = - (thisProject :== project) +: (thisProjectRef :== ref) +: defineConfig - val settings = builtin ++ injectSettings.project ++ project.settings - // map This to thisScope, Select(p) to mapRef(uri, rootProject, p) - transformSettings(projectScope(ref), uri, rootProject, settings) - } + // Collect transformed settings per project and split them into "own-scope" and "cross-project": + // "own-scope" - key's project axis is `ref` itself (or global) + // "cross-project" - key explicitly targets a different project (`otherProj / key += ...`) + // + // It's necessary so that global defaults always precedes any other project's append + // See https://github.com/sbt/sbt/issues/7173 + val (ownSettingsSeqs, crossSettingsSeqs) = + build.defined.toSeq.map { (id, project) => + val ref = ProjectRef(uri, id) + val defineConfig: Seq[Setting[?]] = + for (c <- project.configurations) + yield ((ref / ConfigKey(c.name) / configuration) :== c) + val builtin: Seq[Setting[?]] = + (thisProject :== project) +: (thisProjectRef :== ref) +: defineConfig + val settings = builtin ++ injectSettings.project ++ project.settings + // map This to thisScope, Select(p) to mapRef(uri, rootProject, p) + val transformed = transformSettings(projectScope(ref), uri, rootProject, settings) + transformed.partition { s => + s.key.scope.project match { + case Select(r) => r == ref + case _ => true // global scope + } + } + }.unzip + val projectSettings = (ownSettingsSeqs ++ crossSettingsSeqs).flatten val buildScope = Scope(Select(BuildRef(uri)), Zero, Zero, Zero) val buildBase = baseDirectory :== build.localBase val settings3 = pluginBuildSettings ++ (buildBase +: build.buildSettings) diff --git a/sbt-app/src/sbt-test/project/i7173-cross-project-setting/build.sbt b/sbt-app/src/sbt-test/project/i7173-cross-project-setting/build.sbt new file mode 100644 index 000000000..f9123638a --- /dev/null +++ b/sbt-app/src/sbt-test/project/i7173-cross-project-setting/build.sbt @@ -0,0 +1,27 @@ +val check = taskKey[Unit]("Verify cross-project source generator is applied") + +lazy val foo = project + .in(file("foo")) + .settings( + check := Def.uncached { + val gens = (Compile / sourceGenerators).value + assert( + gens.nonEmpty, + s"#7173: `foo / Compile / sourceGenerators` should contain the generator registered by fooAux" + ) + } + ) + +lazy val fooAux = project + .in(file("foo-aux")) + .settings( + foo / Compile / sourceGenerators += Def.task(Seq.empty[File]) + ) + +lazy val bar = project + +// Fails: +lazy val baz = project + +// Succeeds without i7173 edits: +// lazy val ewikqelkqweqopweo = project diff --git a/sbt-app/src/sbt-test/project/i7173-cross-project-setting/test b/sbt-app/src/sbt-test/project/i7173-cross-project-setting/test new file mode 100644 index 000000000..fa1b96977 --- /dev/null +++ b/sbt-app/src/sbt-test/project/i7173-cross-project-setting/test @@ -0,0 +1,3 @@ +# Adding an unrelated project (baz) should not prevent foo / Compile / sourceGenerators +# from including the generator registered by fooAux. +> foo/check