mirror of https://github.com/sbt/sbt.git
[2.x] fix: Fixes ThisBuild-scoped keys using root project's aggregates (#8703)
Build-level references (ThisBuild, BuildRef) should not participate in aggregation. Only project-level references should aggregate. Previously, when querying `ThisBuild/version`, the aggregation logic would resolve ThisBuild to a BuildRef, then convert it to the root project's ProjectRef, causing it to incorrectly use the root project's aggregate definitions. The fix uses pattern matching to distinguish BuildReference from other reference types, returning None (no aggregation) for build-level scopes. Fixes sbt/sbt#5349
This commit is contained in:
parent
499ec520a7
commit
f6319f19a3
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue