From 217e0091f9a63f51bbda3360bf4c4aa39bd39bd6 Mon Sep 17 00:00:00 2001 From: bitloi <89318445+bitloi@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:50:31 -0500 Subject: [PATCH] [2.x] fix: sbt -debug doesn't display loading log (#8819) * Fix #4979: apply -debug (and other level options) at startup so loading shows debug log - Parse log level from configuration.arguments in StandardMain.initialState - Pass initialLevel to GlobalLogging.initial so console appender uses it from first log - Set Keys.logLevel and BasicKeys.explicitGlobalLogLevels in initial state when level option present - Add initialLevel parameter to GlobalLogging.initial (default Level.Info) for backward compatibility - Add InitialLogLevelSpec tests for logLevelFromArguments - Add docs/fix-4979-manual-verification.md for manual reproduction --- .../sbt/internal/util/GlobalLogging.scala | 13 +++-- main/src/main/scala/sbt/Main.scala | 48 ++++++++++++++----- .../sbt/internal/InitialLogLevelSpec.scala | 38 +++++++++++++++ 3 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 main/src/test/scala/sbt/internal/InitialLogLevelSpec.scala diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/GlobalLogging.scala b/internal/util-logging/src/main/scala/sbt/internal/util/GlobalLogging.scala index 1c09acd23..3d737a071 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/GlobalLogging.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/GlobalLogging.scala @@ -84,11 +84,18 @@ object GlobalLogging { newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging, newBackingFile: => File, console: ConsoleOut - ): GlobalLogging = { + ): GlobalLogging = + initial(newAppender, newBackingFile, console, Level.Info) + + def initial( + newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging, + newBackingFile: => File, + console: ConsoleOut, + initialLevel: Level.Value, + ): GlobalLogging = val loggerName = generateName val log = LoggerContext.globalContext.logger(loggerName, None, None) val appender = ConsoleAppender(ConsoleAppender.generateName(), console) - LoggerContext.globalContext.addAppender(loggerName, appender -> Level.Info) + LoggerContext.globalContext.addAppender(loggerName, appender -> initialLevel) GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender) - } } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 2f87ff5bc..7ef89b4ea 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -246,17 +246,33 @@ object StandardMain { ConsoleOut.systemOutOverwrite(ConsoleOut.overwriteContaining("Resolving ")) ConsoleOut.setGlobalProxy(console) - private def initialGlobalLogging(file: Option[File]): GlobalLogging = { - def createTemp(attempt: Int = 0): File = Retry { + private[sbt] def logLevelFromArguments(args: Seq[String]): Level.Value = + val earlyCmd = BasicCommandStrings.EarlyCommand + val levelOptions = Level.values.toSeq.flatMap: v => + List("-" + v.toString, "--" + v.toString) + def levelFromArg(arg: String): Option[Level.Value] = + if arg.startsWith(earlyCmd + "(") && arg.endsWith(")") then + val inner = arg.slice(earlyCmd.length + 1, arg.length - 1).trim + Level.values.find(_.toString == inner) + else if levelOptions.contains(arg) then + Level.values.find(v => arg == "-" + v.toString || arg == "--" + v.toString) + else None + args.flatMap(levelFromArg).headOption.getOrElse(Level.Info) + + private def initialGlobalLogging(file: Option[File], initialLevel: Level.Value): GlobalLogging = + def createTemp(attempt: Int = 0): File = Retry: file.foreach(f => if (!f.exists()) IO.createDirectory(f)) File.createTempFile("sbt-global-log", ".log", file.orNull) - } GlobalLogging.initial( MainAppender.globalDefault(ConsoleOut.globalProxy), createTemp(), - ConsoleOut.globalProxy + ConsoleOut.globalProxy, + initialLevel ) - } + + private def initialGlobalLogging(file: Option[File]): GlobalLogging = + initialGlobalLogging(file, Level.Info) + def initialGlobalLogging(file: File): GlobalLogging = initialGlobalLogging(Option(file)) @deprecated("use version that takes file argument", "1.4.0") def initialGlobalLogging: GlobalLogging = initialGlobalLogging(None) @@ -265,8 +281,7 @@ object StandardMain { configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String] - ): State = { - // This is to workaround https://github.com/sbt/io/issues/110 + ): State = if (!sys.props.contains("jna.nosys")) sys.props.put("jna.nosys", "true") import BasicCommandStrings.{ DashDashDetachStdio, DashDashServer, isEarlyCommand } @@ -274,11 +289,18 @@ object StandardMain { configuration.arguments .map(_.trim) .filterNot(c => c == DashDashDetachStdio || c == DashDashServer) + .toSeq + val initialLevel = logLevelFromArguments(userCommands) val (earlyCommands, normalCommands) = (preCommands ++ userCommands).partition(isEarlyCommand) - val commands = (earlyCommands ++ normalCommands).toList map { x => - Exec(x, None) - } - val initAttrs = BuiltinCommands.initialAttributes + val commands = (earlyCommands ++ normalCommands).toList.map(x => Exec(x, None)) + val baseAttrs = BuiltinCommands.initialAttributes + val initAttrs = + if initialLevel == Level.Info then baseAttrs + else + baseAttrs + .put(Keys.logLevel.key, initialLevel) + .put(BasicKeys.explicitGlobalLogLevels, true) + val logDir = BuildPaths.globalLoggingStandard(configuration.baseDirectory) val s = State( configuration, initialDefinitions, @@ -287,12 +309,12 @@ object StandardMain { commands, State.newHistory, initAttrs, - initialGlobalLogging(BuildPaths.globalLoggingStandard(configuration.baseDirectory)), + initialGlobalLogging(Option(logDir), initialLevel), None, State.Continue ) s.initializeClassLoaderCache - } + end initialState } import sbt.BasicCommandStrings.* diff --git a/main/src/test/scala/sbt/internal/InitialLogLevelSpec.scala b/main/src/test/scala/sbt/internal/InitialLogLevelSpec.scala new file mode 100644 index 000000000..4296bbe99 --- /dev/null +++ b/main/src/test/scala/sbt/internal/InitialLogLevelSpec.scala @@ -0,0 +1,38 @@ +/* + * sbt + * Copyright 2023, Scala center + * Copyright 2011 - 2022, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal + +import sbt.util.Level + +object InitialLogLevelSpec extends verify.BasicTestSuite: + + test("logLevelFromArguments returns Debug when -debug is in arguments"): + assert(StandardMain.logLevelFromArguments(Seq("-debug")) == Level.Debug) + + test("logLevelFromArguments returns Debug when --debug is in arguments"): + assert(StandardMain.logLevelFromArguments(Seq("compile", "--debug")) == Level.Debug) + + test("logLevelFromArguments returns Debug when early(debug) is in arguments"): + assert(StandardMain.logLevelFromArguments(Seq("early(debug)")) == Level.Debug) + assert(StandardMain.logLevelFromArguments(Seq("compile", "early(debug)")) == Level.Debug) + + test("logLevelFromArguments returns Info when no level option in arguments"): + assert(StandardMain.logLevelFromArguments(Seq()) == Level.Info) + assert(StandardMain.logLevelFromArguments(Seq("compile", "run")) == Level.Info) + + test("logLevelFromArguments uses first level option when multiple present"): + assert(StandardMain.logLevelFromArguments(Seq("-warn", "-debug")) == Level.Warn) + assert(StandardMain.logLevelFromArguments(Seq("-error", "-info")) == Level.Error) + + test("logLevelFromArguments supports all level options"): + assert(StandardMain.logLevelFromArguments(Seq("-info")) == Level.Info) + assert(StandardMain.logLevelFromArguments(Seq("--warn")) == Level.Warn) + assert(StandardMain.logLevelFromArguments(Seq("--error")) == Level.Error) +end InitialLogLevelSpec