diff --git a/main/Defaults.scala b/main/Defaults.scala index 75e6c4b40..fb8d4fdf7 100755 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -131,7 +131,7 @@ object Defaults extends BuildCommon )) def projectCore: Seq[Setting[_]] = Seq( name <<= thisProject(_.id), - logManager <<= extraLoggers(LogManager.defaults), + logManager <<= extraLoggers(extra => LogManager.defaults(extra, StandardMain.console)), onLoadMessage <<= onLoadMessage or (name, thisProjectRef)("Set current project to " + _ + " (in build " + _.build +")"), runnerTask ) diff --git a/main/LogManager.scala b/main/LogManager.scala index 54ced9ba4..220d15881 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -17,18 +17,25 @@ object LogManager { def construct(data: Settings[Scope], state: State) = (task: ScopedKey[_], to: PrintWriter) => { - val manager = logManager in task.scope get data getOrElse default + val manager = logManager in task.scope get data getOrElse defaultManager(StandardMain.console) manager(data, state, task, to) } - lazy val default: LogManager = withLoggers() + @deprecated("Use defaultManager to explicitly specify standard out.", "0.13.0") + lazy val default: LogManager = defaultManager(StandardMain.console) - def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = - withLoggers((task,state) => defaultScreen(suppressedMessage(task, state)), extra = extra) + def defaultManager(console: ConsoleOut): LogManager = withLoggers((sk,s) => defaultScreen(console)) + + @deprecated("Explicitly specify standard out.", "0.13.0") + def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = defaults(extra, StandardMain.console) + + def defaults(extra: ScopedKey[_] => Seq[AbstractLogger], console: ConsoleOut): LogManager = + withLoggers((task,state) => defaultScreen(console, suppressedMessage(task, state)), extra = extra) def withScreenLogger(mk: (ScopedKey[_], State) => AbstractLogger): LogManager = withLoggers(screen = mk) - def withLoggers(screen: (ScopedKey[_], State) => AbstractLogger = (sk, s) => defaultScreen(), backed: PrintWriter => AbstractLogger = defaultBacked(), extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager = - new LogManager { + def withLoggers(screen: (ScopedKey[_], State) => AbstractLogger = (sk, s) => defaultScreen(StandardMain.console), + backed: PrintWriter => AbstractLogger = defaultBacked(), + extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager = new LogManager { def apply(data: Settings[Scope], state: State, task: ScopedKey[_], to: PrintWriter): Logger = defaultLogger(data, state, task, screen(task, state), backed(to), extra(task).toList) } diff --git a/main/Main.scala b/main/Main.scala index 76ca4091b..3940789c9 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -48,13 +48,16 @@ final class ConsoleMain extends xsbti.AppMain object StandardMain { + /** The common interface to standard output, used for all built-in ConsoleLoggers. */ + val console = ConsoleLogger.systemOutOverwrite(ConsoleLogger.overwriteContaining("Resolving ")) + def initialState(configuration: xsbti.AppConfiguration, initialDefinitions: Seq[Command], preCommands: Seq[String]): State = { val commands = preCommands ++ configuration.arguments.map(_.trim) State( configuration, initialDefinitions, Set.empty, None, commands, State.newHistory, BuiltinCommands.initialAttributes, initialGlobalLogging, State.Continue ) } def initialGlobalLogging: GlobalLogging = - GlobalLogging.initial(MainLogging.globalDefault _, File.createTempFile("sbt",".log")) + GlobalLogging.initial((pw, glb) => MainLogging.globalDefault(pw,glb,console), File.createTempFile("sbt",".log"), console) } import DefaultParsers._ diff --git a/util/log/ConsoleLogger.scala b/util/log/ConsoleLogger.scala index bc48d7ad2..711489c5c 100644 --- a/util/log/ConsoleLogger.scala +++ b/util/log/ConsoleLogger.scala @@ -8,6 +8,29 @@ package sbt object ConsoleLogger { def systemOut: ConsoleOut = printStreamOut(System.out) + def overwriteContaining(s: String): (String,String) => Boolean = (cur, prev) => + cur.contains(s) && prev.contains(s) + + /** ConsoleOut instance that is backed by System.out. It overwrites the previously printed line + * if the function `f(lineToWrite, previousLine)` returns true. + * + * The ConsoleOut returned by this method assumes that the only newlines are from println calls + * and not in the String arguments. */ + def systemOutOverwrite(f: (String,String) => Boolean): ConsoleOut = new ConsoleOut { + val lockObject = System.out + private[this] var last: Option[String] = None + private[this] var current = new java.lang.StringBuffer + def print(s: String): Unit = synchronized { current.append(s) } + def println(s: String): Unit = synchronized { current.append(s); println() } + def println(): Unit = synchronized { + val s = current.toString + if(formatEnabled && last.exists(lmsg => f(s, lmsg))) + System.out.print(OverwriteLine) + System.out.println(s) + last = Some(s) + current = new java.lang.StringBuffer + } + } def printStreamOut(out: PrintStream): ConsoleOut = new ConsoleOut { val lockObject = out def print(s: String) = out.print(s) @@ -30,6 +53,9 @@ object ConsoleLogger /** Escape character, used to introduce an escape sequence. */ final val ESC = '\u001B' + /** Move to beginning of previous line and clear the line. */ + private[sbt] final val OverwriteLine = "\r\u001BM\u001B[2K" + /** An escape terminator is a character in the range `@` (decimal value 64) to `~` (decimal value 126). * It is the final character in an escape sequence. */ def isEscapeTerminator(c: Char): Boolean = diff --git a/util/log/GlobalLogging.scala b/util/log/GlobalLogging.scala index e54b00a00..f712dd88a 100644 --- a/util/log/GlobalLogging.scala +++ b/util/log/GlobalLogging.scala @@ -19,9 +19,12 @@ object GlobalLogBacking } object GlobalLogging { + @deprecated("Explicitly specify standard out.", "0.13.0") def initial(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File): GlobalLogging = + initial(newLogger, newBackingFile, ConsoleLogger.systemOut) + def initial(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File, console: ConsoleOut): GlobalLogging = { - val log = ConsoleLogger() + val log = ConsoleLogger(console) GlobalLogging(log, log, GlobalLogBacking(newLogger, newBackingFile)) } } \ No newline at end of file diff --git a/util/log/MainLogging.scala b/util/log/MainLogging.scala index d7b45e043..9e024ef28 100644 --- a/util/log/MainLogging.scala +++ b/util/log/MainLogging.scala @@ -18,17 +18,29 @@ object MainLogging multi: Logger } def globalDefault(writer: PrintWriter, backing: GlobalLogBacking): GlobalLogging = + globalDefault(writer, backing, ConsoleLogger.systemOut) + def globalDefault(writer: PrintWriter, backing: GlobalLogBacking, console: ConsoleOut): GlobalLogging = { val backed = defaultBacked()(writer) - val full = multiLogger(defaultMultiConfig( backed ) ) + val full = multiLogger(defaultMultiConfig(console, backed ) ) GlobalLogging(full, backed, backing) } + @deprecated("Explicitly specify the console output.", "0.13.0") def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig = - new MultiLoggerConfig(defaultScreen(ConsoleLogger.noSuppressedMessage), backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue) + defaultMultiConfig(ConsoleLogger.systemOut, backing) + def defaultMultiConfig(console: ConsoleOut, backing: AbstractLogger): MultiLoggerConfig = + new MultiLoggerConfig(defaultScreen(console, ConsoleLogger.noSuppressedMessage), backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue) + @deprecated("Explicitly specify the console output.", "0.13.0") def defaultScreen(): AbstractLogger = ConsoleLogger() + + @deprecated("Explicitly specify the console output.", "0.13.0") def defaultScreen(suppressedMessage: SuppressedTraceContext => Option[String]): AbstractLogger = ConsoleLogger(suppressedMessage = suppressedMessage) + + def defaultScreen(console: ConsoleOut): AbstractLogger = ConsoleLogger(console) + def defaultScreen(console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): AbstractLogger = + ConsoleLogger(console, suppressedMessage = suppressedMessage) def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger = to => ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = useColor)