diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 874e1d2b7..b5c3f811b 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -239,13 +239,22 @@ object Aggregation { private def maps[T, S](vs: Values[T])(f: T => S): Values[S] = vs map { case KeyValue(k, v) => KeyValue(k, f(v)) } + /** + * Returns the aggregated projects for the given reference. + * Build-level references (ThisBuild, BuildRef) do not aggregate - only project-level + * references participate in aggregation. This fixes issue #5349 where ThisBuild-scoped + * keys incorrectly used the root project's aggregates. + */ def projectAggregates[Proj]( proj: Option[Reference], extra: BuildUtil[Proj], reverse: Boolean ): Seq[ProjectRef] = - val resRef = proj.map(p => extra.projectRefFor(extra.resolveRef(p))) - resRef.toList.flatMap { ref => + val projectRef = proj.flatMap { + case _: BuildReference => None + case ref => Some(extra.projectRefFor(extra.resolveRef(ref))) + } + projectRef.toList.flatMap { ref => if reverse then extra.aggregates.reverse(ref) else extra.aggregates.forward(ref) } diff --git a/main/src/test/scala/sbt/internal/AggregationSpec.scala b/main/src/test/scala/sbt/internal/AggregationSpec.scala index d2f440b9e..bb4fbed5d 100644 --- a/main/src/test/scala/sbt/internal/AggregationSpec.scala +++ b/main/src/test/scala/sbt/internal/AggregationSpec.scala @@ -8,9 +8,35 @@ package sbt.internal +import java.net.URI +import sbt.{ BuildRef, Def, Scope } +import sbt.Def.ScopedKey +import sbt.ScopeAxis.{ Select, Zero } +import sbt.internal.TestBuild.{ Build, Env, Proj, Taskk } +import sbt.internal.util.AttributeKey +import sbt.librarymanagement.Configuration + object AggregationSpec extends verify.BasicTestSuite { val timing = Aggregation.timing(0, _: Long) + test( + "projectAggregates should return empty for BuildRef (ThisBuild-scoped keys do not aggregate, #5349)" + ) { + val buildURI = new URI("file", "///path/", null) + val config = Configuration.of("Compile", "compile") + val project = Proj("root", Nil, Seq(config)) + val build = Build(buildURI, Vector(project)) + val key = AttributeKey[String]("test") + val task = Taskk(key, Nil) + val env = Env(Vector(build), Vector(task)) + val scope = Scope(Select(BuildRef(buildURI)), Zero, Select(key), Zero) + val settings = Seq(Def.setting(ScopedKey(scope, key), Def.value("v"))) + val structure = TestBuild.structure(env, settings, build.allProjects.head._1) + val result = + Aggregation.projectAggregates(Some(BuildRef(buildURI)), structure.extra, reverse = false) + assert(result.isEmpty, s"BuildRef must not aggregate; got: $result") + } + test("timing should format total time properly") { assert(timing(101).startsWith("elapsed time: 0 s")) assert(timing(1000).startsWith("elapsed time: 1 s")) diff --git a/sbt-app/src/sbt-test/actions/thisbuild-no-aggregate/build.sbt b/sbt-app/src/sbt-test/actions/thisbuild-no-aggregate/build.sbt new file mode 100644 index 000000000..17abf53aa --- /dev/null +++ b/sbt-app/src/sbt-test/actions/thisbuild-no-aggregate/build.sbt @@ -0,0 +1,31 @@ +ThisBuild / scalaVersion := "2.13.16" + +lazy val mark = taskKey[Unit]("Creates a marker file to track where this task ran") + +lazy val root = (project in file(".")) + .aggregate(sub) + .settings( + name := "root", + mark := { + val toMark = baseDirectory.value / "root-ran" + if (toMark.exists) sys.error(s"Already ran ($toMark exists)") + else IO.touch(toMark) + } + ) + +lazy val sub = (project in file("sub")) + .settings( + name := "sub", + mark := { + val toMark = baseDirectory.value / "sub-ran" + if (toMark.exists) sys.error(s"Already ran ($toMark exists)") + else IO.touch(toMark) + } + ) + +ThisBuild / mark := { + val base = (ThisBuild / baseDirectory).value + val toMark = base / "build-ran" + if (toMark.exists) sys.error(s"Already ran ($toMark exists)") + else IO.touch(toMark) +} diff --git a/sbt-app/src/sbt-test/actions/thisbuild-no-aggregate/test b/sbt-app/src/sbt-test/actions/thisbuild-no-aggregate/test new file mode 100644 index 000000000..7a32a7989 --- /dev/null +++ b/sbt-app/src/sbt-test/actions/thisbuild-no-aggregate/test @@ -0,0 +1,19 @@ +# Verify ThisBuild-scoped keys do not participate in aggregation (sbt/sbt#5349, PR #8703). +# Each scope level has its own mark task creating distinct files: +# - ThisBuild/mark creates build-ran +# - root/mark creates root-ran +# - sub/mark creates sub-ran +# Before the fix, "show ThisBuild/mark" would aggregate and run root+sub marks. +# After the fix, it runs only the build-level task. +$ mkdir sub + +# ThisBuild/mark should only create build-ran, not trigger root or sub +> show ThisBuild/mark +$ exists build-ran +$ absent root-ran +$ absent sub/sub-ran + +# Verify project-level marks still work and do aggregate +> show root/mark +$ exists root-ran +$ exists sub/sub-ran