diff --git a/build.sbt b/build.sbt index 0734b6987..9119853d3 100644 --- a/build.sbt +++ b/build.sbt @@ -733,6 +733,7 @@ lazy val mainProj = (project in file("main")) exclude[DirectMissingMethodProblem]("sbt.coursierint.LMCoursier.coursierConfiguration"), exclude[IncompatibleMethTypeProblem]("sbt.internal.Compiler.scalaInstanceTask"), exclude[ReversedMissingMethodProblem]("sbt.ScriptedRun.invoke"), + exclude[ReversedMissingMethodProblem]("sbt.internal.KeyIndex.toConfigIdent"), ), ) .dependsOn(lmCore, lmIvy, lmCoursierShadedPublishing) diff --git a/main-settings/src/main/scala/sbt/Def.scala b/main-settings/src/main/scala/sbt/Def.scala index f3526b1a6..c75d19960 100644 --- a/main-settings/src/main/scala/sbt/Def.scala +++ b/main-settings/src/main/scala/sbt/Def.scala @@ -102,6 +102,12 @@ object Def extends BuildSyntax with Init with InitializeImplicits: private[sbt] def showShortKey( keyNameColor: Option[String], + ): Show[ScopedKey[?]] = + showShortKey(keyNameColor, Scope.guessConfigIdent) + + private[sbt] def showShortKey( + keyNameColor: Option[String], + configNameToIdent: String => String, ): Show[ScopedKey[?]] = { def displayShort( project: Reference @@ -117,7 +123,8 @@ object Def extends BuildSyntax with Init with InitializeImplicits: Scope.display( key.scope, withColor(key.key.label, keyNameColor), - ref => displayShort(ref) + ref => displayShort(ref), + configNameToIdent ) ) } diff --git a/main-settings/src/main/scala/sbt/Scope.scala b/main-settings/src/main/scala/sbt/Scope.scala index 2dd328bec..c6db9923d 100644 --- a/main-settings/src/main/scala/sbt/Scope.scala +++ b/main-settings/src/main/scala/sbt/Scope.scala @@ -169,6 +169,9 @@ object Scope: def display(config: ConfigKey): String = guessConfigIdent(config.name) + " /" + def display(config: ConfigKey, configNameToIdent: String => String): String = + configNameToIdent(config.name) + " /" + private[sbt] val configIdents: Map[String, String] = Map( "scala-tool" -> "ScalaTool", @@ -191,6 +194,14 @@ object Scope: def display(scope: Scope, sep: String, showProject: Reference => String): String = displayMasked(scope, sep, showProject, ScopeMask()) + def display( + scope: Scope, + sep: String, + showProject: Reference => String, + configNameToIdent: String => String + ): String = + displayMasked(scope, sep, showProject, ScopeMask(), showZeroConfig = false, configNameToIdent) + private[sbt] def displayPedantic(scope: Scope, sep: String): String = displayMasked(scope, sep, showProject, ScopeMask(), true) @@ -237,25 +248,35 @@ object Scope: showProject: Reference => String, mask: ScopeMask, showZeroConfig: Boolean - ): String = { + ): String = + displayMasked(scope, sep, showProject, mask, showZeroConfig, guessConfigIdent) + + def displayMasked( + scope: Scope, + sep: String, + showProject: Reference => String, + mask: ScopeMask, + showZeroConfig: Boolean, + configNameToIdent: String => String + ): String = import scope.{ project, config, task, extra } - extra.toOption.flatMap(_.get(customShowString)).getOrElse { - val zeroConfig = if (showZeroConfig) "Zero /" else "" - val configPrefix = config.foldStrict(display, zeroConfig, "./") - val taskPrefix = task.foldStrict(_.label + " /", "", "./") - val extras = extra.foldStrict(_.entries.map(_.toString).toList, nil, nil) - val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")") - if (scope == GlobalScope) "Global / " + sep + postfix - else - mask.concatShow( - appendSpace(projectPrefix(project, showProject)), - appendSpace(configPrefix), - appendSpace(taskPrefix), - sep, - postfix - ) - } - } + extra.toOption + .flatMap(_.get(customShowString)) + .getOrElse: + val zeroConfig = if showZeroConfig then "Zero /" else "" + val configPrefix = config.foldStrict(c => display(c, configNameToIdent), zeroConfig, "./") + val taskPrefix = task.foldStrict(_.label + " /", "", "./") + val extras = extra.foldStrict(_.entries.map(_.toString).toList, nil, nil) + val postfix = if extras.isEmpty then "" else extras.mkString("(", ", ", ")") + if scope == GlobalScope then "Global / " + sep + postfix + else + mask.concatShow( + appendSpace(projectPrefix(project, showProject)), + appendSpace(configPrefix), + appendSpace(taskPrefix), + sep, + postfix + ) private[sbt] def appendSpace(s: String): String = if (s == "") "" diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 4ca0b76b6..f2ebab9ba 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -9,7 +9,7 @@ package sbt import sbt.BasicCommandStrings.{ StashOnFailure, networkExecPrefix } -import sbt.ProjectExtra.extract +import sbt.ProjectExtra.* import sbt.internal.{ ConsoleChannel, FastTrackCommands, ShutdownHooks, SysProp, TaskProgress } import sbt.internal.langserver.ErrorCodes import sbt.internal.nio.CheckBuildSources.CheckBuildSourcesKey @@ -156,7 +156,13 @@ private[sbt] object MainLoop: state.get(Keys.superShellSleep.key).getOrElse(SysProp.supershellSleep.millis) val superShellThreshold = state.get(Keys.superShellThreshold.key).getOrElse(SysProp.supershellThreshold) - val taskProgress = new TaskProgress(superShellSleep, superShellThreshold, state.log) + val taskProgress = + new TaskProgress( + superShellSleep, + superShellThreshold, + state.log, + Project.configNameToIdent(state) + ) val gcMonitor = if (SysProp.gcMonitor) Some(new sbt.internal.GCMonitor(state.log)) else None try { ErrorHandling.wideConvert { diff --git a/main/src/main/scala/sbt/ProjectExtra.scala b/main/src/main/scala/sbt/ProjectExtra.scala index 637c5c20d..479a6909b 100755 --- a/main/src/main/scala/sbt/ProjectExtra.scala +++ b/main/src/main/scala/sbt/ProjectExtra.scala @@ -42,6 +42,7 @@ import Def.{ Flattened, Initialize, ScopedKey, Setting } import sbt.internal.{ Load, BuildStructure, + KeyIndex, LoadedBuild, LoadedBuildUnit, SettingGraph, @@ -199,31 +200,69 @@ trait ProjectExtra extends Scoped.Syntax: showContextKey(state, None) def showContextKey(state: State, keyNameColor: Option[String]): Show[ScopedKey[?]] = - if (isProjectLoaded(state)) showContextKey2(session(state), keyNameColor) + if isProjectLoaded(state) then + val se = session(state) + val st = structure(state) + showContextKey2(se, st.index.keyIndex, keyNameColor) else Def.showFullKey - // @deprecated("Use showContextKey2 which doesn't take the unused structure param", "1.1.1") - // def showContextKey( - // session: SessionSettings, - // structure: BuildStructure, - // keyNameColor: Option[String] = None - // ): Show[ScopedKey[_]] = - // showContextKey2(session, keyNameColor) - def showContextKey2( session: SessionSettings, keyNameColor: Option[String] = None ): Show[ScopedKey[?]] = Def.showRelativeKey2(session.current, keyNameColor) + def showContextKey2( + session: SessionSettings, + keyIndex: KeyIndex, + keyNameColor: Option[String] + ): Show[ScopedKey[?]] = + val current = session.current + val configNameToIdent: String => String = name => keyIndex.toConfigIdent(Some(current))(name) + Show[ScopedKey[?]]: key => + val color: String => String = Def.withColor(_, keyNameColor) + key.scope.extra.toOption + .flatMap(_.get(Scope.customShowString).map(color)) + .getOrElse: + Scope.display( + key.scope, + color(key.key.label), + ref => Def.displayRelative2(current, ref), + configNameToIdent + ) + def showLoadingKey( loaded: LoadedBuild, keyNameColor: Option[String] = None ): Show[ScopedKey[?]] = - Def.showRelativeKey2( - ProjectRef(loaded.root, loaded.units(loaded.root).rootProjects.head), - keyNameColor - ) + val configNameToIdent = buildConfigNameToIdent(loaded) + val current = ProjectRef(loaded.root, loaded.units(loaded.root).rootProjects.head) + Show[ScopedKey[?]]: key => + val color: String => String = Def.withColor(_, keyNameColor) + key.scope.extra.toOption + .flatMap(_.get(Scope.customShowString).map(color)) + .getOrElse: + Scope.display( + key.scope, + color(key.key.label), + ref => Def.displayRelative2(current, ref), + configNameToIdent + ) + + private[sbt] def configNameToIdent(state: State): String => String = + if isProjectLoaded(state) then buildConfigNameToIdent(structure(state).units) + else Scope.guessConfigIdent + + private def buildConfigNameToIdent(loaded: LoadedBuild): String => String = + buildConfigNameToIdent(loaded.units) + + private def buildConfigNameToIdent(units: Map[URI, LoadedBuildUnit]): String => String = + val configMap = (for + (_, unit) <- units.iterator + (_, project) <- unit.defined.iterator + config <- project.configurations.iterator + yield config.name -> config.id).toMap + name => configMap.getOrElse(name, Scope.guessConfigIdent(name)) def getOrError[T](state: State, key: AttributeKey[T], msg: String): T = state.get(key).getOrElse(sys.error(msg)) @@ -253,11 +292,11 @@ trait ProjectExtra extends Scoped.Syntax: val se = Project.session(state) val st = Project.structure(state) val currentRef = internal.ProjectNavigation.effectiveCurrentRef(state) - Extracted(st, se, currentRef)(using Project.showContextKey2(se)) + Extracted(st, se, currentRef)(using Project.showContextKey2(se, st.index.keyIndex, None)) } private[sbt] def extract(se: SessionSettings, st: BuildStructure): Extracted = - Extracted(st, se, se.current)(using Project.showContextKey2(se)) + Extracted(st, se, se.current)(using Project.showContextKey2(se, st.index.keyIndex, None)) def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] = ref match @@ -401,7 +440,7 @@ trait ProjectExtra extends Scoped.Syntax: case None => "" 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 Some(k) => k -> s"Provided by:\n\t${display.show(ScopedKey(k.scope, key.key))}\n" case None => key -> "" val comp = Def.compiled(structure.settings, actual)(using diff --git a/main/src/main/scala/sbt/internal/AbstractTaskExecuteProgress.scala b/main/src/main/scala/sbt/internal/AbstractTaskExecuteProgress.scala index 34c90aacd..00163a4b6 100644 --- a/main/src/main/scala/sbt/internal/AbstractTaskExecuteProgress.scala +++ b/main/src/main/scala/sbt/internal/AbstractTaskExecuteProgress.scala @@ -17,10 +17,12 @@ import scala.collection.mutable import scala.collection.immutable.VectorBuilder import scala.concurrent.duration.* -private[sbt] abstract class AbstractTaskExecuteProgress extends ExecuteProgress { +private[sbt] abstract class AbstractTaskExecuteProgress( + configNameToIdent: String => String = Scope.guessConfigIdent +) extends ExecuteProgress { import AbstractTaskExecuteProgress.Timer - private val showScopedKey = Def.showShortKey(None) + private val showScopedKey = Def.showShortKey(None, configNameToIdent) private val anonOwners = new ConcurrentHashMap[TaskId[?], TaskId[?]] private val calledBy = new ConcurrentHashMap[TaskId[?], TaskId[?]] private val timings = new ConcurrentHashMap[TaskId[?], Timer] diff --git a/main/src/main/scala/sbt/internal/KeyIndex.scala b/main/src/main/scala/sbt/internal/KeyIndex.scala index fde16c5db..1af1edb66 100644 --- a/main/src/main/scala/sbt/internal/KeyIndex.scala +++ b/main/src/main/scala/sbt/internal/KeyIndex.scala @@ -48,7 +48,13 @@ object KeyIndex { val data = ids map { id => val configs = configurations.getOrElse(id, Seq()) val configIdentToName = configs.map(config => config.id -> config.name).toMap - Option(id) -> new ConfigIndex(Map.empty, configIdentToName, emptyAKeyIndex) + val configNameToIdent = configs.map(config => config.name -> config.id).toMap + Option(id) -> new ConfigIndex( + Map.empty, + configIdentToName, + configNameToIdent, + emptyAKeyIndex + ) } Option(uri) -> new ProjectIndex(data.toMap) } @@ -66,6 +72,11 @@ object KeyIndex { case Some(idx) => idx.fromConfigIdent(proj)(configIdent) case _ => Scope.unguessConfigIdent(configIdent) } + private[sbt] def toConfigIdent(proj: Option[ResolvedReference])(configName: String): String = + indices.find(idx => idx.exists(proj)) match { + case Some(idx) => idx.toConfigIdent(proj)(configName) + case _ => Scope.guessConfigIdent(configName) + } def tasks(proj: Option[ResolvedReference], conf: Option[String]) = concat(_.tasks(proj, conf)) def tasks(proj: Option[ResolvedReference], conf: Option[String], key: String) = concat(_.tasks(proj, conf, key)) @@ -79,7 +90,8 @@ object KeyIndex { private[sbt] def getOr[A, B](m: Map[A, B], key: A, or: B): B = m.getOrElse(key, or) private[sbt] def keySet[A, B](m: Map[Option[A], B]): Set[A] = m.keys.flatten.toSet private[sbt] val emptyAKeyIndex = new AKeyIndex(Relation.empty) - private[sbt] val emptyConfigIndex = new ConfigIndex(Map.empty, Map.empty, emptyAKeyIndex) + private[sbt] val emptyConfigIndex = + new ConfigIndex(Map.empty, Map.empty, Map.empty, emptyAKeyIndex) private[sbt] val emptyProjectIndex = new ProjectIndex(Map.empty) private[sbt] val emptyBuildIndex = new BuildIndex(Map.empty) @@ -115,6 +127,7 @@ trait KeyIndex { ): Set[String] private[sbt] def configIdents(project: Option[ResolvedReference]): Set[String] private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String + private[sbt] def toConfigIdent(proj: Option[ResolvedReference])(configName: String): String } trait ExtendableKeyIndex extends KeyIndex { def add(scoped: ScopedKey[?]): ExtendableKeyIndex @@ -135,11 +148,13 @@ private[sbt] case class IdentifiableConfig(name: String, ident: Option[String]) /* * data contains the mapping between a configuration name and its keys. * configIdentToName contains the mapping between a configuration ident and its name + * configNameToIdent contains the reverse mapping from name to ident for display purposes * noConfigKeys contains the keys without a configuration. */ private[sbt] final class ConfigIndex( val data: Map[String, AKeyIndex], val configIdentToName: Map[String, String], + val configNameToIdent: Map[String, String], val noConfigKeys: AKeyIndex ) { def add( @@ -157,18 +172,22 @@ private[sbt] final class ConfigIndex( config: IdentifiableConfig, task: Option[AttributeKey[?]], key: AttributeKey[?] - ): ConfigIndex = { + ): ConfigIndex = val keyIndex = data.getOrElse(config.name, emptyAKeyIndex) val configIdent = config.ident.getOrElse(Scope.guessConfigIdent(config.name)) + // Only add to configNameToIdent if not already present to preserve correct mappings from base() + val updatedNameToIdent = + if configNameToIdent.contains(config.name) then configNameToIdent + else configNameToIdent.updated(config.name, configIdent) new ConfigIndex( data.updated(config.name, keyIndex.add(task, key)), configIdentToName.updated(configIdent, config.name), + updatedNameToIdent, noConfigKeys ) - } def addKeyWithoutConfig(task: Option[AttributeKey[?]], key: AttributeKey[?]): ConfigIndex = { - new ConfigIndex(data, configIdentToName, noConfigKeys.add(task, key)) + new ConfigIndex(data, configIdentToName, configNameToIdent, noConfigKeys.add(task, key)) } def keyIndex(conf: Option[String]): AKeyIndex = conf match { @@ -179,8 +198,13 @@ private[sbt] final class ConfigIndex( def configs: Set[String] = data.keySet private[sbt] lazy val idents: Set[String] = configIdentToName.keySet - // guess Configuration name from an identifier. - // There's a guessing involved because we could have scoped key that Project is not aware of. + // Looks up the display identifier for a configuration name. + // Falls back to guessing if the name is not in the index. + private[sbt] def toConfigIdent(name: String): String = + configNameToIdent.getOrElse(name, Scope.guessConfigIdent(name)) + + // Looks up the configuration name from an identifier. + // Falls back to guessing if the identifier is not in the index. private[sbt] def fromConfigIdent(ident: String): String = configIdentToName.getOrElse(ident, Scope.unguessConfigIdent(ident)) } @@ -224,6 +248,9 @@ private[sbt] final class KeyIndex0(val data: BuildIndex) extends ExtendableKeyIn private[sbt] def fromConfigIdent(proj: Option[ResolvedReference])(configIdent: String): String = confIndex(proj).fromConfigIdent(configIdent) + private[sbt] def toConfigIdent(proj: Option[ResolvedReference])(configName: String): String = + confIndex(proj).toConfigIdent(configName) + def tasks(proj: Option[ResolvedReference], conf: Option[String]): Set[AttributeKey[?]] = keyIndex(proj, conf).tasks def tasks( diff --git a/main/src/main/scala/sbt/internal/TaskProgress.scala b/main/src/main/scala/sbt/internal/TaskProgress.scala index e7bd8295a..e7d0ba8df 100644 --- a/main/src/main/scala/sbt/internal/TaskProgress.scala +++ b/main/src/main/scala/sbt/internal/TaskProgress.scala @@ -25,8 +25,9 @@ import sbt.util.Logger private[sbt] class TaskProgress( sleepDuration: FiniteDuration, threshold: FiniteDuration, - logger: Logger -) extends AbstractTaskExecuteProgress + logger: Logger, + configNameToIdent: String => String = Scope.guessConfigIdent +) extends AbstractTaskExecuteProgress(configNameToIdent) with ExecuteProgress with AutoCloseable { private val lastTaskCount = new AtomicInteger(0) diff --git a/sbt-app/src/sbt-test/project/config-name-display/build.sbt b/sbt-app/src/sbt-test/project/config-name-display/build.sbt new file mode 100644 index 000000000..c5c1419e7 --- /dev/null +++ b/sbt-app/src/sbt-test/project/config-name-display/build.sbt @@ -0,0 +1 @@ +// intentionally empty - CheckPlugin provides settings diff --git a/sbt-app/src/sbt-test/project/config-name-display/project/CheckPlugin.scala b/sbt-app/src/sbt-test/project/config-name-display/project/CheckPlugin.scala new file mode 100644 index 000000000..787732eea --- /dev/null +++ b/sbt-app/src/sbt-test/project/config-name-display/project/CheckPlugin.scala @@ -0,0 +1,25 @@ +package sbt + +import sbt.* +import Keys.* + +object CheckPlugin extends AutoPlugin: + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + lazy val MultiJvm = config("multi-jvm") + lazy val test1 = taskKey[Unit]("") + + override def projectConfigurations = Seq(MultiJvm) + override def projectSettings = Seq( + MultiJvm / test1 := {}, + commands += Command.command("checkShortKeyDisplay") { state => + val configNameToIdent = Project.configNameToIdent(state) + val show = Def.showShortKey(None, configNameToIdent) + val displayed = show.show((MultiJvm / test1).scopedKey) + if !displayed.contains("MultiJvm") then + sys.error(s"Expected 'MultiJvm' in short key display but got: '$displayed'") + state.log.info(s"Config display check passed: $displayed") + state + }, + ) diff --git a/sbt-app/src/sbt-test/project/config-name-display/test b/sbt-app/src/sbt-test/project/config-name-display/test new file mode 100644 index 000000000..d00f1b414 --- /dev/null +++ b/sbt-app/src/sbt-test/project/config-name-display/test @@ -0,0 +1,3 @@ +# Verify that custom configuration MultiJvm displays correctly +# instead of the guessed Multi-jvm (#5211) +> checkShortKeyDisplay