From a34bb4756cf36865c405ed27820ac0367c03bf05 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 6 Nov 2024 03:43:39 -0500 Subject: [PATCH 1/7] Resurrect property-based testing for slash syntax --- .../scala/sbt/BuildSettingsInstances.scala | 224 +++++---- .../src/test/scala/sbt/SlashSyntaxSpec.scala | 476 ++++++++++++++---- 2 files changed, 492 insertions(+), 208 deletions(-) diff --git a/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala index 00405643b..31c3869fe 100644 --- a/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala +++ b/main-settings/src/test/scala/sbt/BuildSettingsInstances.scala @@ -5,134 +5,136 @@ * Copyright 2008 - 2010, Mark Harrah * Licensed under Apache License 2.0 (see LICENSE) */ -/* -package sbt.test -import org.scalacheck.{ Test => _, _ }, Arbitrary.arbitrary, Gen._ +package sbt +package test import java.io.File -import sbt.io.IO -import sbt.{ Scope, ScopeAxis, Scoped, Select, This, Zero } -import sbt.{ - BuildRef, - LocalProject, - LocalRootProject, - ProjectRef, - Reference, - RootProject, - ThisBuild, - ThisProject -} -import sbt.ConfigKey -import sbt.librarymanagement.syntax._ -import sbt.{ InputKey, SettingKey, TaskKey } -import sbt.internal.util.{ AttributeKey, AttributeMap } +import hedgehog.* import scala.annotation.nowarn +import scala.reflect.ClassTag +import _root_.sbt.io.IO +import _root_.sbt.Scoped.ScopingSetting +import _root_.sbt.librarymanagement.syntax.* +import _root_.sbt.internal.util.{ AttributeKey, AttributeMap } -object BuildSettingsInstances { - val genFile: Gen[File] = Gen.oneOf(new File("."), new File("/tmp")) // for now.. +object BuildSettingsInstances: + type Key[A1] = ScopingSetting[?] & Scoped - implicit val arbBuildRef: Arbitrary[BuildRef] = Arbitrary(genFile map (f => BuildRef(IO toURI f))) - - implicit val arbProjectRef: Arbitrary[ProjectRef] = - Arbitrary(for (f <- genFile; id <- Gen.identifier) yield ProjectRef(f, id)) - - implicit val arbLocalProject: Arbitrary[LocalProject] = - Arbitrary(arbitrary[String] map LocalProject) - - implicit val arbRootProject: Arbitrary[RootProject] = Arbitrary(genFile map (RootProject(_))) - - implicit val arbReference: Arbitrary[Reference] = Arbitrary { - Gen.frequency( - 96 -> arbitrary[BuildRef], - 10271 -> ThisBuild, - 325 -> LocalRootProject, - 2283 -> arbitrary[ProjectRef], - 299 -> ThisProject, - 436 -> arbitrary[LocalProject], - 1133 -> arbitrary[RootProject], + given Gen[Reference] = + val genFile: Gen[File] = + Gen.choice1(Gen.constant(new File(".")), Gen.constant(new File("/tmp"))) + given genBuildRef: Gen[BuildRef] = genFile.map: f => + BuildRef(IO.toURI(f)) + given genProjectRef: Gen[ProjectRef] = + for + f <- genFile + id <- identifier + yield ProjectRef(f, id) + given genLocalProject: Gen[LocalProject] = + identifier.map(LocalProject.apply) + given genRootProject: Gen[RootProject] = + genFile.map(RootProject.apply) + Gen.frequency1( + 96 -> genBuildRef.map(x => x: Reference), + 10271 -> Gen.constant(ThisBuild), + 325 -> Gen.constant(LocalRootProject), + 2283 -> genProjectRef.map(x => x: Reference), + 299 -> Gen.constant(ThisProject), + 436 -> genLocalProject.map(x => x: Reference), + 1133 -> genRootProject.map(x => x: Reference), ) - } @nowarn - implicit def arbConfigKey: Arbitrary[ConfigKey] = Arbitrary { - Gen.frequency( - 2 -> const[ConfigKey](Compile), - 2 -> const[ConfigKey](Test), - 1 -> const[ConfigKey](Runtime), - 1 -> const[ConfigKey](IntegrationTest), - 1 -> const[ConfigKey](Provided), - ) - } - - implicit def arbAttrKey[A: Manifest]: Arbitrary[AttributeKey[_]] = - Arbitrary(Gen.identifier map (AttributeKey[A](_))) - - implicit val arbAttributeMap: Arbitrary[AttributeMap] = Arbitrary { - Gen.frequency( - 20 -> AttributeMap.empty, - 1 -> { - for (name <- Gen.identifier; isModule <- arbitrary[Boolean]) - yield AttributeMap.empty - .put(AttributeKey[String]("name"), name) - .put(AttributeKey[Boolean]("isModule"), isModule) - } - ) - } - - implicit def arbScopeAxis[A: Arbitrary]: Arbitrary[ScopeAxis[A]] = - Arbitrary(Gen.oneOf[ScopeAxis[A]](This, Zero, arbitrary[A] map (Select(_)))) - - implicit def arbScope: Arbitrary[Scope] = Arbitrary( - for { - r <- arbitrary[ScopeAxis[Reference]] - c <- arbitrary[ScopeAxis[ConfigKey]] - t <- arbitrary[ScopeAxis[AttributeKey[_]]] - e <- arbitrary[ScopeAxis[AttributeMap]] - } yield Scope(r, c, t, e) + given Gen[ConfigKey] = Gen.frequency1( + 2 -> Gen.constant[ConfigKey](Compile), + 2 -> Gen.constant[ConfigKey](Test), + 1 -> Gen.constant[ConfigKey](Runtime), + 1 -> Gen.constant[ConfigKey](IntegrationTest), + 1 -> Gen.constant[ConfigKey](Provided), ) - type Key = K forSome { type K <: Scoped.ScopingSetting[K] with Scoped } + given genSettingKey[A1: ClassTag]: Gen[SettingKey[A1]] = + withScope(WithoutScope.genSettingKey) + given genTaskKey[A1: ClassTag]: Gen[TaskKey[A1]] = + withScope(WithoutScope.genTaskKey) + given genInputKey[A1: ClassTag]: Gen[InputKey[A1]] = + withScope(WithoutScope.genInputKey) + given genScopeAxis[A1: Gen]: Gen[ScopeAxis[A1]] = + Gen.choice1[ScopeAxis[A1]]( + Gen.constant(This), + Gen.constant(Zero), + summon[Gen[A1]].map(Select(_)) + ) - final case class Label(value: String) - val genLabel: Gen[Label] = Gen.identifier map Label - implicit def arbLabel: Arbitrary[Label] = Arbitrary(genLabel) + given genKey[A1: ClassTag]: Gen[Key[A1]] = + def convert[A2](g: Gen[A2]) = g.asInstanceOf[Gen[Key[A1]]] + Gen.frequency1( + 15431 -> convert(genInputKey), + 19645 -> convert(genSettingKey), + 22867 -> convert(genTaskKey), + ) - def genInputKey[A: Manifest]: Gen[InputKey[A]] = genLabel map (x => InputKey[A](x.value)) - def genSettingKey[A: Manifest]: Gen[SettingKey[A]] = genLabel map (x => SettingKey[A](x.value)) - def genTaskKey[A: Manifest]: Gen[TaskKey[A]] = genLabel map (x => TaskKey[A](x.value)) + given genAttrKey: Gen[AttributeKey[?]] = + identifier.map(AttributeKey[Unit](_)) - @nowarn - def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Arbitrary[K] = Arbitrary { - Gen.frequency( + given genAttributeMap: Gen[AttributeMap] = Gen.frequency1( + 20 -> Gen.constant(AttributeMap.empty), + 1 -> + (for + name <- identifier + isModule <- Gen.boolean + yield AttributeMap.empty + .put(AttributeKey[String]("name"), name) + .put(AttributeKey[Boolean]("isModule"), isModule)) + ) + + given Gen[Scope] = + for + r <- summon[Gen[ScopeAxis[Reference]]] + c <- summon[Gen[ScopeAxis[ConfigKey]]] + t <- summon[Gen[ScopeAxis[AttributeKey[?]]]] + e <- summon[Gen[ScopeAxis[AttributeMap]]] + yield Scope(r, c, t, e) + + def withScope[K <: Scoped.ScopingSetting[K]](keyGen: Gen[K]): Gen[K] = + Gen.frequency1( 5 -> keyGen, - 1 -> (for (key <- keyGen; scope <- arbitrary[Scope]) yield key in scope) + 1 -> (for + key <- keyGen + scope <- summon[Gen[Scope]] + yield key.rescope(scope)), ) - } - implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = withScope(genInputKey[A]) - implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = withScope(genSettingKey[A]) - implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = withScope(genTaskKey[A]) + case class Label(value: String) + object Label: + given genLabel: Gen[Label] = identifier.map(Label.apply) + end Label - implicit def arbKey[A: Manifest](implicit - arbInputKey: Arbitrary[InputKey[A]], - arbSettingKey: Arbitrary[SettingKey[A]], - arbTaskKey: Arbitrary[TaskKey[A]], - ): Arbitrary[Key] = Arbitrary { - def convert[T](g: Gen[T]) = g.asInstanceOf[Gen[Key]] - Gen.frequency( - 15431 -> convert(arbitrary[InputKey[A]]), - 19645 -> convert(arbitrary[SettingKey[A]]), - 22867 -> convert(arbitrary[TaskKey[A]]), + object WithoutScope: + def genSettingKey[A1: ClassTag]: Gen[SettingKey[A1]] = + Label.genLabel.map: label => + SettingKey[A1](label.value) + def genTaskKey[A1: ClassTag]: Gen[TaskKey[A1]] = + Label.genLabel.map: label => + TaskKey[A1](label.value) + def genInputKey[A1: ClassTag]: Gen[InputKey[A1]] = + Label.genLabel.map: label => + InputKey[A1](label.value) + end WithoutScope + + def identifier: Gen[String] = for + first <- Gen.char('a', 'z') + length <- Gen.int(Range.linear(0, 20)) + rest <- Gen.list( + Gen.frequency1( + 8 -> Gen.char('a', 'z'), + 8 -> Gen.char('A', 'Z'), + 5 -> Gen.char('0', '9'), + 1 -> Gen.constant('_') + ), + Range.singleton(length) ) - } + yield (first :: rest).mkString - object WithoutScope { - implicit def arbInputKey[A: Manifest]: Arbitrary[InputKey[A]] = Arbitrary(genInputKey[A]) - implicit def arbSettingKey[A: Manifest]: Arbitrary[SettingKey[A]] = Arbitrary(genSettingKey[A]) - implicit def arbTaskKey[A: Manifest]: Arbitrary[TaskKey[A]] = Arbitrary(genTaskKey[A]) - } - - implicit def arbScoped[A: Manifest]: Arbitrary[Scoped] = Arbitrary(arbitrary[Key]) -} - */ +end BuildSettingsInstances diff --git a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala index f6816ca1d..9b71f48ce 100644 --- a/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala +++ b/main-settings/src/test/scala/sbt/SlashSyntaxSpec.scala @@ -5,109 +5,391 @@ * Copyright 2008 - 2010, Mark Harrah * Licensed under Apache License 2.0 (see LICENSE) */ -/* -package sbt.test -import org.scalacheck.{ Test => _, _ }, Prop._ +package sbt +package test -import sbt.SlashSyntax -import sbt.{ Scope, ScopeAxis, Scoped }, Scope.{ Global, ThisScope } -import sbt.Reference -import sbt.ConfigKey -import sbt.internal.util.AttributeKey +import hedgehog.* +import hedgehog.runner.* +import Scope.{ Global, ThisScope } +import SlashSyntax0.given +import BuildSettingsInstances.given +import _root_.sbt.internal.util.AttributeKey -import BuildSettingsInstances._ -import scala.annotation.nowarn +object SlashSyntaxSpec extends Properties: + override def tests: List[Test] = List( + property("Global / key", propGlobalKey), + property("Reference / key", propReferenceKey), + property("Reference / Config / key", propReferenceConfigKey), + property("Reference / task.key / key", propReferenceAttrKeyKey), + property("Reference / task / key", propReferenceTaskKey), + property("Reference / inputtask / key", propReferenceInputTaskKey), + property("Reference / Config / task.key / key", propReferenceConfigAttrKeyKey), + property("Reference / Config / task / key", propReferenceConfigTaskKey), + property("Reference / Config / inputtask / key", propReferenceConfigInputTaskKey), + property("Config / key", propConfigKey), + property("Config / task.key / key", propConfigAttrKeyKey), + property("Config / task / key", propConfigTaskKey), + property("Config / inputtask / key", propConfigInputTaskKey), + property("task.key / key", propAttrKeyKey), + property("task / key", propTaskKey), + property("inputtask / key", propInputTaskKey), + property("Scope / key", propScopeKey), + property("Reference? / key", propReferenceAxisKey), + property("Reference? / Config? / key", propReferenceAxisConfigAxisKey), + // property("Reference? / task.key? / key", propReferenceAxisAttrKeyAxisKey), + property("Reference? / Config? / task.key? / key", propReferenceAxisConfigAxisAttrKeyAxisKey), + ) -@nowarn -object SlashSyntaxSpec extends Properties("SlashSyntax") with SlashSyntax { - property("Global / key == key in Global") = { - forAll((k: Key) => expectValue(k in Global)(Global / k)) - } + def gen[A1: Gen]: Gen[A1] = summon[Gen[A1]] - property("Reference / key == key in Reference") = { - forAll((r: Reference, k: Key) => expectValue(k in r)(r / k)) - } - - property("Reference / Config / key == key in Reference in Config") = { - forAll((r: Reference, c: ConfigKey, k: Key) => expectValue(k in r in c)(r / c / k)) - } - - property("Reference / task.key / key == key in Reference in task") = { - forAll((r: Reference, t: Scoped, k: Key) => expectValue(k in (r, t))(r / t.key / k)) - } - - property("Reference / task / key ~= key in Reference in task") = { - import WithoutScope._ - forAll((r: Reference, t: Key, k: Key) => expectValue(k in (r, t))(r / t / k)) - } - - property("Reference / Config / task.key / key == key in Reference in Config in task") = { - forAll { (r: Reference, c: ConfigKey, t: Scoped, k: Key) => - expectValue(k in (r, c, t))(r / c / t.key / k) - } - } - - property("Reference / Config / task / key ~= key in Reference in Config in task") = { - import WithoutScope._ - forAll { (r: Reference, c: ConfigKey, t: Key, k: Key) => - expectValue(k in (r, c, t))(r / c / t / k) - } - } - - property("Config / key == key in Config") = { - forAll((c: ConfigKey, k: Key) => expectValue(k in c)(c / k)) - } - - property("Config / task.key / key == key in Config in task") = { - forAll((c: ConfigKey, t: Scoped, k: Key) => expectValue(k in c in t)(c / t.key / k)) - } - - property("Config / task / key ~= key in Config in task") = { - import WithoutScope._ - forAll((c: ConfigKey, t: Key, k: Key) => expectValue(k in c in t)(c / t / k)) - } - - property("task.key / key == key in task") = { - forAll((t: Scoped, k: Key) => expectValue(k in t)(t.key / k)) - } - - property("task / key ~= key in task") = { - import WithoutScope._ - forAll((t: Key, k: Key) => expectValue(k in t)(t / k)) - } - - property("Scope / key == key in Scope") = { - forAll((s: Scope, k: Key) => expectValue(k in s)(s / k)) - } - - property("Reference? / key == key in ThisScope.copy(..)") = { - forAll { (r: ScopeAxis[Reference], k: Key) => - expectValue(k in ThisScope.copy(project = r))(r / k) - } - } - - property("Reference? / ConfigKey? / key == key in ThisScope.copy(..)") = { - forAll((r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], k: Key) => - expectValue(k in ThisScope.copy(project = r, config = c))(r / c / k) + def propGlobalKey: Property = + for + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => Global / k + case k: TaskKey[?] => Global / k + case k: SettingKey[?] => Global / k + yield Result.assert( + actual.key == k.key && + // Only if the incoming scope is This/This/This, + // Global scoping is effective. + (if k.scope == ThisScope then actual.scope == Global + else true) ) - } -// property("Reference? / AttributeKey? / key == key in ThisScope.copy(..)") = { -// forAll((r: ScopeAxis[Reference], t: ScopeAxis[AttributeKey[_]], k: AnyKey) => -// expectValue(k in ThisScope.copy(project = r, task = t))(r / t / k)) -// } + def propReferenceKey: Property = + for + ref <- gen[Reference].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / k + case k: TaskKey[?] => ref / k + case k: SettingKey[?] => ref / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) + ) - property("Reference? / ConfigKey? / AttributeKey? / key == key in ThisScope.copy(..)") = { - forAll { - (r: ScopeAxis[Reference], c: ScopeAxis[ConfigKey], t: ScopeAxis[AttributeKey[_]], k: Key) => - expectValue(k in ThisScope.copy(project = r, config = c, task = t))(r / c / t / k) - } - } + def propReferenceConfigKey: Property = + for + ref <- gen[Reference].forAll + config <- gen[ConfigKey].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / config / k + case k: TaskKey[?] => ref / config / k + case k: SettingKey[?] => ref / config / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) + ) - def expectValue(expected: Scoped)(x: Scoped) = { - val equals = x.scope == expected.scope && x.key == expected.key - if (equals) proved else falsified :| s"Expected $expected but got $x" - } -} - */ + def propReferenceAttrKeyKey: Property = + for + ref <- gen[Reference].forAll + scoped <- genKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / scoped.key / k + case k: TaskKey[?] => ref / scoped.key / k + case k: SettingKey[?] => ref / scoped.key / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.task == This then actual.scope.task == Select(scoped.key) + else true) + ) + + def propReferenceTaskKey: Property = + for + ref <- gen[Reference].forAll + t <- genTaskKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / t / k + case k: TaskKey[?] => ref / t / k + case k: SettingKey[?] => ref / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propReferenceInputTaskKey: Property = + for + ref <- gen[Reference].forAll + t <- genInputKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / t / k + case k: TaskKey[?] => ref / t / k + case k: SettingKey[?] => ref / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propReferenceConfigAttrKeyKey: Property = + for + ref <- gen[Reference].forAll + config <- gen[ConfigKey].forAll + scoped <- genKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / config / scoped.key / k + case k: TaskKey[?] => ref / config / scoped.key / k + case k: SettingKey[?] => ref / config / scoped.key / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) && + (if k.scope.task == This then actual.scope.task == Select(scoped.key) + else true) + ) + + def propReferenceConfigTaskKey: Property = + for + ref <- gen[Reference].forAll + config <- gen[ConfigKey].forAll + t <- genTaskKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / config / t / k + case k: TaskKey[?] => ref / config / t / k + case k: SettingKey[?] => ref / config / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propReferenceConfigInputTaskKey: Property = + for + ref <- gen[Reference].forAll + config <- gen[ConfigKey].forAll + t <- genInputKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / config / t / k + case k: TaskKey[?] => ref / config / t / k + case k: SettingKey[?] => ref / config / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == Select(ref) + else true) && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propConfigKey: Property = + for + config <- gen[ConfigKey].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => config / k + case k: TaskKey[?] => config / k + case k: SettingKey[?] => config / k + yield Result.assert( + actual.key == k.key && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) + ) + + def propConfigAttrKeyKey: Property = + for + config <- gen[ConfigKey].forAll + scoped <- genKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => config / scoped.key / k + case k: TaskKey[?] => config / scoped.key / k + case k: SettingKey[?] => config / scoped.key / k + yield Result.assert( + actual.key == k.key && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) && + (if k.scope.task == This then actual.scope.task == Select(scoped.key) + else true) + ) + + def propConfigTaskKey: Property = + for + config <- gen[ConfigKey].forAll + t <- genTaskKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => config / t / k + case k: TaskKey[?] => config / t / k + case k: SettingKey[?] => config / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propConfigInputTaskKey: Property = + for + config <- gen[ConfigKey].forAll + t <- genInputKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => config / t / k + case k: TaskKey[?] => config / t / k + case k: SettingKey[?] => config / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.config == This then actual.scope.config == Select(config) + else true) && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propAttrKeyKey: Property = + for + scoped <- genKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => scoped.key / k + case k: TaskKey[?] => scoped.key / k + case k: SettingKey[?] => scoped.key / k + yield Result.assert( + actual.key == k.key && + (if k.scope.task == This then actual.scope.task == Select(scoped.key) + else true) + ) + + def propTaskKey: Property = + for + t <- genTaskKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => t / k + case k: TaskKey[?] => t / k + case k: SettingKey[?] => t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propInputTaskKey: Property = + for + t <- genInputKey[Unit].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => t / k + case k: TaskKey[?] => t / k + case k: SettingKey[?] => t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.task == This then actual.scope.task == Select(t.key) + else true) + ) + + def propScopeKey: Property = + for + scope <- gen[Scope].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => scope / k + case k: TaskKey[?] => scope / k + case k: SettingKey[?] => scope / k + yield Result.assert( + actual.key == k.key && + // Only if the incoming scope is This/This/This, + // Global scoping is effective. + (if k.scope == ThisScope then actual.scope == scope + else true) + ) + + def propReferenceAxisKey: Property = + for + ref <- gen[ScopeAxis[Reference]].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / k + case k: TaskKey[?] => ref / k + case k: SettingKey[?] => ref / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == ref + else true) + ) + + def propReferenceAxisConfigAxisKey: Property = + for + ref <- gen[ScopeAxis[Reference]].forAll + config <- gen[ScopeAxis[ConfigKey]].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / config / k + case k: TaskKey[?] => ref / config / k + case k: SettingKey[?] => ref / config / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == ref + else true) && + (if k.scope.config == This then actual.scope.config == config + else true) + ) + + /* + def propReferenceAxisAttrKeyAxisKey: Property = + for + ref <- gen[ScopeAxis[Reference]].forAll + t <- gen[ScopeAxis[AttributeKey[?]]].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / t / k + case k: TaskKey[?] => ref / t / k + case k: SettingKey[?] => ref / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == ref + else true) && + (if k.scope.task == This then actual.scope.task == t + else true) + ) + */ + + def propReferenceAxisConfigAxisAttrKeyAxisKey: Property = + for + ref <- gen[ScopeAxis[Reference]].forAll + config <- gen[ScopeAxis[ConfigKey]].forAll + t <- gen[ScopeAxis[AttributeKey[?]]].forAll + k <- genKey[Unit].forAll + actual = k match + case k: InputKey[?] => ref / config / t / k + case k: TaskKey[?] => ref / config / t / k + case k: SettingKey[?] => ref / config / t / k + yield Result.assert( + actual.key == k.key && + (if k.scope.project == This then actual.scope.project == ref + else true) && + (if k.scope.config == This then actual.scope.config == config + else true) && + (if k.scope.task == This then actual.scope.task == t + else true) + ) +end SlashSyntaxSpec From c838094672ad514aef384e652ff09d59d612961b Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Wed, 6 Nov 2024 11:44:52 +0100 Subject: [PATCH 2/7] Optimize Previous --- .../src/main/scala/sbt/Previous.scala | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/main-settings/src/main/scala/sbt/Previous.scala b/main-settings/src/main/scala/sbt/Previous.scala index 43256f6af..cfe289c8c 100644 --- a/main-settings/src/main/scala/sbt/Previous.scala +++ b/main-settings/src/main/scala/sbt/Previous.scala @@ -154,10 +154,9 @@ object Previous { /** Public as a macro implementation detail. Do not call directly. */ def runtime[T](skey: TaskKey[T])(implicit format: JsonFormat[T]): Initialize[Task[Option[T]]] = { - val inputs = (Global / cache) - .zip(Def.validated(skey, selfRefOk = true)) - .zip(Global / references) - inputs { case ((prevTask, resolved), refs) => + type Inputs = (Task[Previous], ScopedKey[Task[T]], References) + val inputs = (Global / cache, Def.validated(skey, selfRefOk = true), Global / references) + Def.app[Inputs, Task[Option[T]]](inputs) { case (prevTask, resolved, refs) => val key = Key(resolved, resolved) refs.recordReference(key, format) // always evaluated on project load prevTask.map(_.get(key)) // evaluated if this task is evaluated @@ -168,14 +167,17 @@ object Previous { def runtimeInEnclosingTask[T](skey: TaskKey[T])(implicit format: JsonFormat[T] ): Initialize[Task[Option[T]]] = { - val inputs = (Global / cache) - .zip(Def.validated(skey, selfRefOk = true)) - .zip(Global / references) - .zip(Def.resolvedScoped) - inputs { case (((prevTask, resolved), refs), inTask) => + type Inputs = (Task[Previous], ScopedKey[Task[T]], References, ScopedKey[?]) + val inputs = ( + Global / cache, + Def.validated(skey, selfRefOk = true), + Global / references, + Def.resolvedScoped + ) + Def.app[Inputs, Task[Option[T]]](inputs) { case (prevTask, resolved, refs, inTask) => val key = Key(resolved, inTask.asInstanceOf[ScopedKey[Task[Any]]]) refs.recordReference(key, format) // always evaluated on project load - prevTask.map(_.get(key)) // evaluated if this task is evaluated + prevTask.map(_.get(key)) } } } From 07ac8625e57a6a97dc0aeae75b8e91ca8e2e43da Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 12 Nov 2024 11:02:02 +0100 Subject: [PATCH 3/7] Reduce settings created by dependency-tree plugins --- .../main/scala/sbt/plugins/DependencyTreePlugin.scala | 10 +++++----- .../scala/sbt/plugins/MiniDependencyTreePlugin.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dependency-tree/src/main/scala/sbt/plugins/DependencyTreePlugin.scala b/dependency-tree/src/main/scala/sbt/plugins/DependencyTreePlugin.scala index a1e93a3aa..9a6866703 100644 --- a/dependency-tree/src/main/scala/sbt/plugins/DependencyTreePlugin.scala +++ b/dependency-tree/src/main/scala/sbt/plugins/DependencyTreePlugin.scala @@ -20,11 +20,11 @@ object DependencyTreePlugin extends AutoPlugin { val configurations = Vector(Compile, Test, IntegrationTest, Runtime, Provided, Optional) // MiniDependencyTreePlugin provides baseBasicReportingSettings for Compile and Test - override def projectSettings: Seq[Def.Setting[?]] = - ((configurations diff Vector(Compile, Test)) flatMap { config => + override lazy val projectSettings: Seq[Def.Setting[?]] = + configurations.diff(Vector(Compile, Test)).flatMap { config => inConfig(config)(DependencyTreeSettings.baseBasicReportingSettings) - }) ++ - (configurations flatMap { config => + } ++ + configurations.flatMap { config => inConfig(config)(DependencyTreeSettings.baseFullReportingSettings) - }) + } } diff --git a/main/src/main/scala/sbt/plugins/MiniDependencyTreePlugin.scala b/main/src/main/scala/sbt/plugins/MiniDependencyTreePlugin.scala index c042cec10..aba6da778 100644 --- a/main/src/main/scala/sbt/plugins/MiniDependencyTreePlugin.scala +++ b/main/src/main/scala/sbt/plugins/MiniDependencyTreePlugin.scala @@ -21,7 +21,7 @@ object MiniDependencyTreePlugin extends AutoPlugin { override def globalSettings: Seq[Def.Setting[?]] = Seq( dependencyTreeIncludeScalaLibrary := false ) - override def projectSettings: Seq[Def.Setting[?]] = + override lazy val projectSettings: Seq[Def.Setting[?]] = DependencyTreeSettings.coreSettings ++ inConfig(Compile)(DependencyTreeSettings.baseBasicReportingSettings) ++ inConfig(Test)(DependencyTreeSettings.baseBasicReportingSettings) From 8406fc86a8d0c6ba03ccd5f3c87335750b367ca3 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 12 Nov 2024 11:03:13 +0100 Subject: [PATCH 4/7] Reduce settings created by ProjectMatrix --- main/src/main/scala/sbt/ProjectMatrix.scala | 79 ++++++++++----------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/main/src/main/scala/sbt/ProjectMatrix.scala b/main/src/main/scala/sbt/ProjectMatrix.scala index 058294508..9407cc43d 100644 --- a/main/src/main/scala/sbt/ProjectMatrix.scala +++ b/main/src/main/scala/sbt/ProjectMatrix.scala @@ -274,22 +274,8 @@ object ProjectMatrix { private def resolveMappings: ListMap[ProjectRow, Project] = { val projectIds = resolveProjectIds - def dirSuffix(axes: Seq[VirtualAxis]): String = - axes.map(_.directorySuffix).filter(_.nonEmpty).mkString("-") val projects = rows.map { r => - import VirtualAxis.* - val axes = r.axisValues.sortBy(_.suffixOrder) - val scalaDirSuffix = dirSuffix(axes) - val nonScalaDirSuffix = dirSuffix(axes.filterNot(_.isInstanceOf[ScalaVersionAxis])) - val nonScalaNorPlatformDirSuffix = - dirSuffix(axes.filterNot(_.isInstanceOf[ScalaVersionAxis | PlatformAxis])) - val platform = axes - .collect { case pa: VirtualAxis.PlatformAxis => - pa - } - .headOption - .getOrElse(sys.error(s"platform axis is missing in $axes")) val childId = projectIds(r) val deps = dependencies.map { resolveMatrixDependency(_, r) } ++ nonMatrixDependencies val aggs = aggregate.map { case ref: LocalProjectMatrix => @@ -303,32 +289,7 @@ object ProjectMatrix { .aggregate(aggs*) .setPlugins(plugins) .configs(configurations*) - .settings( - name := self.id - ) - .settings( - r.scalaVersionOpt match { - case Some(sv) => - List(scalaVersion := sv) - case _ => - List(autoScalaLibrary := false, crossPaths := false) - } - ) - .settings( - outputPath := { - val o = outputPath.value - if nonScalaNorPlatformDirSuffix.nonEmpty then s"$o/$nonScalaNorPlatformDirSuffix" - else o - }, - sourceDirectory := base.getAbsoluteFile / "src", - unmanagedBase := base.getAbsoluteFile / "lib", - ProjectExtra.inConfig(Compile)(makeSources(nonScalaDirSuffix, scalaDirSuffix)), - ProjectExtra.inConfig(Test)(makeSources(nonScalaDirSuffix, scalaDirSuffix)), - projectDependencies := projectDependenciesTask.value, - virtualAxes := axes, - projectMatrixBaseDirectory := base, - ) - .settings(self.settings) + .settings(baseSettings ++ rowSettings(r) ++ self.settings) .configure(transforms*) r -> r.process(p) @@ -336,8 +297,10 @@ object ProjectMatrix { ListMap(projects*) } + override lazy val componentProjects: Seq[Project] = resolvedMappings.values.toList + // backport of https://github.com/sbt/sbt/pull/5767 - def projectDependenciesTask: Def.Initialize[Task[Seq[ModuleID]]] = + lazy val projectDependenciesTask: Def.Initialize[Task[Seq[ModuleID]]] = Def.task { val orig = projectDependencies.value val sbv = scalaBinaryVersion.value @@ -363,7 +326,39 @@ object ProjectMatrix { } } - override lazy val componentProjects: Seq[Project] = resolvedMappings.values.toList + private lazy val noScalaLibrary: Seq[Def.Setting[?]] = + Seq(autoScalaLibrary := false, crossPaths := false) + + private lazy val baseSettings: Seq[Def.Setting[?]] = Def.settings( + name := self.id, + sourceDirectory := base.getAbsoluteFile / "src", + unmanagedBase := base.getAbsoluteFile / "lib", + projectDependencies := projectDependenciesTask.value, + projectMatrixBaseDirectory := base, + ) + + private def rowSettings(r: ProjectRow): Seq[Def.Setting[?]] = + import VirtualAxis.* + val axes = r.axisValues.sortBy(_.suffixOrder) + def dirSuffix(axes: Seq[VirtualAxis]): String = + axes.map(_.directorySuffix).filter(_.nonEmpty).mkString("-") + val scalaDirSuffix = dirSuffix(axes) + val nonScalaDirSuffix = dirSuffix(axes.filterNot(_.isInstanceOf[ScalaVersionAxis])) + val nonScalaNorPlatformDirSuffix = dirSuffix( + axes.filterNot(_.isInstanceOf[ScalaVersionAxis | PlatformAxis]) + ) + Def.settings( + r.scalaVersionOpt match { + case Some(sv) => Seq(scalaVersion := sv) + case _ => noScalaLibrary + }, + if nonScalaNorPlatformDirSuffix.nonEmpty then + Seq(outputPath ~= (o => s"$o/$nonScalaNorPlatformDirSuffix")) + else Seq.empty, + ProjectExtra.inConfig(Compile)(makeSources(nonScalaDirSuffix, scalaDirSuffix)), + ProjectExtra.inConfig(Test)(makeSources(nonScalaDirSuffix, scalaDirSuffix)), + virtualAxes := axes, + ) private def resolveMatrixAggregate( other: ProjectMatrix, From 6c226a2afdd7abe68a5ec6bca524923cf318aaa9 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 12 Nov 2024 11:51:01 +0100 Subject: [PATCH 5/7] Reduce settings created by Defaults --- main/src/main/scala/sbt/Defaults.scala | 327 +++++++++++-------------- 1 file changed, 149 insertions(+), 178 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 750f0de08..4ee29d9b7 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -31,7 +31,7 @@ import sbt.Project.{ // sbtRichTaskPromise } import sbt.ProjectExtra.* -import sbt.Scope.{ GlobalScope, ThisScope, fillTaskAxis } +import sbt.Scope.{ GlobalScope, ThisBuildScope, ThisScope, fillTaskAxis } import sbt.State.StateOpsImpl import sbt.coursierint._ import sbt.internal.CommandStrings.ExportStream @@ -156,9 +156,9 @@ object Defaults extends BuildCommon { private[sbt] def globalDefaults(ss: Seq[Setting[?]]): Seq[Setting[?]] = Def.defaultSettings(inScope(GlobalScope)(ss)) - def buildCore: Seq[Setting[?]] = thisBuildCore ++ globalCore - def thisBuildCore: Seq[Setting[?]] = - inScope(GlobalScope.copy(project = Select(ThisBuild)))( + lazy val buildCore: Seq[Setting[?]] = thisBuildCore ++ globalCore + private def thisBuildCore: Seq[Setting[?]] = + inScope(ThisBuildScope)( Seq( managedDirectory := baseDirectory.value / "lib_managed" ) @@ -652,8 +652,6 @@ object Defaults extends BuildCommon { } }, ) - // This exists for binary compatibility and probably never should have been public. - def addBaseSources: Seq[Def.Setting[Task[Seq[File]]]] = Nil lazy val outputConfigPaths: Seq[Setting[?]] = Seq( classDirectory := target.value / (prefix(configuration.value.name) + "classes"), backendOutput := { @@ -717,13 +715,21 @@ object Defaults extends BuildCommon { }, crossSbtVersions := Vector((pluginCrossBuild / sbtVersion).value), crossTarget := target.value, - scalaCompilerBridgeBinaryJar := Def.settingDyn { + scalaCompilerBridgeBinaryJar := { val sv = scalaVersion.value val managed = managedScalaInstance.value val hasSbtBridge = ScalaArtifacts.isScala3(sv) || ZincLmUtil.hasScala2SbtBridge(sv) - if (hasSbtBridge && managed) fetchBridgeBinaryJarTask(sv) - else Def.task[Option[File]](None) - }.value, + if hasSbtBridge && managed then + val jar = ZincLmUtil.fetchDefaultBridgeModule( + sv, + dependencyResolution.value, + updateConfiguration.value, + (update / unresolvedWarningConfiguration).value, + streams.value.log + ) + Some(jar) + else None + }, scalaCompilerBridgeSource := ZincLmUtil.getDefaultBridgeSourceModule(scalaVersion.value), auxiliaryClassFiles ++= { if (ScalaArtifacts.isScala3(scalaVersion.value)) List(TastyFiles.instance) @@ -736,7 +742,6 @@ object Defaults extends BuildCommon { classpathOptions := ClasspathOptionsUtil.noboot(scalaVersion.value), console / classpathOptions := ClasspathOptionsUtil.replNoboot(scalaVersion.value), ) - // must be a val: duplication detected by object identity private lazy val compileBaseGlobal: Seq[Setting[?]] = globalDefaults( Seq( auxiliaryClassFiles :== Nil, @@ -812,18 +817,6 @@ object Defaults extends BuildCommon { if (plugin) scalaBase / ("sbt-" + sbtv) else scalaBase } - private def fetchBridgeBinaryJarTask(scalaVersion: String): Initialize[Task[Option[File]]] = - Def.task { - val bridgeJar = ZincLmUtil.fetchDefaultBridgeModule( - scalaVersion, - dependencyResolution.value, - updateConfiguration.value, - (update / unresolvedWarningConfiguration).value, - streams.value.log - ) - Some(bridgeJar) - } - def compilersSetting = { compilers := { val st = state.value @@ -1010,8 +1003,10 @@ object Defaults extends BuildCommon { }, persistJarClasspath :== true, classpathEntryDefinesClassVF := { - (if (persistJarClasspath.value) classpathDefinesClassCache.value - else VirtualFileValueCache.definesClassCache(fileConverter.value)).get + val cache = + if persistJarClasspath.value then classpathDefinesClassCache.value + else VirtualFileValueCache.definesClassCache(fileConverter.value) + cache.get }, compileIncSetup := compileIncSetupTask.value, console := consoleTask.value, @@ -1083,16 +1078,9 @@ object Defaults extends BuildCommon { "1.3.0" ) def watchTransitiveSourcesTask: Initialize[Task[Seq[Source]]] = - watchTransitiveSourcesTaskImpl(watchSources) - - private def watchTransitiveSourcesTaskImpl( - key: TaskKey[Seq[Source]] - ): Initialize[Task[Seq[Source]]] = { import ScopeFilter.Make.* val selectDeps = ScopeFilter(inAggregates(ThisProject) || inDependencies(ThisProject)) - val allWatched = (key ?? Nil).all(selectDeps) - Def.task { allWatched.value.flatten } - } + watchSources.??(Nil).all(selectDeps).map(_.flatten) def transitiveUpdateTask: Initialize[Task[Seq[UpdateReport]]] = { import ScopeFilter.Make.* @@ -1140,15 +1128,13 @@ object Defaults extends BuildCommon { // use the same class loader as the Scala classes used by sbt Def.task { val allJars = scalaProvider.jars - val libraryJars = allJars - .filter { jar => - (jar.getName == "scala-library.jar") || (jar.getName.startsWith( - "scala3-library_3" - )) - } - (allJars.filter { jar => + val libraryJars = allJars.filter { jar => + jar.getName == "scala-library.jar" || jar.getName.startsWith("scala3-library_3") + } + val compilerJar = allJars.filter { jar => jar.getName == "scala-compiler.jar" || jar.getName.startsWith("scala3-compiler_3") - }) match + } + compilerJar match case Array(compilerJar) if libraryJars.nonEmpty => makeScalaInstance( sv, @@ -1318,75 +1304,76 @@ object Defaults extends BuildCommon { extraTestDigests :== Nil, ) ) - lazy val testTasks: Seq[Setting[?]] = - testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions( - testQuick - ) ++ testDefaults ++ Seq( - testLoader := ClassLoaders.testTask.value, - loadedTestFrameworks := { - val loader = testLoader.value - val log = streams.value.log - testFrameworks.value.flatMap(f => f.create(loader, log).map(x => (f, x))).toMap - }, - definedTests := detectTests.value, - definedTestNames := definedTests - .map(_.map(_.name).distinct) - .storeAs(definedTestNames) - .triggeredBy(compile) - .value, - definedTestDigests := IncrementalTest.definedTestDigestTask - .triggeredBy(compile) - .value, - testQuick / testFilter := IncrementalTest.filterTask.value, - extraTestDigests ++= IncrementalTest.extraTestDigestsTask.value, - executeTests := { - import sbt.TupleSyntax.* - ( - test / streams, - loadedTestFrameworks, - testLoader, - (test / testGrouping), - (test / testExecution), - (test / fullClasspath), - testForkedParallel, - (test / javaOptions), - (classLoaderLayeringStrategy), - thisProject, - fileConverter, - ).flatMapN { case (s, lt, tl, gp, ex, cp, fp, jo, clls, thisProj, c) => - allTestGroupsTask( - s, - lt, - tl, - gp, - ex, - cp, - fp, - jo, - clls, - projectId = s"${thisProj.id} / ", - c, - ) - } - }.value, - // ((streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel, javaOptions in test) flatMap allTestGroupsTask).value, - Test / testFull / testResultLogger :== TestResultLogger.SilentWhenNoTests, // https://github.com/sbt/sbt/issues/1185 - testFull := { - val trl = (Test / testFull / testResultLogger).value - val taskName = Project.showContextKey(state.value).show(resolvedScoped.value) - try trl.run(streams.value.log, executeTests.value, taskName) - finally close(testLoader.value) - }, - testOnly := { - try inputTests(testOnly).evaluated - finally close(testLoader.value) - }, - testQuick := { - try inputTests(testQuick).evaluated - finally close(testLoader.value) - }, - test := testQuick.evaluated, - ) + lazy val testTasks: Seq[Setting[?]] = Def.settings( + testTaskOptions(test), + testTaskOptions(testOnly), + testTaskOptions(testQuick), + testDefaults, + testLoader := ClassLoaders.testTask.value, + loadedTestFrameworks := { + val loader = testLoader.value + val log = streams.value.log + testFrameworks.value.flatMap(f => f.create(loader, log).map(x => (f, x))).toMap + }, + definedTests := detectTests.value, + definedTestNames := definedTests + .map(_.map(_.name).distinct) + .storeAs(definedTestNames) + .triggeredBy(compile) + .value, + definedTestDigests := IncrementalTest.definedTestDigestTask + .triggeredBy(compile) + .value, + testQuick / testFilter := IncrementalTest.filterTask.value, + extraTestDigests ++= IncrementalTest.extraTestDigestsTask.value, + executeTests := { + import sbt.TupleSyntax.* + ( + test / streams, + loadedTestFrameworks, + testLoader, + (test / testGrouping), + (test / testExecution), + (test / fullClasspath), + testForkedParallel, + (test / javaOptions), + (classLoaderLayeringStrategy), + thisProject, + fileConverter, + ).flatMapN { case (s, lt, tl, gp, ex, cp, fp, jo, clls, thisProj, c) => + allTestGroupsTask( + s, + lt, + tl, + gp, + ex, + cp, + fp, + jo, + clls, + projectId = s"${thisProj.id} / ", + c, + ) + } + }.value, + // ((streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel, javaOptions in test) flatMap allTestGroupsTask).value, + Test / testFull / testResultLogger :== TestResultLogger.SilentWhenNoTests, // https://github.com/sbt/sbt/issues/1185 + testFull := { + val trl = (Test / testFull / testResultLogger).value + val taskName = Project.showContextKey(state.value).show(resolvedScoped.value) + try trl.run(streams.value.log, executeTests.value, taskName) + finally close(testLoader.value) + }, + testOnly := { + try inputTests(testOnly).evaluated + finally close(testLoader.value) + }, + testQuick := { + try inputTests(testQuick).evaluated + finally close(testLoader.value) + }, + test := testQuick.evaluated, + ) private def close(sbtLoader: ClassLoader): Unit = sbtLoader match { case u: AutoCloseable => u.close() @@ -1398,13 +1385,11 @@ object Defaults extends BuildCommon { * A scope whose task axis is set to Zero. */ lazy val TaskZero: Scope = ThisScope.copy(task = Zero) - lazy val TaskGlobal: Scope = TaskZero /** * A scope whose configuration axis is set to Zero. */ lazy val ConfigZero: Scope = ThisScope.copy(config = Zero) - lazy val ConfigGlobal: Scope = ConfigZero def testTaskOptions(key: Scoped): Seq[Setting[?]] = inTask(key)( Seq( @@ -1468,7 +1453,7 @@ object Defaults extends BuildCommon { def singleTestGroup(key: Scoped): Initialize[Task[Seq[Tests.Group]]] = inTask(key, singleTestGroupDefault) - def singleTestGroupDefault: Initialize[Task[Seq[Tests.Group]]] = Def.task { + lazy val singleTestGroupDefault: Initialize[Task[Seq[Tests.Group]]] = Def.task { val tests = definedTests.value val fk = fork.value val opts = forkOptions.value @@ -1940,7 +1925,7 @@ object Defaults extends BuildCommon { converter.toVirtualFile(p.toPath()) } - def artifactSetting: Initialize[Artifact] = + lazy val artifactSetting: Initialize[Artifact] = Def.setting { val a = artifact.value val classifier = artifactClassifier.value @@ -1980,7 +1965,7 @@ object Defaults extends BuildCommon { ) ) - def packageTask: Initialize[Task[HashedVirtualFileRef]] = + lazy val packageTask: Initialize[Task[HashedVirtualFileRef]] = Def.cachedTask { val config = packageConfiguration.value val s = streams.value @@ -1996,7 +1981,7 @@ object Defaults extends BuildCommon { out } - def packageConfigurationTask: Initialize[Task[Pkg.Configuration]] = + lazy val packageConfigurationTask: Initialize[Task[Pkg.Configuration]] = Def.task { Pkg.Configuration( mappings.value, @@ -2809,8 +2794,8 @@ object Defaults extends BuildCommon { def noAggregation: Seq[Scoped] = Seq(run, runMain, bgRun, bgRunMain, console, consoleQuick, consoleProject) - lazy val disableAggregation = Defaults.globalDefaults(noAggregation map disableAggregate) - def disableAggregate(k: Scoped) = (k / aggregate) :== false + lazy val disableAggregation = + Defaults.globalDefaults(noAggregation.map(k => (k / aggregate) :== false)) // 1. runnerSettings is added unscoped via JvmPlugin. // 2. In addition it's added scoped to run task. @@ -2835,9 +2820,7 @@ object Defaults extends BuildCommon { "Create a separate subproject instead of using IntegrationTest and in addition avoid using itSettings", "1.9.0" ) - lazy val itSettings: Seq[Setting[?]] = inConfig(IntegrationTest) { - testSettings - } + lazy val itSettings: Seq[Setting[?]] = inConfig(IntegrationTest)(testSettings) lazy val defaultConfigs: Seq[Setting[?]] = inConfig(Compile)(compileSettings) ++ inConfig(Test)(testSettings) ++ inConfig(Runtime)(Classpaths.configSettings) @@ -2870,7 +2853,7 @@ object Defaults extends BuildCommon { ) ) - def dependencyResolutionTask: Def.Initialize[Task[DependencyResolution]] = + lazy val dependencyResolutionTask: Def.Initialize[Task[DependencyResolution]] = Def.task { CoursierDependencyResolution(csrConfiguration.value) } @@ -3133,16 +3116,14 @@ object Classpaths { // Both POMs and JARs are Maven-compatible in sbt 2.x, so ignore the workarounds packagedDefaultArtifacts.value } else { - val crossVersion = sbtCrossVersion.value + val sbtV = (pluginCrossBuild / sbtBinaryVersion).value + val scalaV = scalaBinaryVersion.value + val crossVersion = (name: String) => name + s"_${scalaV}_$sbtV" val legacyPomArtifact = (makePom / artifact).value val converter = fileConverter.value def addSuffix(a: Artifact): Artifact = a.withName(crossVersion(a.name)) - Map( - addSuffix(legacyPomArtifact) -> converter.toVirtualFile( - makeMavenPomOfSbtPlugin.value.toPath() - ) - ) ++ - pomConsistentArtifactsForLegacySbt.value ++ + Map(addSuffix(legacyPomArtifact) -> makeMavenPomOfSbtPlugin(converter, crossVersion)) ++ + pomConsistentArtifactsForLegacySbt(converter, crossVersion) ++ legacyPackagedArtifacts.value } } @@ -3154,52 +3135,46 @@ object Classpaths { else Map.empty[Artifact, HashedVirtualFileRef] } - private def pomConsistentArtifactsForLegacySbt - : Def.Initialize[Task[Map[Artifact, HashedVirtualFileRef]]] = - Def.task { - val crossVersion = sbtCrossVersion.value - val legacyPackages = packaged(defaultPackages).value - val converter = fileConverter.value - def copyArtifact( - artifact: Artifact, - fileRef: HashedVirtualFileRef - ): (Artifact, HashedVirtualFileRef) = { - val nameWithSuffix = crossVersion(artifact.name) - val file = converter.toPath(fileRef).toFile - val targetFile = - new File(file.getParentFile, file.name.replace(artifact.name, nameWithSuffix)) - IO.copyFile(file, targetFile) - artifact.withName(nameWithSuffix) -> converter.toVirtualFile(targetFile.toPath) - } - legacyPackages.map { case (artifact, file) => - copyArtifact(artifact, file); - } + private inline def pomConsistentArtifactsForLegacySbt( + converter: FileConverter, + crossVersion: String => String + ): Map[Artifact, HashedVirtualFileRef] = + val legacyPackages = packaged(defaultPackages).value + def copyArtifact( + artifact: Artifact, + fileRef: HashedVirtualFileRef + ): (Artifact, HashedVirtualFileRef) = { + val nameWithSuffix = crossVersion(artifact.name) + val file = converter.toPath(fileRef).toFile + val targetFile = + new File(file.getParentFile, file.name.replace(artifact.name, nameWithSuffix)) + IO.copyFile(file, targetFile) + artifact.withName(nameWithSuffix) -> converter.toVirtualFile(targetFile.toPath) + } + legacyPackages.map { case (artifact, file) => + copyArtifact(artifact, file); } - - private def sbtCrossVersion: Def.Initialize[String => String] = Def.setting { - val sbtV = (pluginCrossBuild / sbtBinaryVersion).value - val scalaV = scalaBinaryVersion.value - name => name + s"_${scalaV}_$sbtV" - } /** * Generates a POM file that Maven can resolve. * It appends the sbt cross version into all artifactIds of sbt plugins * (the main one and the dependencies). */ - private def makeMavenPomOfSbtPlugin: Def.Initialize[Task[File]] = Def.task { + private inline def makeMavenPomOfSbtPlugin( + converter: FileConverter, + crossVersion: String => String + ): HashedVirtualFileRef = val config = makePomConfiguration.value - val nameWithCross = sbtCrossVersion.value(artifact.value.name) + val nameWithCross = crossVersion(artifact.value.name) val version = Keys.version.value val pomFile = config.file.get.getParentFile / s"$nameWithCross-$version.pom" val publisher = Keys.publisher.value val ivySbt = Keys.ivySbt.value val module = new ivySbt.Module(moduleSettings.value, appendSbtCrossVersion = true) publisher.makePomFile(module, config.withFile(pomFile), streams.value.log) - pomFile - } + converter.toVirtualFile(pomFile.toPath) - val ivyPublishSettings: Seq[Setting[?]] = publishGlobalDefaults ++ Seq( + def ivyPublishSettings: Seq[Setting[?]] = publishGlobalDefaults ++ Seq( artifacts :== Nil, packagedArtifacts :== Map.empty, makePom := { @@ -3218,7 +3193,7 @@ object Classpaths { publishM2 := publishOrSkip(publishM2Configuration, publishM2 / skip).value ) - private def baseGlobalDefaults = + def baseGlobalDefaults = Defaults.globalDefaults( Seq( conflictWarning :== ConflictWarning.default("global"), @@ -3290,7 +3265,7 @@ object Classpaths { ) ) - val ivyBaseSettings: Seq[Setting[?]] = baseGlobalDefaults ++ sbtClassifiersTasks ++ Seq( + def ivyBaseSettings: Seq[Setting[?]] = baseGlobalDefaults ++ sbtClassifiersTasks ++ Seq( conflictWarning := conflictWarning.value.copy(label = Reference.display(thisProjectRef.value)), unmanagedBase := baseDirectory.value / "lib", normalizedName := Project.normalizeModuleID(name.value), @@ -3593,8 +3568,8 @@ object Classpaths { update / unresolvedWarningConfiguration := UnresolvedWarningConfiguration( dependencyPositions.value ), - updateFull := (updateTask.tag(Tags.Update, Tags.Network)).value, - update := (updateWithoutDetails("update").tag(Tags.Update, Tags.Network)).value, + updateFull := updateTask.value, + update := updateWithoutDetails("update").value, update := { val report = update.value val log = streams.value.log @@ -3605,7 +3580,7 @@ object Classpaths { evicted / evictionWarningOptions := EvictionWarningOptions.full, evicted := { import ShowLines._ - val report = (updateTask.tag(Tags.Update, Tags.Network)).value + val report = updateTask.value val log = streams.value.log val ew = EvictionWarning(ivyModule.value, (evicted / evictionWarningOptions).value, report) @@ -3630,7 +3605,7 @@ object Classpaths { }, dependencyResolution := dependencyResolutionTask.value, csrConfiguration := LMCoursier.updateClassifierConfigurationTask.value, - TaskGlobal / updateClassifiers := LibraryManagement.updateClassifiersTask.value, + TaskZero / updateClassifiers := LibraryManagement.updateClassifiersTask.value, ) ) ++ Seq( csrProject := CoursierInputsTasks.coursierProjectTask.value, @@ -3645,7 +3620,7 @@ object Classpaths { IvyXml.generateIvyXmlSettings() ++ LMCoursier.publicationsSetting(Seq(Compile, Test).map(c => c -> CConfiguration(c.name))) - val jvmBaseSettings: Seq[Setting[?]] = Seq( + def jvmBaseSettings: Seq[Setting[?]] = Seq( libraryDependencies ++= autoLibraryDependency( autoScalaLibrary.value && scalaHome.value.isEmpty && managedScalaInstance.value, sbtPlugin.value, @@ -3761,7 +3736,7 @@ object Classpaths { ) else projectID.value } - private[sbt] def ivySbt0: Initialize[Task[IvySbt]] = + private[sbt] lazy val ivySbt0: Initialize[Task[IvySbt]] = Def.task { Credentials.register(credentials.value, streams.value.log) new IvySbt(ivyConfiguration.value) @@ -3834,7 +3809,7 @@ object Classpaths { }, dependencyResolution := dependencyResolutionTask.value, csrConfiguration := LMCoursier.updateSbtClassifierConfigurationTask.value, - (TaskGlobal / updateSbtClassifiers) := (Def + (TaskZero / updateSbtClassifiers) := (Def .task { val lm = dependencyResolution.value val s = streams.value @@ -4010,9 +3985,10 @@ object Classpaths { } } - def updateTask: Initialize[Task[UpdateReport]] = updateTask0("updateFull", true, true) + lazy val updateTask: Initialize[Task[UpdateReport]] = + updateTask0("updateFull", true, true).tag(Tags.Update, Tags.Network) def updateWithoutDetails(label: String): Initialize[Task[UpdateReport]] = - updateTask0(label, false, false) + updateTask0(label, false, false).tag(Tags.Update, Tags.Network) /** * cacheLabel - label to identify an update cache @@ -4419,7 +4395,7 @@ object Classpaths { def internalDependencyJarsTask: Initialize[Task[Classpath]] = ClasspathImpl.internalDependencyJarsTask - def mkIvyConfiguration: Initialize[Task[InlineIvyConfiguration]] = + lazy val mkIvyConfiguration: Initialize[Task[InlineIvyConfiguration]] = Def.task { val (rs, other) = (fullResolvers.value.toVector, otherResolvers.value.toVector) val s = streams.value @@ -4508,12 +4484,7 @@ object Classpaths { ClasspathImpl.getClasspath(key, dep, conf, data) def defaultConfigurationTask(p: ResolvedReference, data: Settings[Scope]): Configuration = - flatten( - (p / defaultConfiguration) - .get(data) - ).getOrElse(Configurations.Default) - - def flatten[T](o: Option[Option[T]]): Option[T] = o flatMap idFun + (p / defaultConfiguration).get(data).flatten.getOrElse(Configurations.Default) val sbtIvySnapshots: URLRepository = Resolver.sbtIvyRepo("snapshots") val typesafeReleases: URLRepository = From f4f185a1c1fdb3d0159e2c701d88a2033c2fce29 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Mon, 11 Nov 2024 13:49:14 +0100 Subject: [PATCH 6/7] Settings as a Map[ScopedKey[x], x] Settings0 used to be a Map[Scope, AttributeMap], and is now a Map[ScopedKey[x], x]. This is better because we don't need to decompose all ScopedKey[x] into a Scope and an AttributeKey[x], for recomposing it back later, which duplicates all ScopedKey[x]. It reduces the number of long-living ScopedKey[x] by 8%, and the total number of instances by 1.4%. Also it improves the performance of Settings0, which was responsible of 2.95% of the total CPU time, and is now responsible of 0.41%. --- .../sbt/internal/EvaluateConfigurations.scala | 36 +--- main-settings/src/main/scala/sbt/Def.scala | 3 +- .../src/main/scala/sbt/Project.scala | 7 +- .../src/main/scala/sbt/Structure.scala | 7 +- .../src/main/scala/sbt/std/Instances.scala | 2 +- main/src/main/scala/sbt/Defaults.scala | 25 +-- main/src/main/scala/sbt/EvaluateTask.scala | 4 +- main/src/main/scala/sbt/Extracted.scala | 25 ++- main/src/main/scala/sbt/ProjectExtra.scala | 64 +++--- main/src/main/scala/sbt/ScopedKeyData.scala | 10 +- main/src/main/scala/sbt/SessionVar.scala | 12 +- main/src/main/scala/sbt/internal/Act.scala | 40 ++-- .../main/scala/sbt/internal/Aggregation.scala | 6 +- .../scala/sbt/internal/BuildStructure.scala | 16 +- .../main/scala/sbt/internal/BuildUtil.scala | 6 +- .../scala/sbt/internal/ClasspathImpl.scala | 22 +-- main/src/main/scala/sbt/internal/Clean.scala | 6 +- .../scala/sbt/internal/GlobalPlugin.scala | 2 +- .../src/main/scala/sbt/internal/Inspect.scala | 3 +- .../main/scala/sbt/internal/LintUnused.scala | 26 +-- main/src/main/scala/sbt/internal/Load.scala | 16 +- .../main/scala/sbt/internal/LogManager.scala | 30 +-- .../scala/sbt/internal/ProjectQuery.scala | 3 +- .../sbt/internal/SettingCompletions.scala | 16 +- .../scala/sbt/internal/SettingGraph.scala | 11 +- .../WatchTransitiveDependencies.scala | 47 ++--- .../sbt/internal/server/SettingQuery.scala | 8 +- main/src/test/scala/PluginCommandTest.scala | 3 +- .../test/scala/sbt/internal/TestBuild.scala | 28 ++- sbt-app/src/main/scala/sbt/Import.scala | 6 +- .../main/scala/sbt/internal/util/INode.scala | 15 +- .../scala/sbt/internal/util/Settings.scala | 186 +++++++++--------- .../src/test/scala-2/SettingsTest.scala | 4 +- .../src/test/scala/SettingsExample.scala | 5 +- 34 files changed, 312 insertions(+), 388 deletions(-) diff --git a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 4aa859018..96a4108fa 100644 --- a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -9,19 +9,12 @@ package sbt package internal -import sbt.internal.util.{ - AttributeEntry, - AttributeKey, - LineRange, - MessageOnlyException, - RangePosition, - Settings -} +import sbt.internal.util.{ AttributeKey, LineRange, MessageOnlyException, RangePosition } import java.io.File import java.nio.file.Path import sbt.internal.util.complete.DefaultParsers.validID -import Def.{ ScopedKey, Setting } +import Def.{ ScopedKey, Setting, Settings } import Scope.GlobalScope import sbt.SlashSyntax0.given import sbt.internal.parser.SbtParser @@ -351,16 +344,10 @@ object BuildUtilLite: end BuildUtilLite object Index { - def taskToKeyMap(data: Settings[Scope]): Map[Task[?], ScopedKey[Task[?]]] = { - - val pairs = data.scopes flatMap (scope => - data.data(scope).entries collect { case AttributeEntry(key, value: Task[_]) => - (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[?]]])) - } - ) - - pairs.toMap[Task[?], ScopedKey[Task[?]]] - } + def taskToKeyMap(data: Settings): Map[Task[?], ScopedKey[Task[?]]] = + data.data.collect { case (key, value: Task[?]) => + (value, key.asInstanceOf[ScopedKey[Task[?]]]) + } def allKeys(settings: Seq[Setting[?]]): Set[ScopedKey[?]] = { val result = new java.util.HashSet[ScopedKey[?]] @@ -372,9 +359,6 @@ object Index { result.asScala.toSet } - def attributeKeys(settings: Settings[Scope]): Set[AttributeKey[?]] = - settings.data.values.flatMap(_.keys).toSet[AttributeKey[?]] - def stringToKeyMap(settings: Set[AttributeKey[?]]): Map[String, AttributeKey[?]] = stringToKeyMap0(settings)(_.label) @@ -396,19 +380,17 @@ object Index { private type TriggerMap = collection.mutable.HashMap[TaskId[?], Seq[TaskId[?]]] - def triggers(ss: Settings[Scope]): Triggers = { + def triggers(ss: Settings): Triggers = { val runBefore = new TriggerMap val triggeredBy = new TriggerMap - for - a <- ss.data.values - case AttributeEntry(_, base: Task[?]) <- a.entries - do + ss.values.collect { case base: Task[?] => def update(map: TriggerMap, key: AttributeKey[Seq[Task[?]]]): Unit = base.info.attributes.get(key).getOrElse(Seq.empty).foreach { task => map(task) = base +: map.getOrElse(task, Nil) } update(runBefore, Def.runBefore) update(triggeredBy, Def.triggeredBy) + } val onComplete = (GlobalScope / Def.onComplete).get(ss).getOrElse(() => ()) new Triggers(runBefore, triggeredBy, map => { onComplete(); map }) } diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index c4d48256b..235587b60 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -54,7 +54,8 @@ trait BuildSyntax: end BuildSyntax /** A concrete settings system that uses `sbt.Scope` for the scope type. */ -object Def extends BuildSyntax with Init[Scope] with InitializeImplicits: +object Def extends BuildSyntax with Init with InitializeImplicits: + type ScopeType = Scope type Classpath = Seq[Attributed[HashedVirtualFileRef]] def settings(ss: SettingsDefinition*): Seq[Setting[?]] = ss.flatMap(_.settings) diff --git a/main-settings/src/main/scala/sbt/Project.scala b/main-settings/src/main/scala/sbt/Project.scala index 73f455378..cfca795f4 100644 --- a/main-settings/src/main/scala/sbt/Project.scala +++ b/main-settings/src/main/scala/sbt/Project.scala @@ -334,7 +334,7 @@ object Project: ScopedKey(Scope.fillTaskAxis(scoped.scope, scoped.key), scoped.key) def mapScope(f: Scope => Scope): [a] => ScopedKey[a] => ScopedKey[a] = - [a] => (k: ScopedKey[a]) => ScopedKey(f(k.scope), k.key) + [a] => (k: ScopedKey[a]) => k.copy(scope = f(k.scope)) def transform(g: Scope => Scope, ss: Seq[Def.Setting[?]]): Seq[Def.Setting[?]] = // We use caching to avoid creating new Scope instances too many times @@ -361,7 +361,10 @@ object Project: Project.transform(Scope.replaceThis(scope), ss) private[sbt] def inScope[A](scope: Scope, i: Initialize[A]): Initialize[A] = - i.mapReferenced(Project.mapScope(Scope.replaceThis(scope))) + i.mapReferenced(replaceThis(scope)) + + private[sbt] def replaceThis(scope: Scope): Def.MapScoped = + mapScope(Scope.replaceThis(scope)) /** * Normalize a String so that it is suitable for use as a dependency management module identifier. diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 7986dc16d..608f4aecb 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -11,7 +11,7 @@ package sbt import scala.annotation.targetName import sbt.internal.util.Types.* -import sbt.internal.util.{ AttributeKey, KeyTag, Settings, SourcePosition } +import sbt.internal.util.{ AttributeKey, KeyTag, SourcePosition } import sbt.internal.util.TupleMapExtension.* import sbt.util.OptJsonWriter import sbt.ConcurrentRestrictions.Tag @@ -303,8 +303,7 @@ object Scoped: setting(scopedKey, app, source) /** From the given `Settings`, extract the value bound to this key. */ - final def get(settings: Settings[Scope]): Option[A1] = - settings.get(scopedKey.scope, scopedKey.key) + final def get(settings: Def.Settings): Option[A1] = settings.get(scopedKey) /** * Creates an [[Def.Initialize]] with value `scala.None` if there was no previous definition of this key, @@ -460,7 +459,7 @@ object Scoped: def toSettingKey: SettingKey[Task[A1]] = scopedSetting(scope, key) - def get(settings: Settings[Scope]): Option[Task[A1]] = settings.get(scope, key) + def get(settings: Def.Settings): Option[Task[A1]] = settings.get(scopedKey) /** * Creates an [[Def.Initialize]] with value `scala.None` if there was no previous definition of this key, diff --git a/main-settings/src/main/scala/sbt/std/Instances.scala b/main-settings/src/main/scala/sbt/std/Instances.scala index b6ca657d1..1bbd22023 100644 --- a/main-settings/src/main/scala/sbt/std/Instances.scala +++ b/main-settings/src/main/scala/sbt/std/Instances.scala @@ -47,7 +47,7 @@ end ParserInstance /** Composes the Task and Initialize Instances to provide an Instance for [A1] Initialize[Task[A1]]. */ object FullInstance: - type SS = sbt.internal.util.Settings[Scope] + type SS = Def.Settings val settingsData = TaskKey[SS]( "settings-data", "Provides access to the project data for the build.", diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 4ee29d9b7..cad5f61de 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -4177,14 +4177,15 @@ object Classpaths { try { val extracted = Project.extract(st) val sk = (projRef / Zero / Zero / libraryDependencies).scopedKey - val empty = extracted.structure.data.set(sk.scope, sk.key, Nil) + val empty = extracted.structure.data.set(sk, Nil) val settings = extracted.structure.settings filter { (s: Setting[?]) => (s.key.key == libraryDependencies.key) && (s.key.scope.project == Select(projRef)) } - Map(settings.asInstanceOf[Seq[Setting[Seq[ModuleID]]]].flatMap { s => - s.init.evaluate(empty) map { _ -> s.pos } - }*) + settings + .asInstanceOf[Seq[Setting[Seq[ModuleID]]]] + .flatMap(s => s.init.evaluate(empty).map(_ -> s.pos)) + .toMap } catch { case NonFatal(_) => Map() } @@ -4339,7 +4340,7 @@ object Classpaths { private[sbt] def depMap( projects: Seq[ProjectRef], - data: Settings[Scope], + data: Def.Settings, log: Logger ): Task[Map[ModuleRevisionId, ModuleDescriptor]] = val ivyModules = projects.flatMap { proj => @@ -4416,14 +4417,14 @@ object Classpaths { def interSort( projectRef: ProjectRef, conf: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies ): Seq[(ProjectRef, String)] = ClasspathImpl.interSort(projectRef, conf, data, deps) def interSortConfigurations( projectRef: ProjectRef, conf: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies ): Seq[(ProjectRef, ConfigRef)] = interSort(projectRef, conf, data, deps).map { case (projectRef, configName) => @@ -4467,23 +4468,23 @@ object Classpaths { sys.error("Configuration '" + conf + "' not defined in '" + in + "'") def allConfigs(conf: Configuration): Seq[Configuration] = ClasspathImpl.allConfigs(conf) - def getConfigurations(p: ResolvedReference, data: Settings[Scope]): Seq[Configuration] = + def getConfigurations(p: ResolvedReference, data: Def.Settings): Seq[Configuration] = ClasspathImpl.getConfigurations(p, data) def confOpt(configurations: Seq[Configuration], conf: String): Option[Configuration] = ClasspathImpl.confOpt(configurations, conf) - def unmanagedLibs(dep: ResolvedReference, conf: String, data: Settings[Scope]): Task[Classpath] = + def unmanagedLibs(dep: ResolvedReference, conf: String, data: Def.Settings): Task[Classpath] = ClasspathImpl.unmanagedLibs(dep, conf, data) def getClasspath( key: TaskKey[Classpath], dep: ResolvedReference, conf: String, - data: Settings[Scope] + data: Def.Settings ): Task[Classpath] = ClasspathImpl.getClasspath(key, dep, conf, data) - def defaultConfigurationTask(p: ResolvedReference, data: Settings[Scope]): Configuration = + def defaultConfigurationTask(p: ResolvedReference, data: Def.Settings): Configuration = (p / defaultConfiguration).get(data).flatten.getOrElse(Configurations.Default) val sbtIvySnapshots: URLRepository = Resolver.sbtIvyRepo("snapshots") @@ -4924,7 +4925,7 @@ trait BuildExtra extends BuildCommon with DefExtra { def initScoped[T](sk: ScopedKey[?], i: Initialize[T]): Initialize[T] = initScope(fillTaskAxis(sk.scope, sk.key), i) def initScope[T](s: Scope, i: Initialize[T]): Initialize[T] = - i.mapReferenced(Project.mapScope(Scope.replaceThis(s))) + Project.inScope(s, i) /** * Disables post-compilation hook for determining tests for tab-completion (such as for 'test-only'). diff --git a/main/src/main/scala/sbt/EvaluateTask.scala b/main/src/main/scala/sbt/EvaluateTask.scala index 326736b90..38804caec 100644 --- a/main/src/main/scala/sbt/EvaluateTask.scala +++ b/main/src/main/scala/sbt/EvaluateTask.scala @@ -449,8 +449,8 @@ object EvaluateTask { ref: ProjectRef ): Option[(Task[T], NodeView)] = { val thisScope = Load.projectScope(ref) - val resolvedScope = Scope.replaceThis(thisScope)(taskKey.scope) - for (t <- structure.data.get(resolvedScope, taskKey.key)) + val subScoped = Project.replaceThis(thisScope)(taskKey.scopedKey) + for (t <- structure.data.get(subScoped)) yield (t, nodeView(state, streams, taskKey :: Nil)) } def nodeView( diff --git a/main/src/main/scala/sbt/Extracted.scala b/main/src/main/scala/sbt/Extracted.scala index 4f3adc174..b560ab801 100644 --- a/main/src/main/scala/sbt/Extracted.scala +++ b/main/src/main/scala/sbt/Extracted.scala @@ -12,7 +12,6 @@ import sbt.internal.{ Load, BuildStructure, Act, Aggregation, SessionSettings } import Scope.GlobalScope import Def.{ ScopedKey, Setting } import sbt.internal.util.complete.Parser -import sbt.internal.util.AttributeKey import sbt.util.Show import std.Transform.DummyTaskMap import sbt.EvaluateTask.extractedTaskConfig @@ -34,21 +33,21 @@ final case class Extracted( * If the project axis is not explicitly specified, it is resolved to be the current project according to the extracted `session`. * Other axes are resolved to be `Zero` if they are not specified. */ - def get[T](key: SettingKey[T]): T = getOrError(inCurrent(key.scope), key.key) - def get[T](key: TaskKey[T]): Task[T] = getOrError(inCurrent(key.scope), key.key) + def get[T](key: SettingKey[T]): T = getOrError(inCurrent(key.scopedKey)) + def get[T](key: TaskKey[T]): Task[T] = getOrError(inCurrent(key.scopedKey)) /** * Gets the value assigned to `key` in the computed settings map wrapped in Some. If it does not exist, None is returned. * If the project axis is not explicitly specified, it is resolved to be the current project according to the extracted `session`. * Other axes are resolved to be `Zero` if they are not specified. */ - def getOpt[T](key: SettingKey[T]): Option[T] = structure.data.get(inCurrent(key.scope), key.key) + def getOpt[T](key: SettingKey[T]): Option[T] = structure.data.get(inCurrent(key.scopedKey)) def getOpt[T](key: TaskKey[T]): Option[Task[T]] = - structure.data.get(inCurrent(key.scope), key.key) + structure.data.get(inCurrent(key)) - private def inCurrent(scope: Scope): Scope = - if scope.project == This then scope.rescope(currentRef) - else scope + private def inCurrent[T](key: ScopedKey[T]): ScopedKey[T] = + if key.scope.project == This then key.copy(scope = key.scope.rescope(currentRef)) + else key /** * Runs the task specified by `key` and returns the transformed State and the resulting value of the task. @@ -63,7 +62,7 @@ final case class Extracted( val config = extractedTaskConfig(this, structure, state) val value: Option[(State, Result[T])] = EvaluateTask(structure, key.scopedKey, state, currentRef, config) - val (newS, result) = getOrError(rkey.scope, rkey.key, value) + val (newS, result) = getOrError(rkey.scopedKey, value) (newS, EvaluateTask.processResult2(result)) } @@ -116,15 +115,15 @@ final case class Extracted( private def resolve[K <: Scoped.ScopingSetting[K] & Scoped](key: K): K = Scope.resolveScope(GlobalScope, currentRef.build, rootProject)(key.scope) / key - private def getOrError[T](scope: Scope, key: AttributeKey[?], value: Option[T])(implicit + private def getOrError[T](key: ScopedKey[?], value: Option[T])(implicit display: Show[ScopedKey[?]] ): T = - value getOrElse sys.error(display.show(ScopedKey(scope, key)) + " is undefined.") + value.getOrElse(sys.error(display.show(key) + " is undefined.")) - private def getOrError[T](scope: Scope, key: AttributeKey[T])(implicit + private def getOrError[T](key: ScopedKey[T])(implicit display: Show[ScopedKey[?]] ): T = - getOrError(scope, key, structure.data.get(scope, key))(display) + getOrError(key, structure.data.get(key))(display) @deprecated( "This discards session settings. Migrate to appendWithSession or appendWithoutSession.", diff --git a/main/src/main/scala/sbt/ProjectExtra.scala b/main/src/main/scala/sbt/ProjectExtra.scala index f512f845a..d5d40ddb8 100755 --- a/main/src/main/scala/sbt/ProjectExtra.scala +++ b/main/src/main/scala/sbt/ProjectExtra.scala @@ -47,7 +47,7 @@ import sbt.internal.{ SettingGraph, SessionSettings } -import sbt.internal.util.{ AttributeKey, AttributeMap, Relation, Settings } +import sbt.internal.util.{ AttributeKey, AttributeMap, Relation } import sbt.internal.util.Types.const import sbt.internal.server.ServerHandler import sbt.librarymanagement.Configuration @@ -288,10 +288,10 @@ trait ProjectExtra extends Scoped.Syntax: def orIdentity[A](opt: Option[A => A]): A => A = opt.getOrElse(identity) - def getHook[A](key: SettingKey[A => A], data: Settings[Scope]): A => A = + def getHook[A](key: SettingKey[A => A], data: Def.Settings): A => A = orIdentity((Global / key).get(data)) - def getHooks(data: Settings[Scope]): (State => State, State => State) = + def getHooks(data: Def.Settings): (State => State, State => State) = (getHook(Keys.onLoad, data), getHook(Keys.onUnload, data)) def current(state: State): ProjectRef = session(state).current @@ -373,46 +373,34 @@ trait ProjectExtra extends Scoped.Syntax: private[sbt] def scopedKeyData( structure: BuildStructure, - scope: Scope, - key: AttributeKey[?] + key: ScopedKey[?] ): Option[ScopedKeyData[?]] = - structure.data.get(scope, key) map { v => - ScopedKeyData(ScopedKey(scope, key), v) - } + structure.data.getKeyValue(key).map((defining, value) => ScopedKeyData(key, defining, value)) - def details(structure: BuildStructure, actual: Boolean, scope: Scope, key: AttributeKey[?])( - using display: Show[ScopedKey[?]] + def details(structure: BuildStructure, actual: Boolean, key: ScopedKey[?])(using + display: Show[ScopedKey[?]] ): String = { - val scoped = ScopedKey(scope, key) + val data = scopedKeyData(structure, key).map(_.description).getOrElse("No entry for key.") + val description = key.key.description match + case Some(desc) => s"Description:\n\t$desc\n" + case None => "" - val data = scopedKeyData(structure, scope, key) map { _.description } getOrElse { - "No entry for key." - } - val description = key.description match { - case Some(desc) => "Description:\n\t" + desc + "\n"; case None => "" - } - - val definingScope = structure.data.definingScope(scope, key) - val providedBy = definingScope match { - case Some(sc) => "Provided by:\n\t" + Scope.display(sc, key.label) + "\n" - case None => "" - } - val definingScoped = definingScope match { - case Some(sc) => ScopedKey(sc, key) - case None => scoped - } + val (definingKey, providedBy) = structure.data.definingKey(key) match + case Some(k) => k -> s"Provided by:\n\t${Scope.display(k.scope, key.key.label)}\n" + case None => key -> "" val comp = Def.compiled(structure.settings, actual)(using structure.delegates, structure.scopeLocal, display ) - val definedAt = comp get definingScoped map { c => - Def.definedAtString(c.settings).capitalize - } getOrElse "" + val definedAt = comp + .get(definingKey) + .map(c => Def.definedAtString(c.settings).capitalize) + .getOrElse("") val cMap = Def.flattenLocals(comp) - val related = cMap.keys.filter(k => k.key == key && k.scope != scope) + val related = cMap.keys.filter(k => k.key == key.key && k.scope != key.scope) def derivedDependencies(c: ScopedKey[?]): List[ScopedKey[?]] = comp .get(c) @@ -420,14 +408,14 @@ trait ProjectExtra extends Scoped.Syntax: .toList .flatten - val depends = cMap.get(scoped) match { - case Some(c) => c.dependencies.toSet; case None => Set.empty - } - val derivedDepends: Set[ScopedKey[?]] = derivedDependencies(definingScoped).toSet + val depends = cMap.get(key) match + case Some(c) => c.dependencies.toSet + case None => Set.empty + val derivedDepends: Set[ScopedKey[?]] = derivedDependencies(definingKey).toSet - val reverse = Project.reverseDependencies(cMap, scoped) + val reverse = Project.reverseDependencies(cMap, key) val derivedReverse = - reverse.filter(r => derivedDependencies(r).contains(definingScoped)).toSet + reverse.filter(r => derivedDependencies(r).contains(definingKey)).toSet def printDepScopes( baseLabel: String, @@ -460,7 +448,7 @@ trait ProjectExtra extends Scoped.Syntax: definedAt + printDepScopes("Dependencies", "derived from", depends, derivedDepends) + printDepScopes("Reverse dependencies", "derives", reverse, derivedReverse) + - printScopes("Delegates", delegates(structure, scope, key)) + + printScopes("Delegates", delegates(structure, key.scope, key.key)) + printScopes("Related", related, 10) } diff --git a/main/src/main/scala/sbt/ScopedKeyData.scala b/main/src/main/scala/sbt/ScopedKeyData.scala index 3cfbf3750..4c4d5a48a 100644 --- a/main/src/main/scala/sbt/ScopedKeyData.scala +++ b/main/src/main/scala/sbt/ScopedKeyData.scala @@ -11,16 +11,14 @@ package sbt import Def.ScopedKey import sbt.internal.util.KeyTag -final case class ScopedKeyData[A](scoped: ScopedKey[A], value: Any) { - val key = scoped.key - val scope = scoped.scope - def typeName: String = key.tag.toString +final case class ScopedKeyData[A](key: ScopedKey[A], definingKey: ScopedKey[A], value: Any) { + def typeName: String = key.key.tag.toString def settingValue: Option[Any] = - key.tag match + key.key.tag match case KeyTag.Setting(_) => Some(value) case _ => None def description: String = - key.tag match + key.key.tag match case KeyTag.Task(typeArg) => s"Task: $typeArg" case KeyTag.SeqTask(typeArg) => s"Task: Seq[$typeArg]" case KeyTag.InputTask(typeArg) => s"Input task: $typeArg" diff --git a/main/src/main/scala/sbt/SessionVar.scala b/main/src/main/scala/sbt/SessionVar.scala index 40a36b587..4e3bc28c2 100644 --- a/main/src/main/scala/sbt/SessionVar.scala +++ b/main/src/main/scala/sbt/SessionVar.scala @@ -58,11 +58,13 @@ object SessionVar { key: ScopedKey[Task[T]], context: Scope, state: State - ): ScopedKey[Task[T]] = { - val subScope = Scope.replaceThis(context)(key.scope) - val scope = Project.structure(state).data.definingScope(subScope, key.key) getOrElse subScope - ScopedKey(scope, key.key) - } + ): ScopedKey[Task[T]] = + val subScoped = Project.replaceThis(context)(key) + Project + .structure(state) + .data + .definingKey(subScoped) + .getOrElse(subScoped) def read[T](key: ScopedKey[Task[T]], state: State)(implicit f: JsonFormat[T]): Option[T] = Project.structure(state).streams(state).use(key) { s => diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index c5ca91bfa..324bf6491 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -24,7 +24,6 @@ import sbt.internal.util.{ AttributeMap, IMap, MessageOnlyException, - Settings, Util, } import sbt.util.Show @@ -61,7 +60,7 @@ object Act { current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[?]], - data: Settings[Scope] + data: Def.Settings ): Parser[ScopedKey[Any]] = scopedKeySelected(index, current, defaultConfigs, keyMap, data, askProject = true) .map(_.key.asInstanceOf[ScopedKey[Any]]) @@ -115,7 +114,7 @@ object Act { current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[?]], - data: Settings[Scope], + data: Def.Settings, askProject: Boolean, ): Parser[ParsedKey] = scopedKeyFull(index, current, defaultConfigs, keyMap, askProject = askProject).flatMap { @@ -197,7 +196,7 @@ object Act { key ) - def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])(implicit + def select(allKeys: Seq[Parser[ParsedKey]], data: Def.Settings)(implicit show: Show[ScopedKey[?]] ): Parser[ParsedKey] = seq(allKeys) flatMap { ss => @@ -235,10 +234,7 @@ object Act { def showAmbiguous(keys: Seq[ScopedKey[?]])(implicit show: Show[ScopedKey[?]]): String = keys.take(3).map(x => show.show(x)).mkString("", ", ", if (keys.size > 3) ", ..." else "") - def isValid(data: Settings[Scope])(parsed: ParsedKey): Boolean = { - val key = parsed.key - data.definingScope(key.scope, key.key) == Some(key.scope) - } + def isValid(data: Def.Settings)(parsed: ParsedKey): Boolean = data.contains(parsed.key) def examples(p: Parser[String], exs: Set[String], label: String): Parser[String] = (p !!! ("Expected " + label)).examples(exs) @@ -571,28 +567,14 @@ object Act { def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(extracted.structure)(keys) def keyValues[T](structure: BuildStructure)(keys: Seq[ScopedKey[T]]): Values[T] = - keys.flatMap { key => - getValue(structure.data, key.scope, key.key) map { value => - KeyValue(key, value) - } - } - private def anyKeyValues( - structure: BuildStructure, - keys: Seq[ScopedKey[?]] - ): Seq[KeyValue[?]] = - keys.flatMap { key => - getValue(structure.data, key.scope, key.key) map { value => - KeyValue(key, value) - } - } + keys.flatMap(key => getValue(structure.data, key).map(KeyValue(key, _))) - private def getValue[T]( - data: Settings[Scope], - scope: Scope, - key: AttributeKey[T] - ): Option[T] = - if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(scope, key) - else data.get(scope, key) + private def anyKeyValues(structure: BuildStructure, keys: Seq[ScopedKey[?]]): Seq[KeyValue[?]] = + keys.flatMap(key => getValue(structure.data, key).map(KeyValue(key, _))) + + private def getValue[T](data: Def.Settings, key: ScopedKey[T]): Option[T] = + if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(key) + else data.get(key) def requireSession[T](s: State, p: => Parser[T]): Parser[T] = if s.get(sessionSettings).isEmpty then failure("No project loaded") else p diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 924edc4f3..6f106952e 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -11,7 +11,7 @@ package internal import java.text.DateFormat -import sbt.Def.ScopedKey +import sbt.Def.{ ScopedKey, Settings } import sbt.Keys.{ showSuccess, showTiming, timingFormat } import sbt.ProjectExtra.* import sbt.SlashSyntax0.given @@ -157,7 +157,7 @@ object Aggregation { private def timingString( startTime: Long, endTime: Long, - data: Settings[Scope], + data: Settings, currentRef: ProjectRef, ): String = { val format = (currentRef / timingFormat).get(data) getOrElse defaultFormat @@ -301,7 +301,7 @@ object Aggregation { ScopedKey(resolved, key.key) } - def aggregationEnabled(key: ScopedKey[?], data: Settings[Scope]): Boolean = + def aggregationEnabled(key: ScopedKey[?], data: Settings): Boolean = (Scope.fillTaskAxis(key.scope, key.key) / Keys.aggregate).get(data).getOrElse(true) private[sbt] val suppressShow = AttributeKey[Boolean]("suppress-aggregation-show", Int.MaxValue) diff --git a/main/src/main/scala/sbt/internal/BuildStructure.scala b/main/src/main/scala/sbt/internal/BuildStructure.scala index eb52c31a9..0625051cb 100644 --- a/main/src/main/scala/sbt/internal/BuildStructure.scala +++ b/main/src/main/scala/sbt/internal/BuildStructure.scala @@ -20,7 +20,7 @@ import sbt.SlashSyntax0.given import BuildStreams.Streams import sbt.io.syntax._ import sbt.internal.inc.MappedFileConverter -import sbt.internal.util.{ AttributeEntry, AttributeKey, AttributeMap, Attributed, Settings } +import sbt.internal.util.{ AttributeEntry, AttributeKey, AttributeMap, Attributed } import sbt.internal.util.Attributed.data import sbt.util.Logger import xsbti.FileConverter @@ -29,7 +29,7 @@ final class BuildStructure( val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[?]], - val data: Settings[Scope], + val data: Def.Settings, val index: StructureIndex, val streams: State => Streams, val delegates: Scope => Seq[Scope], @@ -271,7 +271,7 @@ final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit]) { unit.projects.map(p => ProjectRef(build, p.id) -> p) }.toIndexedSeq - def extra(data: Settings[Scope])(keyIndex: KeyIndex): BuildUtil[ResolvedProject] = + def extra(data: Def.Settings)(keyIndex: KeyIndex): BuildUtil[ResolvedProject] = BuildUtil(root, units, keyIndex, data) private[sbt] def autos = GroupedAutoPlugins(units) @@ -309,7 +309,7 @@ object BuildStreams { def mkStreams( units: Map[URI, LoadedBuildUnit], root: URI, - data: Settings[Scope] + data: Def.Settings ): State => Streams = s => { s.get(Keys.stateStreams).getOrElse { std.Streams( @@ -324,7 +324,7 @@ object BuildStreams { } } - def path(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope])( + def path(units: Map[URI, LoadedBuildUnit], root: URI, data: Def.Settings)( scoped: ScopedKey[?] ): File = resolvePath(projectPath(units, root, scoped, data), nonProjectPath(scoped)) @@ -386,7 +386,7 @@ object BuildStreams { units: Map[URI, LoadedBuildUnit], root: URI, scoped: ScopedKey[?], - data: Settings[Scope] + data: Def.Settings ): File = scoped.scope.project match { case Zero => refTarget(GlobalScope, units(root).localBase, data) / GlobalPath @@ -397,9 +397,9 @@ object BuildStreams { case This => sys.error("Unresolved project reference (This) in " + displayFull(scoped)) } - def refTarget(ref: ResolvedReference, fallbackBase: File, data: Settings[Scope]): File = + def refTarget(ref: ResolvedReference, fallbackBase: File, data: Def.Settings): File = refTarget(GlobalScope.copy(project = Select(ref)), fallbackBase, data) - def refTarget(scope: Scope, fallbackBase: File, data: Settings[Scope]): File = + def refTarget(scope: Scope, fallbackBase: File, data: Def.Settings): File = ((scope / Keys.target).get(data) getOrElse outputDirectory(fallbackBase)) / StreamsDirectory } diff --git a/main/src/main/scala/sbt/internal/BuildUtil.scala b/main/src/main/scala/sbt/internal/BuildUtil.scala index 1cbab6e24..5b6cce1f0 100644 --- a/main/src/main/scala/sbt/internal/BuildUtil.scala +++ b/main/src/main/scala/sbt/internal/BuildUtil.scala @@ -9,13 +9,13 @@ package sbt package internal -import sbt.internal.util.{ Relation, Settings, Dag } +import sbt.internal.util.{ Relation, Dag } import java.net.URI final class BuildUtil[Proj]( val keyIndex: KeyIndex, - val data: Settings[Scope], + val data: Def.Settings, val root: URI, val rootProjectID: URI => String, val project: (URI, String) => Proj, @@ -57,7 +57,7 @@ object BuildUtil { root: URI, units: Map[URI, LoadedBuildUnit], keyIndex: KeyIndex, - data: Settings[Scope] + data: Def.Settings ): BuildUtil[ResolvedProject] = { val getp = (build: URI, project: String) => Load.getProject(units, build, project) val configs = (_: ResolvedProject).configurations.map(c => ConfigKey(c.name)) diff --git a/main/src/main/scala/sbt/internal/ClasspathImpl.scala b/main/src/main/scala/sbt/internal/ClasspathImpl.scala index a18d1c0fc..2f8c5af69 100644 --- a/main/src/main/scala/sbt/internal/ClasspathImpl.scala +++ b/main/src/main/scala/sbt/internal/ClasspathImpl.scala @@ -15,7 +15,7 @@ import sbt.Keys._ import sbt.nio.Keys._ import sbt.nio.file.{ Glob, RecursiveGlob } import sbt.Def.Initialize -import sbt.internal.util.{ Attributed, Dag, Settings } +import sbt.internal.util.{ Attributed, Dag } import sbt.librarymanagement.{ Configuration, TrackLevel } import sbt.librarymanagement.Configurations.names import sbt.std.TaskExtra._ @@ -180,7 +180,7 @@ private[sbt] object ClasspathImpl { projectRef: ProjectRef, conf: Configuration, self: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies, track: TrackLevel, log: Logger @@ -198,7 +198,7 @@ private[sbt] object ClasspathImpl { projectRef: ProjectRef, conf: Configuration, self: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies, track: TrackLevel, log: Logger @@ -244,7 +244,7 @@ private[sbt] object ClasspathImpl { projectRef: ProjectRef, conf: Configuration, self: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies, track: TrackLevel, log: Logger @@ -282,7 +282,7 @@ private[sbt] object ClasspathImpl { def unmanagedDependencies0( projectRef: ProjectRef, conf: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies, log: Logger ): Initialize[Task[Classpath]] = @@ -306,7 +306,7 @@ private[sbt] object ClasspathImpl { def unmanagedLibs( dep: ResolvedReference, conf: String, - data: Settings[Scope] + data: Def.Settings ): Task[Classpath] = getClasspath(unmanagedJars, dep, conf, data) @@ -315,7 +315,7 @@ private[sbt] object ClasspathImpl { deps: BuildDependencies, conf: Configuration, self: Configuration, - data: Settings[Scope], + data: Def.Settings, track: TrackLevel, includeSelf: Boolean, log: Logger @@ -346,7 +346,7 @@ private[sbt] object ClasspathImpl { def interSort( projectRef: ProjectRef, conf: Configuration, - data: Settings[Scope], + data: Def.Settings, deps: BuildDependencies ): Seq[(ProjectRef, String)] = val visited = (new LinkedHashSet[(ProjectRef, String)]).asScala @@ -431,7 +431,7 @@ private[sbt] object ClasspathImpl { def allConfigs(conf: Configuration): Seq[Configuration] = Dag.topologicalSort(conf)(_.extendsConfigs) - def getConfigurations(p: ResolvedReference, data: Settings[Scope]): Seq[Configuration] = + def getConfigurations(p: ResolvedReference, data: Def.Settings): Seq[Configuration] = (p / ivyConfigurations).get(data).getOrElse(Nil) def confOpt(configurations: Seq[Configuration], conf: String): Option[Configuration] = @@ -441,14 +441,14 @@ private[sbt] object ClasspathImpl { key: TaskKey[Seq[A]], dep: ResolvedReference, conf: Configuration, - data: Settings[Scope] + data: Def.Settings ): Task[Seq[A]] = getClasspath(key, dep, conf.name, data) def getClasspath[A]( key: TaskKey[Seq[A]], dep: ResolvedReference, conf: String, - data: Settings[Scope] + data: Def.Settings ): Task[Seq[A]] = (dep / ConfigKey(conf) / key).get(data) match { case Some(x) => x diff --git a/main/src/main/scala/sbt/internal/Clean.scala b/main/src/main/scala/sbt/internal/Clean.scala index 48a0b5d48..14075307d 100644 --- a/main/src/main/scala/sbt/internal/Clean.scala +++ b/main/src/main/scala/sbt/internal/Clean.scala @@ -114,11 +114,9 @@ private[sbt] object Clean { // This is the special portion of the task where we clear out the relevant streams // and file outputs of a task. val streamsKey = scope.task.toOption.map(k => ScopedKey(scope.copy(task = Zero), k)) + val stampKey = ScopedKey(scope, inputFileStamps.key) val stampsKey = - extracted.structure.data.getDirect(scope, inputFileStamps.key) match { - case Some(_) => ScopedKey(scope, inputFileStamps.key) :: Nil - case _ => Nil - } + if extracted.structure.data.contains(stampKey) then stampKey :: Nil else Nil val streamsGlobs = (streamsKey.toSeq ++ stampsKey) .map(k => manager(k).cacheDirectory.toPath.toGlob / **) diff --git a/main/src/main/scala/sbt/internal/GlobalPlugin.scala b/main/src/main/scala/sbt/internal/GlobalPlugin.scala index 3635b4c55..0fe096a8e 100644 --- a/main/src/main/scala/sbt/internal/GlobalPlugin.scala +++ b/main/src/main/scala/sbt/internal/GlobalPlugin.scala @@ -92,7 +92,7 @@ object GlobalPlugin { (prods ++ intcp).distinct )(updateReport.value) } - val resolvedTaskInit = taskInit.mapReferenced(Project.mapScope(Scope.replaceThis(p))) + val resolvedTaskInit = taskInit.mapReferenced(Project.replaceThis(p)) val task = resolvedTaskInit.evaluate(data) val roots = resolvedTaskInit.dependencies evaluate(state, structure, task, roots) diff --git a/main/src/main/scala/sbt/internal/Inspect.scala b/main/src/main/scala/sbt/internal/Inspect.scala index 76b1a5072..127eb551c 100644 --- a/main/src/main/scala/sbt/internal/Inspect.scala +++ b/main/src/main/scala/sbt/internal/Inspect.scala @@ -87,8 +87,7 @@ object Inspect { val extracted = Project.extract(s) import extracted._ option match { - case Details(actual) => - Project.details(extracted.structure, actual, sk.scope, sk.key) + case Details(actual) => Project.details(extracted.structure, actual, sk) case DependencyTreeMode => val basedir = new File(Project.session(s).current.build) Project diff --git a/main/src/main/scala/sbt/internal/LintUnused.scala b/main/src/main/scala/sbt/internal/LintUnused.scala index ed7336a93..4ad806e4a 100644 --- a/main/src/main/scala/sbt/internal/LintUnused.scala +++ b/main/src/main/scala/sbt/internal/LintUnused.scala @@ -94,7 +94,7 @@ object LintUnused { } def lintResultLines( - result: Seq[(ScopedKey[?], String, Vector[SourcePosition])] + result: Seq[(ScopedKey[?], String, Seq[SourcePosition])] ): Vector[String] = { import scala.collection.mutable.ListBuffer val buffer = ListBuffer.empty[String] @@ -127,7 +127,7 @@ object LintUnused { state: State, includeKeys: String => Boolean, excludeKeys: String => Boolean - ): Seq[(ScopedKey[?], String, Vector[SourcePosition])] = { + ): Seq[(ScopedKey[?], String, Seq[SourcePosition])] = { val extracted = Project.extract(state) val structure = extracted.structure val display = Def.showShortKey(None) // extracted.showKey @@ -135,17 +135,11 @@ object LintUnused { val cMap = Def.flattenLocals(comp) val used: Set[ScopedKey[?]] = cMap.values.flatMap(_.dependencies).toSet val unused: Seq[ScopedKey[?]] = cMap.keys.filter(!used.contains(_)).toSeq - val withDefinedAts: Seq[UnusedKey] = unused map { u => - val definingScope = structure.data.definingScope(u.scope, u.key) - val definingScoped = definingScope match { - case Some(sc) => ScopedKey(sc, u.key) - case _ => u - } - val definedAt = comp.get(definingScoped) match { - case Some(c) => definedAtString(c.settings.toVector) + val withDefinedAts: Seq[UnusedKey] = unused.map { u => + val data = Project.scopedKeyData(structure, u) + val definedAt = comp.get(data.map(_.definingKey).getOrElse(u)) match + case Some(c) => definedAtString(c.settings) case _ => Vector.empty - } - val data = Project.scopedKeyData(structure, u.scope, u.key) UnusedKey(u, definedAt, data) } @@ -167,18 +161,16 @@ object LintUnused { && isLocallyDefined(u) => u } - (unusedKeys map { u => - (u.scoped, display.show(u.scoped), u.positions) - }).sortBy(_._2) + unusedKeys.map(u => (u.scoped, display.show(u.scoped), u.positions)).sortBy(_._2) } private case class UnusedKey( scoped: ScopedKey[?], - positions: Vector[SourcePosition], + positions: Seq[SourcePosition], data: Option[ScopedKeyData[?]] ) - private def definedAtString(settings: Vector[Setting[?]]): Vector[SourcePosition] = { + private def definedAtString(settings: Seq[Setting[?]]): Seq[SourcePosition] = { settings flatMap { setting => setting.pos match { case NoPosition => Vector.empty diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 7878dd305..4c659f064 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -21,7 +21,7 @@ import sbt.internal.inc.classpath.ClasspathUtil import sbt.internal.inc.{ MappedFileConverter, ScalaInstance, ZincLmUtil, ZincUtil } import sbt.internal.util.Attributed.data import sbt.internal.util.Types.const -import sbt.internal.util.{ Attributed, Settings } +import sbt.internal.util.Attributed import sbt.internal.server.BuildServerEvalReporter import sbt.io.{ GlobFilter, IO } import sbt.librarymanagement.ivy.{ InlineIvyConfiguration, IvyDependencyResolution, IvyPaths } @@ -310,7 +310,7 @@ private[sbt] object Load { (rootEval, bs) } - private def checkTargets(data: Settings[Scope]): Option[String] = + private def checkTargets(data: Def.Settings): Option[String] = val dups = overlappingTargets(allTargets(data)) if (dups.isEmpty) None else { @@ -323,7 +323,7 @@ private[sbt] object Load { private def overlappingTargets(targets: Seq[(ProjectRef, File)]): Map[File, Seq[ProjectRef]] = targets.groupBy(_._2).view.filter(_._2.size > 1).mapValues(_.map(_._1)).toMap - private def allTargets(data: Settings[Scope]): Seq[(ProjectRef, File)] = { + private def allTargets(data: Def.Settings): Seq[(ProjectRef, File)] = { import ScopeFilter._ val allProjects = ScopeFilter(Make.inAnyProject) val targetAndRef = Def.setting { (Keys.thisProjectRef.value, Keys.target.value) } @@ -369,19 +369,19 @@ private[sbt] object Load { if (isDummy(tk)) tk else Task(tk.info.set(Keys.taskDefinitionKey, key), tk.work) def structureIndex( - data: Settings[Scope], + data: Def.Settings, settings: Seq[Setting[?]], extra: KeyIndex => BuildUtil[?], projects: Map[URI, LoadedBuildUnit] ): StructureIndex = { val keys = Index.allKeys(settings) - val attributeKeys = Index.attributeKeys(data) ++ keys.map(_.key) - val scopedKeys = keys ++ data.allKeys((s, k) => ScopedKey(s, k)).toVector + val attributeKeys = data.attributeKeys ++ keys.map(_.key) + val scopedKeys = (keys ++ data.keys).toVector val projectsMap = projects.view.mapValues(_.defined.keySet).toMap val configsMap: Map[String, Seq[Configuration]] = projects.values.flatMap(bu => bu.defined map { case (k, v) => (k, v.configurations) }).toMap - val keyIndex = KeyIndex(scopedKeys.toVector, projectsMap, configsMap) - val aggIndex = KeyIndex.aggregate(scopedKeys.toVector, extra(keyIndex), projectsMap, configsMap) + val keyIndex = KeyIndex(scopedKeys, projectsMap, configsMap) + val aggIndex = KeyIndex.aggregate(scopedKeys, extra(keyIndex), projectsMap, configsMap) new StructureIndex( Index.stringToKeyMap(attributeKeys), Index.taskToKeyMap(data), diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index d5268f61b..cc786db7c 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -22,7 +22,7 @@ import java.io.PrintWriter sealed abstract class LogManager { def apply( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], writer: PrintWriter, @@ -30,20 +30,20 @@ sealed abstract class LogManager { ): ManagedLogger @deprecated("Use alternate apply that provides a LoggerContext", "1.4.0") def apply( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], writer: PrintWriter ): ManagedLogger = apply(data, state, task, writer, LoggerContext.globalContext) def backgroundLog( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], context: LoggerContext ): ManagedLogger @deprecated("Use alternate background log that provides a LoggerContext", "1.4.0") - final def backgroundLog(data: Settings[Scope], state: State, task: ScopedKey[?]): ManagedLogger = + final def backgroundLog(data: Def.Settings, state: State, task: ScopedKey[?]): ManagedLogger = backgroundLog(data, state, task, LoggerContext.globalContext) } @@ -62,7 +62,7 @@ object LogManager { // This is called by mkStreams // def construct( - data: Settings[Scope], + data: Def.Settings, state: State ): (ScopedKey[?], PrintWriter) => ManagedLogger = (task: ScopedKey[?], to: PrintWriter) => { @@ -74,7 +74,7 @@ object LogManager { @deprecated("Use alternate constructBackgroundLog that provides a LoggerContext", "1.8.0") def constructBackgroundLog( - data: Settings[Scope], + data: Def.Settings, state: State ): ScopedKey[?] => ManagedLogger = { val context = state.get(Keys.loggerContext).getOrElse(LoggerContext.globalContext) @@ -82,7 +82,7 @@ object LogManager { } def constructBackgroundLog( - data: Settings[Scope], + data: Def.Settings, state: State, context: LoggerContext ): (ScopedKey[?]) => ManagedLogger = @@ -119,7 +119,7 @@ object LogManager { extra: AppenderSupplier ) extends LogManager { def apply( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], to: PrintWriter, @@ -137,7 +137,7 @@ object LogManager { ) def backgroundLog( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], context: LoggerContext @@ -150,16 +150,16 @@ object LogManager { // to change from global being the default to overriding, switch the order of state.get and data.get def getOr[T]( key: AttributeKey[T], - data: Settings[Scope], + data: Def.Settings, scope: Scope, state: State, default: T ): T = - data.get(scope, key) orElse state.get(key) getOrElse default + data.get(ScopedKey(scope, key)).orElse(state.get(key)).getOrElse(default) @deprecated("Use defaultLogger that provides a LoggerContext", "1.4.0") def defaultLogger( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], console: Appender, @@ -170,7 +170,7 @@ object LogManager { defaultLogger(data, state, task, console, backed, relay, extra, LoggerContext.globalContext) // This is the main function that is used to generate the logger for tasks. def defaultLogger( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], console: Appender, @@ -242,7 +242,7 @@ object LogManager { } def backgroundLog( - data: Settings[Scope], + data: Def.Settings, state: State, task: ScopedKey[?], console: Appender, @@ -271,7 +271,7 @@ object LogManager { // TODO: Fix this // if global logging levels are not explicitly set, set them from project settings - // private[sbt] def setGlobalLogLevels(s: State, data: Settings[Scope]): State = + // private[sbt] def setGlobalLogLevels(s: State, data: Def.Settings): State = // if (hasExplicitGlobalLogLevels(s)) // s // else { diff --git a/main/src/main/scala/sbt/internal/ProjectQuery.scala b/main/src/main/scala/sbt/internal/ProjectQuery.scala index 33743eabf..b79e86b5b 100644 --- a/main/src/main/scala/sbt/internal/ProjectQuery.scala +++ b/main/src/main/scala/sbt/internal/ProjectQuery.scala @@ -24,7 +24,8 @@ private[sbt] case class ProjectQuery( val scalaMatches = params.get(Keys.scalaBinaryVersion.key) match case Some(expected) => - val actualSbv = structure.data.get(Scope.ThisScope.rescope(p), scalaBinaryVersion.key) + val actualSbv = + structure.data.get(Def.ScopedKey(Scope.ThisScope.rescope(p), scalaBinaryVersion.key)) actualSbv match case Some(sbv) => sbv == expected case None => true diff --git a/main/src/main/scala/sbt/internal/SettingCompletions.scala b/main/src/main/scala/sbt/internal/SettingCompletions.scala index 615ed67e8..a16513ced 100644 --- a/main/src/main/scala/sbt/internal/SettingCompletions.scala +++ b/main/src/main/scala/sbt/internal/SettingCompletions.scala @@ -9,7 +9,7 @@ package sbt package internal -import sbt.internal.util.{ AttributeKey, complete, Relation, Settings, Util } +import sbt.internal.util.{ AttributeKey, complete, Relation, Util } import sbt.util.Show import sbt.librarymanagement.Configuration @@ -138,7 +138,7 @@ private[sbt] object SettingCompletions { * The last part of the completion will generate a template for the value or function literal that will initialize the setting or task. */ def settingParser( - settings: Settings[Scope], + settings: Def.Settings, rawKeyMap: Map[String, AttributeKey[?]], context: ResolvedProject, ): Parser[String] = { @@ -156,7 +156,7 @@ private[sbt] object SettingCompletions { /** Parser for a Scope+AttributeKey (ScopedKey). */ def scopedKeyParser( keyMap: Map[String, AttributeKey[?]], - settings: Settings[Scope], + settings: Def.Settings, context: ResolvedProject ): Parser[ScopedKey[?]] = { val cutoff = KeyRanks.MainCutoff @@ -195,15 +195,11 @@ private[sbt] object SettingCompletions { */ def scopeParser( key: AttributeKey[?], - settings: Settings[Scope], + settings: Def.Settings, context: ResolvedProject ): Parser[Scope] = { - val data = settings.data - val allScopes = data.keys.toSeq - val definedScopes = data.toSeq flatMap { case (scope, attrs) => - if attrs.contains(key) then scope :: Nil else Nil - } - scope(allScopes, definedScopes, context) + val definedScopes = settings.keys.collect { case sk if sk.key == key => sk.scope } + scope(settings.scopes.toSeq, definedScopes.toSeq, context) } private def scope( diff --git a/main/src/main/scala/sbt/internal/SettingGraph.scala b/main/src/main/scala/sbt/internal/SettingGraph.scala index 5d66531f8..1d725a12c 100644 --- a/main/src/main/scala/sbt/internal/SettingGraph.scala +++ b/main/src/main/scala/sbt/internal/SettingGraph.scala @@ -25,11 +25,8 @@ object SettingGraph { compiled(structure.settings, false)(using structure.delegates, structure.scopeLocal, display) ) def loop(scoped: ScopedKey[?], generation: Int): SettingGraph = { - val key = scoped.key - val scope = scoped.scope - val definedIn = structure.data.definingScope(scope, key) map { sc => - display.show(ScopedKey(sc, key)) - } + val data = Project.scopedKeyData(structure, scoped) + val definedIn = data.map(d => display.show(d.definingKey)) val depends = cMap.get(scoped) match { case Some(c) => c.dependencies.toSet; case None => Set.empty } @@ -39,8 +36,8 @@ object SettingGraph { SettingGraph( display.show(scoped), definedIn, - Project.scopedKeyData(structure, scope, key), - key.description, + data, + scoped.key.description, basedir, depends map { (x: ScopedKey[?]) => loop(x, generation + 1) diff --git a/main/src/main/scala/sbt/internal/WatchTransitiveDependencies.scala b/main/src/main/scala/sbt/internal/WatchTransitiveDependencies.scala index d0c29b518..927e85e77 100644 --- a/main/src/main/scala/sbt/internal/WatchTransitiveDependencies.scala +++ b/main/src/main/scala/sbt/internal/WatchTransitiveDependencies.scala @@ -16,7 +16,6 @@ import sbt.ProjectExtra.* import sbt.SlashSyntax0.given import sbt.internal.io.Source import sbt.internal.nio.Globs -import sbt.internal.util.AttributeMap import sbt.internal.util.complete.Parser import sbt.nio.FileStamper import sbt.nio.Keys._ @@ -54,7 +53,7 @@ private[sbt] object WatchTransitiveDependencies { val state: State ) { def structure: BuildStructure = extracted.structure - def data: Map[Scope, AttributeMap] = extracted.structure.data.data + def data: Settings = extracted.structure.data } private def argumentsImpl( @@ -113,18 +112,18 @@ private[sbt] object WatchTransitiveDependencies { val keys = collectKeys(args, allKeys, Set.empty, Set.empty) def getDynamicInputs(scopedKey: ScopedKey[Seq[Glob]], trigger: Boolean): Seq[DynamicInput] = { data - .get(scopedKey.scope) - .map { am => - am.get(scopedKey.key) match { - case Some(globs: Seq[Glob]) => - if (!trigger) { - val stamper = am.get(inputFileStamper.key).getOrElse(FileStamper.Hash) - val forceTrigger = am.get(watchForceTriggerOnAnyChange.key).getOrElse(false) - globs.map(g => DynamicInput(g, stamper, forceTrigger)) - } else { - globs.map(g => DynamicInput(g, FileStamper.LastModified, forceTrigger = true)) - } - case None => Nil: Seq[DynamicInput] + .getDirect(scopedKey) + .map { globs => + if (!trigger) { + val stamper = + data.getDirect(scopedKey.copy(key = inputFileStamper.key)).getOrElse(FileStamper.Hash) + val forceTrigger = + data + .getDirect(scopedKey.copy(key = watchForceTriggerOnAnyChange.key)) + .getOrElse(false) + globs.map(g => DynamicInput(g, stamper, forceTrigger)) + } else { + globs.map(g => DynamicInput(g, FileStamper.LastModified, forceTrigger = true)) } } .getOrElse(Nil) @@ -148,21 +147,15 @@ private[sbt] object WatchTransitiveDependencies { .toIndexedSeq val projects = projectScopes.flatMap(_.project.toOption).distinct.toSet val scopes: Seq[Either[Scope, Seq[Glob]]] = - data.flatMap { case (s, am) => - if (s == Scope.Global || s.project.toOption.exists(projects.contains)) - am.get(Keys.watchSources.key) match { - case Some(k) => - k.work match { - // Avoid extracted.runTask if possible. - case Action.Pure(w, _) => Some(Right(w().map(_.toGlob))) - case _ => Some(Left(s)) - } - case _ => None + data.scopes.toSeq + .filter(s => s == Scope.Global || s.project.toOption.exists(projects.contains)) + .flatMap { s => + data.getDirect(ScopedKey(s, Keys.watchSources.key)).map { task => + task.work match + case a: Action.Pure[Seq[Watched.WatchSource]] => Right(a.f().map(_.toGlob)) + case _ => Left(s) } - else { - None } - }.toSeq def toDynamicInput(glob: Glob): DynamicInput = DynamicInput(glob, FileStamper.LastModified, forceTrigger = true) scopes.flatMap { diff --git a/main/src/main/scala/sbt/internal/server/SettingQuery.scala b/main/src/main/scala/sbt/internal/server/SettingQuery.scala index 86a824c3f..3b7b563bc 100644 --- a/main/src/main/scala/sbt/internal/server/SettingQuery.scala +++ b/main/src/main/scala/sbt/internal/server/SettingQuery.scala @@ -19,7 +19,7 @@ import sjsonnew._ import sjsonnew.support.scalajson.unsafe._ object SettingQuery { - import sbt.internal.util.{ AttributeKey, Settings } + import sbt.internal.util.AttributeKey import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ import sbt.Def.{ showBuildRelativeKey2, ScopedKey } @@ -70,7 +70,7 @@ object SettingQuery { currentBuild: URI, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[?]], - data: Settings[Scope] + data: Def.Settings ): Parser[ParsedKey] = scopedKeyFull(index, currentBuild, defaultConfigs, keyMap) flatMap { choices => Act.select(choices, data)(showBuildRelativeKey2(currentBuild)) @@ -81,7 +81,7 @@ object SettingQuery { currentBuild: URI, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[?]], - data: Settings[Scope] + data: Def.Settings ): Parser[ScopedKey[?]] = scopedKeySelected(index, currentBuild, defaultConfigs, keyMap, data).map(_.key) @@ -96,7 +96,7 @@ object SettingQuery { def getSettingValue[A](structure: BuildStructure, key: Def.ScopedKey[A]): Either[String, A] = structure.data - .get(key.scope, key.key) + .get(key) .toRight(s"Key ${Def.displayFull(key)} not found") .flatMap { case _: Task[_] => Left(s"Key ${Def.displayFull(key)} is a task, can only query settings") diff --git a/main/src/test/scala/PluginCommandTest.scala b/main/src/test/scala/PluginCommandTest.scala index 55268b6d0..eeb81a9d9 100644 --- a/main/src/test/scala/PluginCommandTest.scala +++ b/main/src/test/scala/PluginCommandTest.scala @@ -17,7 +17,6 @@ import sbt.internal.util.{ ConsoleOut, GlobalLogging, MainAppender, - Settings, Terminal, } import sbt.internal.inc.PlainVirtualFileConverter @@ -97,7 +96,7 @@ object FakeState { val delegates: (Scope) => Seq[Scope] = _ => Nil val scopeLocal: Def.ScopeLocal = _ => Nil - val (cMap, data: Settings[Scope]) = + val (cMap, data: Def.Settings) = Def.makeWithCompiledMap(settings)(using delegates, scopeLocal, Def.showFullKey) val extra: KeyIndex => BuildUtil[?] = (keyIndex) => BuildUtil(base.toURI, Map.empty, keyIndex, data) diff --git a/main/src/test/scala/sbt/internal/TestBuild.scala b/main/src/test/scala/sbt/internal/TestBuild.scala index ee68dc371..48dbec92e 100644 --- a/main/src/test/scala/sbt/internal/TestBuild.scala +++ b/main/src/test/scala/sbt/internal/TestBuild.scala @@ -10,7 +10,7 @@ package sbt package internal import Def.{ ScopedKey, Setting } -import sbt.internal.util.{ AttributeKey, AttributeMap, Relation, Settings } +import sbt.internal.util.{ AttributeKey, Relation } import sbt.internal.util.Types.{ const, some } import sbt.internal.util.complete.Parser import sbt.librarymanagement.Configuration @@ -59,17 +59,18 @@ abstract class TestBuild { sealed case class Structure( env: Env, current: ProjectRef, - data: Settings[Scope], + data: Def.Settings, keyIndex: KeyIndex, keyMap: Map[String, AttributeKey[?]] ) { override def toString = env.toString + "\n" + "current: " + current + "\nSettings:\n\t" + showData + keyMap.keys .mkString("All keys:\n\t", ", ", "") - def showKeys(map: AttributeMap): String = map.keys.mkString("\n\t ", ",", "\n") + def showKeys(keys: Iterable[AttributeKey[?]]): String = keys.mkString("\n\t ", ",", "\n") def showData: String = { val scopeStrings = - for ((scope, map) <- data.data) yield (Scope.display(scope, ""), showKeys(map)) + for (scope, keys) <- data.keys.groupMap(_.scope)(_.key) + yield (Scope.display(scope, ""), showKeys(keys)) scopeStrings.toSeq.sorted.map(t => t._1 + t._2).mkString("\n\t") } val extra: BuildUtil[Proj] = { @@ -86,11 +87,10 @@ abstract class TestBuild { } lazy val allAttributeKeys: Set[AttributeKey[?]] = { - val x = data.data.values.flatMap(_.keys).toSet - if (x.isEmpty) { + if (data.attributeKeys.isEmpty) { sys.error("allAttributeKeys is empty") } - x + data.attributeKeys } lazy val (taskAxes, zeroTaskAxis, onlyTaskAxis, multiTaskAxis) = { import collection.mutable @@ -98,11 +98,10 @@ abstract class TestBuild { // task axis of Scope is set to Zero and the value of the second map is the original task axis val taskAxesMappings = - for ((scope, keys) <- data.data; key <- keys.keys) - yield (ScopedKey(scope.copy(task = Zero), key), scope.task): ( - ScopedKey[?], - ScopeAxis[AttributeKey[?]] - ) + for + (scope, keys) <- data.keys.groupMap(_.scope)(_.key) + key <- keys + yield (ScopedKey(scope.copy(task = Zero), key), scope.task) val taskAxes = Relation.empty ++ taskAxesMappings val zero = new HashSet[ScopedKey[?]] @@ -240,15 +239,14 @@ abstract class TestBuild { } } val data = Def.makeWithCompiledMap(settings)(using env.delegates, const(Nil), display)._2 - val keys = data.allKeys((s, key) => ScopedKey(s, key)) - val keyMap = keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[?]] + val keyMap = data.keys.map(k => (k.key.label, k.key)).toMap[String, AttributeKey[?]] val projectsMap = env.builds.map(b => (b.uri, b.projects.map(_.id).toSet)).toMap val confs = for { b <- env.builds p <- b.projects } yield p.id -> p.configurations val confMap = confs.toMap - Structure(env, current, data, KeyIndex(keys, projectsMap, confMap), keyMap) + Structure(env, current, data, KeyIndex(data.keys, projectsMap, confMap), keyMap) } lazy val mkEnv: Gen[Env] = { diff --git a/sbt-app/src/main/scala/sbt/Import.scala b/sbt-app/src/main/scala/sbt/Import.scala index 669732047..bf3c0425c 100644 --- a/sbt-app/src/main/scala/sbt/Import.scala +++ b/sbt-app/src/main/scala/sbt/Import.scala @@ -9,6 +9,7 @@ package sbt trait Import { + type Settings = Def.Settings type Setting[T] = Def.Setting[T] type ScopedKey[T] = Def.ScopedKey[T] type SettingsDefinition = Def.SettingsDefinition @@ -146,7 +147,7 @@ trait Import { // type Dag[A <: Dag[A]] = sbt.internal.util.Dag[A] type DelegatingPMap[K[_], V[_]] = sbt.internal.util.DelegatingPMap[K, V] val ErrorHandling = sbt.internal.util.ErrorHandling - type EvaluateSettings[S] = sbt.internal.util.EvaluateSettings[S] + // type EvaluateSettings[I <: Init] = sbt.internal.util.EvaluateSettings[I] val EvaluationState = sbt.internal.util.EvaluationState val ExitHook = sbt.internal.util.ExitHook type ExitHook = sbt.internal.util.ExitHook @@ -168,7 +169,7 @@ trait Import { type IDSet[T] = sbt.internal.util.IDSet[T] val IMap = sbt.internal.util.IMap type IMap[K[_], V[_]] = sbt.internal.util.IMap[K, V] - type Init[S] = sbt.internal.util.Init[S] + type Init = sbt.internal.util.Init type JLine = sbt.internal.util.JLine // val KCons = sbt.internal.util.KCons // type KCons[H, +T <: KList[M], +M[_]] = sbt.internal.util.KCons[H, T, M] @@ -193,7 +194,6 @@ trait Import { val Relation = sbt.internal.util.Relation type Relation[A, B] = sbt.internal.util.Relation[A, B] val ScalaKeywords = sbt.internal.util.ScalaKeywords - type Settings[S] = sbt.internal.util.Settings[S] type SharedAttributeKey[T] = sbt.internal.util.SharedAttributeKey[T] val Signals = sbt.internal.util.Signals val SimpleReader = sbt.internal.util.SimpleReader diff --git a/util-collection/src/main/scala/sbt/internal/util/INode.scala b/util-collection/src/main/scala/sbt/internal/util/INode.scala index 8e73edce3..1935c0346 100644 --- a/util-collection/src/main/scala/sbt/internal/util/INode.scala +++ b/util-collection/src/main/scala/sbt/internal/util/INode.scala @@ -20,13 +20,12 @@ enum EvaluationState: case Calling case Evaluated -abstract class EvaluateSettings[ScopeType]: - protected val init: Init[ScopeType] +class EvaluateSettings[I <: Init]( + val init: I, + executor: Executor, + compiledSettings: Seq[init.Compiled[?]], +): import init._ - - protected def executor: Executor - protected def compiledSettings: Seq[Compiled[?]] - import EvaluationState.* private val complete = new LinkedBlockingQueue[Option[Throwable]] @@ -68,7 +67,7 @@ abstract class EvaluateSettings[ScopeType]: private val running = new AtomicInteger private val cancel = new AtomicBoolean(false) - def run(implicit delegates: ScopeType => Seq[ScopeType]): Settings[ScopeType] = { + def run(implicit delegates: ScopeType => Seq[ScopeType]): Settings = { assert(running.get() == 0, "Already running") startWork() roots.foreach(_.registerIfNew()) @@ -83,7 +82,7 @@ abstract class EvaluateSettings[ScopeType]: private def getResults(implicit delegates: ScopeType => Seq[ScopeType]) = static.toTypedSeq.foldLeft(empty) { case (ss, static.TPair(key, node)) => if key.key.isLocal then ss - else ss.set(key.scope, key.key, node.get) + else ss.set(key, node.get) } private lazy val getValue: [A] => INode[A] => A = [A] => (fa: INode[A]) => fa.get diff --git a/util-collection/src/main/scala/sbt/internal/util/Settings.scala b/util-collection/src/main/scala/sbt/internal/util/Settings.scala index d3ebec8d3..43da08b47 100644 --- a/util-collection/src/main/scala/sbt/internal/util/Settings.scala +++ b/util-collection/src/main/scala/sbt/internal/util/Settings.scala @@ -12,53 +12,10 @@ import sbt.util.Show import Util.{ nil, nilSeq } import scala.jdk.CollectionConverters.* -sealed trait Settings[ScopeType]: - def data: Map[ScopeType, AttributeMap] - def keys(scope: ScopeType): Set[AttributeKey[?]] - def scopes: Set[ScopeType] - def definingScope(scope: ScopeType, key: AttributeKey[?]): Option[ScopeType] - def allKeys[A](f: (ScopeType, AttributeKey[?]) => A): Seq[A] - def get[A](scope: ScopeType, key: AttributeKey[A]): Option[A] - def getDirect[A](scope: ScopeType, key: AttributeKey[A]): Option[A] - def set[A](scope: ScopeType, key: AttributeKey[A], value: A): Settings[ScopeType] -end Settings - -private final class Settings0[ScopeType]( - val data: Map[ScopeType, AttributeMap], - val delegates: ScopeType => Seq[ScopeType] -) extends Settings[ScopeType]: - - def scopes: Set[ScopeType] = data.keySet - def keys(scope: ScopeType) = data(scope).keys.toSet - - def allKeys[A](f: (ScopeType, AttributeKey[?]) => A): Seq[A] = - data.flatMap { case (scope, map) => - map.keys.map(k => f(scope, k)) - }.toSeq - - def get[A](scope: ScopeType, key: AttributeKey[A]): Option[A] = - delegates(scope).flatMap { sc => - getDirect(sc, key) - }.headOption - - def definingScope(scope: ScopeType, key: AttributeKey[?]): Option[ScopeType] = - delegates(scope).find { sc => - getDirect(sc, key).isDefined - } - - def getDirect[A](scope: ScopeType, key: AttributeKey[A]): Option[A] = - data.get(scope).flatMap(_.get(key)) - - def set[A](scope: ScopeType, key: AttributeKey[A], value: A): Settings[ScopeType] = - val map = data.getOrElse(scope, AttributeMap.empty) - val newData = data.updated(scope, map.put(key, value)) - Settings0(newData, delegates) - -end Settings0 - -// delegates should contain the input Scope as the first entry +// delegates should contain the input ScopeType as the first entry // this trait is intended to be mixed into an object -trait Init[ScopeType]: +trait Init: + type ScopeType /** * The Show instance used when a detailed String needs to be generated. @@ -80,6 +37,58 @@ trait Init[ScopeType]: type ScopeLocal = ScopedKey[?] => Seq[Setting[?]] type MapConstant = [a] => ScopedKey[a] => Option[a] + sealed trait Settings: + def attributeKeys: Set[AttributeKey[?]] + def keys: Iterable[ScopedKey[?]] + def contains(key: ScopedKey[?]): Boolean + def values: Iterable[Any] + def data: Map[ScopedKey[?], Any] + def scopes: Set[ScopeType] + def getKeyValue[A](key: ScopedKey[A]): Option[(ScopedKey[A], A)] + def get[A](key: ScopedKey[A]): Option[A] + def definingKey[A](key: ScopedKey[A]): Option[ScopedKey[A]] + def getDirect[A](key: ScopedKey[A]): Option[A] + def set[A](key: ScopedKey[A], value: A): Settings + end Settings + + private final class Settings0( + val scopes: Set[ScopeType], + val attributeKeys: Set[AttributeKey[?]], + // In 1.x it was a Map[Scope, AttributeMap] + // For the heap, it is better to store the ScopedKey[?] directly to avoid recreating + // abd duplicating them later. + val data: Map[ScopedKey[?], Any], + d: ScopeType => Seq[ScopeType] + ) extends Settings: + def keys: Iterable[ScopedKey[?]] = data.keys + def contains(key: ScopedKey[?]): Boolean = data.contains(key) + def values: Iterable[Any] = data.values + + def get[A](key: ScopedKey[A]): Option[A] = + delegates(key).flatMap(data.get).nextOption.asInstanceOf[Option[A]] + + def definingKey[A](key: ScopedKey[A]): Option[ScopedKey[A]] = + delegates(key).find(data.contains) + + def getKeyValue[A](key: ScopedKey[A]): Option[(ScopedKey[A], A)] = + delegates(key).flatMap { k => + data.get(k) match + case None => None + case Some(v) => Some(k -> v.asInstanceOf[A]) + }.nextOption + + def getDirect[A](key: ScopedKey[A]): Option[A] = data.get(key).asInstanceOf[Option[A]] + + def set[A](key: ScopedKey[A], value: A): Settings = + val newScopes = scopes + key.scope + val newAttributeKeys = attributeKeys + key.key + val newData = data.updated(key, value) + Settings0(newScopes, newAttributeKeys, newData, d) + + private def delegates[A](key: ScopedKey[A]): Iterator[ScopedKey[A]] = + d(key.scope).iterator.map(s => key.copy(scope = s)) + end Settings0 + private[sbt] abstract class ValidateKeyRef { def apply[T](key: ScopedKey[T], selfRefOk: Boolean): ValidatedRef[T] } @@ -163,16 +172,16 @@ trait Init[ScopeType]: private final val nextID = new java.util.concurrent.atomic.AtomicLong private final def nextDefaultID(): Long = nextID.incrementAndGet() - def empty(implicit delegates: ScopeType => Seq[ScopeType]): Settings[ScopeType] = - Settings0(Map.empty, delegates) + def empty(implicit delegates: ScopeType => Seq[ScopeType]): Settings = + Settings0(Set.empty, Set.empty, Map.empty, delegates) - def asTransform(s: Settings[ScopeType]): [A] => ScopedKey[A] => A = + def asTransform(s: Settings): [A] => ScopedKey[A] => A = [A] => (sk: ScopedKey[A]) => getValue(s, sk) - def getValue[T](s: Settings[ScopeType], k: ScopedKey[T]) = - s.get(k.scope, k.key) getOrElse (throw new InvalidReference(k)) + def getValue[T](s: Settings, k: ScopedKey[T]) = + s.get(k).getOrElse(throw new InvalidReference(k)) - def asFunction[A](s: Settings[ScopeType]): ScopedKey[A] => A = k => getValue(s, k) + def asFunction[A](s: Settings): ScopedKey[A] => A = k => getValue(s, k) def mapScope(f: ScopeType => ScopeType): MapScoped = [a] => (k: ScopedKey[a]) => k.copy(scope = f(k.scope)) @@ -197,7 +206,7 @@ trait Init[ScopeType]: // inject derived settings into scopes where their dependencies are directly defined // and prepend per-scope settings val derived = deriveAndLocal(initDefaults, mkDelegates(delegates)) - // group by Scope/Key, dropping dead initializations + // group by ScopeType/Key, dropping dead initializations val sMap: ScopedMap = grouped(derived) // delegate references to undefined values according to 'delegates' val dMap: ScopedMap = @@ -211,13 +220,13 @@ trait Init[ScopeType]: delegates: ScopeType => Seq[ScopeType], scopeLocal: ScopeLocal, display: Show[ScopedKey[?]] - ): Settings[ScopeType] = makeWithCompiledMap(init)._2 + ): Settings = makeWithCompiledMap(init)._2 def makeWithCompiledMap(init: Seq[Setting[?]])(using delegates: ScopeType => Seq[ScopeType], scopeLocal: ScopeLocal, display: Show[ScopedKey[?]] - ): (CompiledMap, Settings[ScopeType]) = + ): (CompiledMap, Settings) = val cMap = compiled(init)(using delegates, scopeLocal, display) // order the initializations. cyclic references are detected here. val ordered: Seq[Compiled[?]] = sort(cMap) @@ -235,16 +244,14 @@ trait Init[ScopeType]: def compile(sMap: ScopedMap): CompiledMap = sMap match case m: IMap.IMap0[ScopedKey, SettingSeq] @unchecked => - Par(m.backing.toVector) + import scala.collection.parallel.CollectionConverters.* + m.backing.par .map { case (k, ss) => - val deps = ss.flatMap(_.dependencies).toSet - ( - k, - Compiled(k.asInstanceOf[ScopedKey[Any]], deps, ss.asInstanceOf[SettingSeq[Any]]) - ) + val deps = ss.iterator.flatMap(_.dependencies).toSet + k -> Compiled(k.asInstanceOf[ScopedKey[Any]], deps, ss.asInstanceOf[SettingSeq[Any]]) } - .toVector - .toMap + .to(Map) + case _ => sMap.toTypedSeq.map { case sMap.TPair(k, ss) => val deps = ss.flatMap(_.dependencies) @@ -324,16 +331,12 @@ trait Init[ScopeType]: private def applyInits(ordered: Seq[Compiled[?]])(implicit delegates: ScopeType => Seq[ScopeType] - ): Settings[ScopeType] = + ): Settings = val x = java.util.concurrent.Executors.newFixedThreadPool(Runtime.getRuntime.availableProcessors) try { - val eval: EvaluateSettings[ScopeType] = new EvaluateSettings[ScopeType] { - override val init: Init.this.type = Init.this - def compiledSettings = ordered - def executor = x - } - eval.run + val eval: EvaluateSettings[Init.this.type] = new EvaluateSettings(Init.this, x, ordered) + eval.run(using delegates) } finally { x.shutdown() } @@ -416,15 +419,9 @@ trait Init[ScopeType]: final class Flattened(val key: ScopedKey[?], val dependencies: Iterable[ScopedKey[?]]) def flattenLocals(compiled: CompiledMap): Map[ScopedKey[?], Flattened] = { - val locals = compiled flatMap { case (key, comp) => - if (key.key.isLocal) Seq(comp) - else nilSeq[Compiled[?]] - } + val locals = compiled.collect { case (key, comp) if key.key.isLocal => comp } val ordered = Dag.topologicalSort(locals)( - _.dependencies.flatMap(dep => - if (dep.key.isLocal) Seq[Compiled[?]](compiled(dep)) - else nilSeq[Compiled[?]] - ) + _.dependencies.collect { case dep if dep.key.isLocal => compiled(dep) } ) def flatten( cmap: Map[ScopedKey[?], Flattened], @@ -433,7 +430,7 @@ trait Init[ScopeType]: ): Flattened = new Flattened( key, - deps.flatMap(dep => if (dep.key.isLocal) cmap(dep).dependencies else Seq[ScopedKey[?]](dep)) + deps.flatMap(dep => if (dep.key.isLocal) cmap(dep).dependencies else Seq(dep)) ) val empty = Map.empty[ScopedKey[?], Flattened] @@ -442,10 +439,9 @@ trait Init[ScopeType]: cmap.updated(c.key, flatten(cmap, c.key, c.dependencies)) } - compiled flatMap { case (key, comp) => - if (key.key.isLocal) nilSeq[(ScopedKey[?], Flattened)] - else - Seq[(ScopedKey[?], Flattened)]((key, flatten(flattenedLocals, key, comp.dependencies))) + compiled.collect { + case (key, comp) if !key.key.isLocal => + (key, flatten(flattenedLocals, key, comp.dependencies)) } } @@ -653,7 +649,7 @@ trait Init[ScopeType]: private[sbt] def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[A1] - def evaluate(map: Settings[ScopeType]): A1 + def evaluate(map: Settings): A1 def zip[A2](o: Initialize[A2]): Initialize[(A1, A2)] = zipTupled(o)(identity) def zipWith[A2, U](o: Initialize[A2])(f: (A1, A2) => U): Initialize[U] = @@ -799,7 +795,7 @@ trait Init[ScopeType]: (fa: Initialize[A]) => (fa.mapReferenced(g)) private def mapConstantK(g: MapConstant): [A] => Initialize[A] => Initialize[A] = [A] => (fa: Initialize[A]) => (fa.mapConstant(g)) - private def evaluateK(g: Settings[ScopeType]): [A] => Initialize[A] => A = [A] => + private def evaluateK(g: Settings): [A] => Initialize[A] => A = [A] => (fa: Initialize[A]) => (fa.evaluate(g)) private def deps(ls: List[Initialize[?]]): Seq[ScopedKey[?]] = ls.flatMap(_.dependencies) @@ -820,7 +816,7 @@ trait Init[ScopeType]: extends Keyed[S, A1]: override final def apply[A2](g: A1 => A2): Initialize[A2] = GetValue(scopedKey, g compose transform) - override final def evaluate(ss: Settings[ScopeType]): A1 = transform(getValue(ss, scopedKey)) + override final def evaluate(ss: Settings): A1 = transform(getValue(ss, scopedKey)) override final def mapReferenced(g: MapScoped): Initialize[A1] = GetValue(g(scopedKey), transform) @@ -842,7 +838,7 @@ trait Init[ScopeType]: trait KeyedInitialize[A1] extends Keyed[A1, A1]: override final def apply[A2](g: A1 => A2): Initialize[A2] = GetValue(scopedKey, g) - override final def evaluate(ss: Settings[ScopeType]): A1 = getValue(ss, scopedKey) + override final def evaluate(ss: Settings): A1 = getValue(ss, scopedKey) override final def mapReferenced(g: MapScoped): Initialize[A1] = g(scopedKey) private[sbt] override final def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[A1] = @@ -861,7 +857,7 @@ trait Init[ScopeType]: override def dependencies: Seq[ScopedKey[?]] = Nil override def apply[A2](g2: ([x] => Initialize[x] => Initialize[x]) => A2): Initialize[A2] = map(this)(g2) - override def evaluate(ss: Settings[ScopeType]): [x] => Initialize[x] => Initialize[x] = f + override def evaluate(ss: Settings): [x] => Initialize[x] => Initialize[x] = f override def mapReferenced(g: MapScoped): Initialize[[x] => Initialize[x] => Initialize[x]] = TransformCapture(mapReferencedK(g) ∙ f) override def mapConstant(g: MapConstant): Initialize[[x] => Initialize[x] => Initialize[x]] = @@ -880,7 +876,7 @@ trait Init[ScopeType]: extends Initialize[ScopedKey[A1]]: override def dependencies: Seq[ScopedKey[?]] = Nil override def apply[A2](g2: ScopedKey[A1] => A2): Initialize[A2] = map(this)(g2) - override def evaluate(ss: Settings[ScopeType]): ScopedKey[A1] = key + override def evaluate(ss: Settings): ScopedKey[A1] = key override def mapReferenced(g: MapScoped): Initialize[ScopedKey[A1]] = ValidationCapture(g(key), selfRefOk) override def mapConstant(g: MapConstant): Initialize[ScopedKey[A1]] = this @@ -898,7 +894,7 @@ trait Init[ScopeType]: extends Initialize[A1]: override def dependencies: Seq[ScopedKey[?]] = in.dependencies override def apply[A2](g: A1 => A2): Initialize[A2] = Bind[S, A2](s => f(s)(g), in) - override def evaluate(ss: Settings[ScopeType]): A1 = f(in.evaluate(ss)).evaluate(ss) + override def evaluate(ss: Settings): A1 = f(in.evaluate(ss)).evaluate(ss) override def mapReferenced(g: MapScoped) = Bind[S, A1](s => f(s).mapReferenced(g), in.mapReferenced(g)) @@ -927,7 +923,7 @@ trait Init[ScopeType]: case Some(i) => Right(Optional(i.validateKeyReferenced(g).toOption, f)) override def mapConstant(g: MapConstant): Initialize[A1] = Optional(a map mapConstantK(g)[S], f) - override def evaluate(ss: Settings[ScopeType]): A1 = + override def evaluate(ss: Settings): A1 = f(a.flatMap { i => trapBadRef(evaluateK(ss)(i)) }) // proper solution is for evaluate to be deprecated or for external use only and a new internal method returning Either be used @@ -946,7 +942,7 @@ trait Init[ScopeType]: override def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[A1] = Right(this) override def apply[A2](g: A1 => A2): Initialize[A2] = Value[A2](() => g(value())) override def mapConstant(g: MapConstant): Initialize[A1] = this - override def evaluate(map: Settings[ScopeType]): A1 = value() + override def evaluate(map: Settings): A1 = value() private[sbt] override def processAttributes[A2](init: A2)(f: (A2, AttributeMap) => A2): A2 = init end Value @@ -958,7 +954,7 @@ trait Init[ScopeType]: Right(this) override def apply[A2](g: Set[ScopeType] => A2) = map(this)(g) override def mapConstant(g: MapConstant): Initialize[Set[ScopeType]] = this - override def evaluate(map: Settings[ScopeType]): Set[ScopeType] = map.scopes + override def evaluate(map: Settings): Set[ScopeType] = map.scopes private[sbt] override def processAttributes[A2](init: A2)(f: (A2, AttributeMap) => A2): A2 = init end StaticScopes @@ -971,7 +967,7 @@ trait Init[ScopeType]: override def mapConstant(g: MapConstant): Initialize[A2] = Uniform(f, inputs.map(_.mapConstant(g))) override def apply[A3](g: A2 => A3): Initialize[A3] = Uniform(g.compose(f), inputs) - override def evaluate(ss: Settings[ScopeType]): A2 = f(inputs.map(_.evaluate(ss))) + override def evaluate(ss: Settings): A2 = f(inputs.map(_.evaluate(ss))) override def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[A2] = val tx = inputs.map(_.validateKeyReferenced(g)) @@ -997,7 +993,7 @@ trait Init[ScopeType]: override def apply[A2](g: A1 => A2): Initialize[A2] = Apply(g compose f, inputs) - override def evaluate(ss: Settings[ScopeType]): A1 = f(inputs.unmap(evaluateK(ss))) + override def evaluate(ss: Settings): A1 = f(inputs.unmap(evaluateK(ss))) override def validateKeyReferenced(g: ValidateKeyRef): ValidatedInit[A1] = val tx: Tuple.Map[Tup, ValidatedInit] = inputs.transform(validateKeyReferencedK(g)) diff --git a/util-collection/src/test/scala-2/SettingsTest.scala b/util-collection/src/test/scala-2/SettingsTest.scala index 00cf122fb..541431c02 100644 --- a/util-collection/src/test/scala-2/SettingsTest.scala +++ b/util-collection/src/test/scala-2/SettingsTest.scala @@ -191,7 +191,7 @@ object SettingsTest extends Properties("settings") { checkKey(chk, Some(expected), eval) } - def checkKey[T](key: ScopedKey[T], expected: Option[T], settings: Settings[Scope]) = { + def checkKey[T](key: ScopedKey[T], expected: Option[T], settings: Def.Settings) = { val value = settings.get(key.scope, key.key) ("Key: " + key) |: ("Value: " + value) |: @@ -199,7 +199,7 @@ object SettingsTest extends Properties("settings") { (value == expected) } - def evaluate(settings: Seq[Setting[_]]): Settings[Scope] = + def evaluate(settings: Seq[Setting[_]]): Def.Settings = try { makeWithCompiledMap(settings)(delegates, scopeLocal, showFullKey)._2 } catch { diff --git a/util-collection/src/test/scala/SettingsExample.scala b/util-collection/src/test/scala/SettingsExample.scala index 32ead8616..1631624cd 100644 --- a/util-collection/src/test/scala/SettingsExample.scala +++ b/util-collection/src/test/scala/SettingsExample.scala @@ -19,7 +19,8 @@ final case class Scope(nestIndex: Int, idAtIndex: Int = 0) // Lots of type constructors would become binary, which as you may know requires lots of type lambdas // when you want a type function with only one parameter. // That would be a general pain.) -case class SettingsExample() extends Init[Scope] { +case class SettingsExample() extends Init { + type ScopeType = Scope // Provides a way of showing a Scope+AttributeKey[_] val showFullKey: Show[ScopedKey[?]] = Show[ScopedKey[?]]((key: ScopedKey[?]) => { s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}" @@ -64,7 +65,7 @@ case class SettingsUsage(val settingsExample: SettingsExample) { // "compiles" and applies the settings. // This can be split into multiple steps to access intermediate results if desired. // The 'inspect' command operates on the output of 'compile', for example. - val applied: Settings[Scope] = + val applied: Settings = makeWithCompiledMap(mySettings)(using delegates, scopeLocal, showFullKey)._2 // Show results. From 0ccf3325c8f718ceee676fe16797a7fc3666877d Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Mon, 11 Nov 2024 15:24:04 +0100 Subject: [PATCH 7/7] Optimize indexing of aggregate keys To build the index of all aggregate keys, we were computing the reverse aggregation of each key, before indexing them. And so each aggregate key was indexed many times, once for each aggregated project. It was parallelized to reduce the latency. In this PR, we compute the reverse aggregation of all the keys at once, removing all duplication. We cannot parallelize this process anymore but we don't need to, because it is a lot faster. It reduces the total CPU time by 12%. The impact for the user depends on its number of cores. --- .../main/scala/sbt/internal/Aggregation.scala | 45 ++++++++++++------- .../main/scala/sbt/internal/KeyIndex.scala | 28 +++--------- main/src/main/scala/sbt/internal/Load.scala | 2 +- 3 files changed, 35 insertions(+), 40 deletions(-) diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 6f106952e..9afa960ce 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -266,29 +266,40 @@ object Aggregation { else extra.aggregates.forward(ref) } + /** + * Compute the reverse aggregate keys of all the `keys` at once. + * This is more performant than computing the revere aggregate keys of each key individually + * because of the duplicates. One aggregate key is the aggregation of many keys. + */ + def reverseAggregate[Proj]( + keys: Set[ScopedKey[?]], + extra: BuildUtil[Proj], + ): Iterable[ScopedKey[?]] = + val mask = ScopeMask() + def recur(keys: Set[ScopedKey[?]], acc: Set[ScopedKey[?]]): Set[ScopedKey[?]] = + if keys.isEmpty then acc + else + val aggKeys = for + key <- keys + ref <- projectAggregates(key.scope.project.toOption, extra, reverse = true) + toResolve = key.scope.copy(project = Select(ref)) + resolved = Resolve(extra, Zero, key.key, mask)(toResolve) + scoped = ScopedKey(resolved, key.key) + if !acc.contains(scoped) + yield scoped + val filteredAggKeys = aggKeys.filter(aggregationEnabled(_, extra.data)) + // recursive because an aggregate project can be aggregated in another aggregate project + recur(filteredAggKeys, acc ++ filteredAggKeys) + recur(keys, keys) + def aggregate[A1, Proj]( key: ScopedKey[A1], rawMask: ScopeMask, - extra: BuildUtil[Proj], - reverse: Boolean = false + extra: BuildUtil[Proj] ): Seq[ScopedKey[A1]] = val mask = rawMask.copy(project = true) Dag.topologicalSort(key): (k) => - if reverse then reverseAggregatedKeys(k, extra, mask) - else if aggregationEnabled(k, extra.data) then aggregatedKeys(k, extra, mask) - else Nil - - def reverseAggregatedKeys[T]( - key: ScopedKey[T], - extra: BuildUtil[?], - mask: ScopeMask - ): Seq[ScopedKey[T]] = - projectAggregates(key.scope.project.toOption, extra, reverse = true) flatMap { ref => - val toResolve = key.scope.copy(project = Select(ref)) - val resolved = Resolve(extra, Zero, key.key, mask)(toResolve) - val skey = ScopedKey(resolved, key.key) - if (aggregationEnabled(skey, extra.data)) skey :: Nil else Nil - } + if aggregationEnabled(k, extra.data) then aggregatedKeys(k, extra, mask) else Nil def aggregatedKeys[T]( key: ScopedKey[T], diff --git a/main/src/main/scala/sbt/internal/KeyIndex.scala b/main/src/main/scala/sbt/internal/KeyIndex.scala index 4c6be3ef4..9b3545413 100644 --- a/main/src/main/scala/sbt/internal/KeyIndex.scala +++ b/main/src/main/scala/sbt/internal/KeyIndex.scala @@ -29,30 +29,14 @@ object KeyIndex { } def aggregate( - known: Iterable[ScopedKey[?]], + known: Set[ScopedKey[?]], extra: BuildUtil[?], projects: Map[URI, Set[String]], configurations: Map[String, Seq[Configuration]] - ): ExtendableKeyIndex = { - /* - * Used to be: - * (base(projects, configurations) /: known) { (index, key) => - * index.addAggregated(key, extra) - * } - * This was a significant serial bottleneck during project loading that we can work around by - * computing the aggregations in parallel and then bulk adding them to the index. - */ - import scala.collection.parallel.CollectionConverters.* - val toAggregate = known.par.map { - case key if validID(key.key.label) => - Aggregation.aggregate(key, ScopeMask(), extra, reverse = true) - case _ => Nil - } - toAggregate.foldLeft(base(projects, configurations)) { - case (index, Nil) => index - case (index, keys) => keys.foldLeft(index)(_.add(_)) - } - } + ): ExtendableKeyIndex = + Aggregation + .reverseAggregate(known.filter(k => validID(k.key.label)), extra) + .foldLeft(base(projects, configurations))(_.add(_)) private def base( projects: Map[URI, Set[String]], @@ -278,7 +262,7 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn def addAggregated(scoped: ScopedKey[?], extra: BuildUtil[?]): ExtendableKeyIndex = if (validID(scoped.key.label)) { - val aggregateProjects = Aggregation.aggregate(scoped, ScopeMask(), extra, reverse = true) + val aggregateProjects = Aggregation.reverseAggregate(Set(scoped), extra) aggregateProjects.foldLeft(this: ExtendableKeyIndex)(_.add(_)) } else this diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 4c659f064..d88cdbab7 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -376,7 +376,7 @@ private[sbt] object Load { ): StructureIndex = { val keys = Index.allKeys(settings) val attributeKeys = data.attributeKeys ++ keys.map(_.key) - val scopedKeys = (keys ++ data.keys).toVector + val scopedKeys = keys ++ data.keys val projectsMap = projects.view.mapValues(_.defined.keySet).toMap val configsMap: Map[String, Seq[Configuration]] = projects.values.flatMap(bu => bu.defined map { case (k, v) => (k, v.configurations) }).toMap