From 963acca8ade599c8f47f4ddfe0e02ccd1c99a4b4 Mon Sep 17 00:00:00 2001 From: BitToby <218712309+bittoby@users.noreply.github.com> Date: Wed, 18 Mar 2026 06:53:54 +0200 Subject: [PATCH] [2.x] fix: Preserve user-specified scope axes in command instead of silently discarding them (#8916) **Problem** set every silently discards scope axes the user provides. Running: set every Test / sources := Nil empties sources in **all** scopes including Compile, not just Test. This happens because rescope in SettingCompletions.setAll strips the scope and forces Global. **Solution** Changed rescope to keep the user's config/task/extra axes and only wildcard the project axis. When no scope is given, behavior is unchanged - all axes match everything as before. --- .../sbt/internal/SettingCompletions.scala | 20 +++++++++++++++++-- .../set-every-scoped/a/src/main/scala/A.scala | 1 + .../set-every-scoped/b/src/main/scala/B.scala | 1 + .../sbt-test/tests/set-every-scoped/build.sbt | 18 +++++++++++++++++ .../src/sbt-test/tests/set-every-scoped/test | 10 ++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 sbt-app/src/sbt-test/tests/set-every-scoped/a/src/main/scala/A.scala create mode 100644 sbt-app/src/sbt-test/tests/set-every-scoped/b/src/main/scala/B.scala create mode 100644 sbt-app/src/sbt-test/tests/set-every-scoped/build.sbt create mode 100644 sbt-app/src/sbt-test/tests/set-every-scoped/test diff --git a/main/src/main/scala/sbt/internal/SettingCompletions.scala b/main/src/main/scala/sbt/internal/SettingCompletions.scala index 0f4157eb1..77e3cae67 100644 --- a/main/src/main/scala/sbt/internal/SettingCompletions.scala +++ b/main/src/main/scala/sbt/internal/SettingCompletions.scala @@ -55,12 +55,28 @@ private[sbt] object SettingCompletions { def resolve(s: Setting[?]): Seq[Setting[?]] = Load.transformSettings(projectScope, currentRef.build, rootProject, s :: Nil) + def axisMatches[T](user: ScopeAxis[T], existing: ScopeAxis[T]): Boolean = + user match + case Zero | This => true + case Select(_) => user == existing + def rescope[T](setting: Setting[T]): Seq[Setting[?]] = { val akey = setting.key.key - val global = ScopedKey(Global, akey) + val userScope = setting.key.scope + val baseScope = Scope( + Zero, + Scope.subThis(Zero, userScope.config), + Scope.subThis(Zero, userScope.task), + Scope.subThis(Zero, userScope.extra), + ) + val global = ScopedKey(baseScope, akey) val globalSetting = resolve(Def.setting(global, setting.init, setting.pos)) globalSetting ++ allDefs.flatMap { d => - if d.key == akey then Seq((d.scope / SettingKey(akey)) := global.value) + if d.key == akey + && axisMatches(userScope.config, d.scope.config) + && axisMatches(userScope.task, d.scope.task) + && axisMatches(userScope.extra, d.scope.extra) + then Seq((d.scope / SettingKey(akey)) := global.value) else Nil } } diff --git a/sbt-app/src/sbt-test/tests/set-every-scoped/a/src/main/scala/A.scala b/sbt-app/src/sbt-test/tests/set-every-scoped/a/src/main/scala/A.scala new file mode 100644 index 000000000..69c493db2 --- /dev/null +++ b/sbt-app/src/sbt-test/tests/set-every-scoped/a/src/main/scala/A.scala @@ -0,0 +1 @@ +object A diff --git a/sbt-app/src/sbt-test/tests/set-every-scoped/b/src/main/scala/B.scala b/sbt-app/src/sbt-test/tests/set-every-scoped/b/src/main/scala/B.scala new file mode 100644 index 000000000..251ef7397 --- /dev/null +++ b/sbt-app/src/sbt-test/tests/set-every-scoped/b/src/main/scala/B.scala @@ -0,0 +1 @@ +object B diff --git a/sbt-app/src/sbt-test/tests/set-every-scoped/build.sbt b/sbt-app/src/sbt-test/tests/set-every-scoped/build.sbt new file mode 100644 index 000000000..4e073fe44 --- /dev/null +++ b/sbt-app/src/sbt-test/tests/set-every-scoped/build.sbt @@ -0,0 +1,18 @@ +val a = project +val b = project + +val checkCompileSourcesNonEmpty = taskKey[Unit]("Verify Compile / sources is still non-empty") + +checkCompileSourcesNonEmpty := { + val srcs = (Compile / sources).value + if (srcs.isEmpty) + sys.error("Compile / sources should not be empty, but it was.") +} + +val checkTestSourcesEmpty = taskKey[Unit]("Verify Test / sources is empty") + +checkTestSourcesEmpty := { + val srcs = (Test / sources).value + if (srcs.nonEmpty) + sys.error(s"Test / sources should be empty, but had: ${srcs}") +} diff --git a/sbt-app/src/sbt-test/tests/set-every-scoped/test b/sbt-app/src/sbt-test/tests/set-every-scoped/test new file mode 100644 index 000000000..612019c18 --- /dev/null +++ b/sbt-app/src/sbt-test/tests/set-every-scoped/test @@ -0,0 +1,10 @@ +# set every with a scoped key should only affect that scope across all projects +> set every Test / sources := Nil + +# Compile / sources should still be non-empty for all projects +> a/checkCompileSourcesNonEmpty +> b/checkCompileSourcesNonEmpty + +# Test / sources should be empty for all projects +> a/checkTestSourcesEmpty +> b/checkTestSourcesEmpty