mirror of https://github.com/sbt/sbt.git
improved global logging and 'last' command
This commit is contained in:
parent
f654b03618
commit
30baf74169
|
|
@ -11,7 +11,8 @@ import Path._
|
|||
|
||||
object CommandSupport
|
||||
{
|
||||
def logger(s: State) = s get Keys.logged getOrElse ConsoleLogger()
|
||||
def logger(s: State) = globalLogging(s).full
|
||||
def globalLogging(s: State) = s get Keys.globalLogging.key getOrElse error("Global logging misconfigured")
|
||||
|
||||
// slightly better fallback in case of older launcher
|
||||
def bootDirectory(state: State): File =
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ package sbt
|
|||
|
||||
import java.io.File
|
||||
import Project.{ScopedKey, Setting}
|
||||
import Keys.{streams, Streams, TaskStreams}
|
||||
import Keys.{globalLogging, streams, Streams, TaskStreams}
|
||||
import Keys.{dummyState, dummyStreamsManager, streamsManager, taskDefinitionKey}
|
||||
import Scope.{GlobalScope, ThisScope}
|
||||
import scala.Console.{RED, RESET}
|
||||
|
|
@ -41,7 +41,6 @@ object EvaluateTask
|
|||
def logIncResult(result: Result[_], streams: Streams) = result match { case Inc(i) => logIncomplete(i, streams); case _ => () }
|
||||
def logIncomplete(result: Incomplete, streams: Streams)
|
||||
{
|
||||
val log = streams(ScopedKey(GlobalScope, Keys.logged)).log
|
||||
val all = Incomplete linearize result
|
||||
val keyed = for(Incomplete(Some(key: Project.ScopedKey[_]), _, msg, _, ex) <- all) yield (key, msg, ex)
|
||||
val un = all.filter { i => i.node.isEmpty || i.message.isEmpty }
|
||||
|
|
@ -51,8 +50,9 @@ object EvaluateTask
|
|||
for( (key, msg, ex) <- keyed if(msg.isDefined || ex.isDefined) )
|
||||
{
|
||||
val msgString = (msg.toList ++ ex.toList.map(ErrorHandling.reducedToString)).mkString("\n\t")
|
||||
val log = getStreams(key, streams).log
|
||||
val keyString = if(log.ansiCodesSupported) RED + key.key.label + RESET else key.key.label
|
||||
getStreams(key, streams).log.error(Scope.display(key.scope, keyString) + ": " + msgString)
|
||||
log.error(Scope.display(key.scope, keyString) + ": " + msgString)
|
||||
}
|
||||
}
|
||||
def getStreams(key: ScopedKey[_], streams: Streams): TaskStreams =
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ object Keys
|
|||
val onUnload = SettingKey[State => State]("on-unload", "Transformation to apply to the build state when the build is unloaded.")
|
||||
|
||||
// Command keys
|
||||
val logged = AttributeKey[Logger]("log", "Provides a Logger for commands.")
|
||||
val globalLogging = SettingKey[GlobalLogging]("global-logging", "Provides a global Logger, including command logging.")
|
||||
val historyPath = SettingKey[Option[File]]("history", "The location where command line history is persisted.")
|
||||
val shellPrompt = SettingKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.")
|
||||
val analysis = AttributeKey[inc.Analysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ package sbt
|
|||
import inc.{FileValueCache, Locate}
|
||||
import Project.{inScope, ScopedKey, ScopeLocal, Setting}
|
||||
import Keys.{appConfiguration, baseDirectory, configuration, streams, Streams, thisProject, thisProjectRef}
|
||||
import Keys.{isDummy, parseResult, resolvedScoped, taskDefinitionKey}
|
||||
import Keys.{globalLogging, isDummy, parseResult, resolvedScoped, taskDefinitionKey}
|
||||
import tools.nsc.reporters.ConsoleReporter
|
||||
import Build.{analyzed, data}
|
||||
import Scope.{GlobalScope, ThisScope}
|
||||
|
|
@ -47,10 +47,13 @@ object Load
|
|||
val compilers = Compiler.compilers(ClasspathOptions.boot)(state.configuration, log)
|
||||
val evalPluginDef = EvaluateTask.evalPluginDef(log) _
|
||||
val delegates = defaultDelegates
|
||||
val injectGlobal: Seq[Project.Setting[_]] = ((appConfiguration in GlobalScope) :== state.configuration) +: EvaluateTask.injectSettings
|
||||
val inject = InjectSettings(injectGlobal, Nil, const(Nil))
|
||||
val inject = InjectSettings(injectGlobal(state), Nil, const(Nil))
|
||||
new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, definesClass, delegates, EvaluateTask.injectStreams, inject, None, log)
|
||||
}
|
||||
def injectGlobal(state: State): Seq[Project.Setting[_]] =
|
||||
(appConfiguration in GlobalScope :== state.configuration) +:
|
||||
(globalLogging in GlobalScope := CommandSupport.globalLogging(state)) +:
|
||||
EvaluateTask.injectSettings
|
||||
def defaultWithGlobal(state: State, base: File, rawConfig: LoadBuildConfiguration, globalBase: File, log: Logger): LoadBuildConfiguration =
|
||||
{
|
||||
val withGlobal = loadGlobal(state, base, defaultGlobalPlugins(globalBase), rawConfig)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
package sbt
|
||||
|
||||
import java.io.PrintWriter
|
||||
import java.io.File
|
||||
import LogManager._
|
||||
import std.Transform
|
||||
import Project.ScopedKey
|
||||
import Keys.{logLevel, logManager, persistLogLevel, persistTraceLevel, traceLevel}
|
||||
import Scope.GlobalScope
|
||||
import Keys.{logLevel, logManager, persistLogLevel, persistTraceLevel, state, traceLevel}
|
||||
|
||||
object LogManager
|
||||
{
|
||||
|
|
@ -20,12 +22,13 @@ object LogManager
|
|||
def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = withLoggers(extra = extra)
|
||||
|
||||
def defaultScreen: AbstractLogger = ConsoleLogger()
|
||||
def defaultBacked(useColor: Boolean): PrintWriter => AbstractLogger =
|
||||
|
||||
def defaultBacked(useColor: Boolean = ConsoleLogger.formatEnabled): PrintWriter => ConsoleLogger =
|
||||
to => ConsoleLogger(ConsoleLogger.printWriterOut(to), useColor = useColor) // TODO: should probably filter ANSI codes when useColor=false
|
||||
|
||||
def withScreenLogger(mk: => AbstractLogger): LogManager = withLoggers(mk)
|
||||
|
||||
def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(ConsoleLogger.formatEnabled), extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager =
|
||||
def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(), extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager =
|
||||
new LogManager {
|
||||
def apply(data: Settings[Scope], task: ScopedKey[_], to: PrintWriter): Logger =
|
||||
defaultLogger(data, task, screen, backed(to), extra(task).toList)
|
||||
|
|
@ -39,7 +42,12 @@ object LogManager
|
|||
val backingLevel = getOr(persistLogLevel.key, Level.Debug)
|
||||
val screenTrace = getOr(traceLevel.key, -1)
|
||||
val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue)
|
||||
|
||||
val extraBacked = data.get(Scope.GlobalScope, Keys.globalLogging.key).map(_.backed).toList
|
||||
multiLogger( new MultiLoggerConfig(console, backed, extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace) )
|
||||
}
|
||||
def multiLogger(config: MultiLoggerConfig): Logger =
|
||||
{
|
||||
import config._
|
||||
val multi = new MultiLogger(console :: backed :: extra)
|
||||
// sets multi to the most verbose for clients that inspect the current level
|
||||
multi setLevel Level.unionAll(backingLevel :: screenLevel :: extra.map(_.getLevel))
|
||||
|
|
@ -50,8 +58,19 @@ object LogManager
|
|||
backed setTrace backingTrace
|
||||
multi: Logger
|
||||
}
|
||||
def globalDefault(writer: PrintWriter, file: File): GlobalLogging =
|
||||
{
|
||||
val backed = defaultBacked()(writer)
|
||||
val full = multiLogger(defaultMultiConfig( backed ) )
|
||||
GlobalLogging(full, backed, file)
|
||||
}
|
||||
|
||||
def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig =
|
||||
new MultiLoggerConfig(defaultScreen, backing, Nil, Level.Info, Level.Debug, -1, Int.MaxValue)
|
||||
}
|
||||
final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger], screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int)
|
||||
trait LogManager
|
||||
{
|
||||
def apply(data: Settings[Scope], task: ScopedKey[_], writer: PrintWriter): Logger
|
||||
}
|
||||
}
|
||||
final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: File)
|
||||
|
|
@ -10,7 +10,7 @@ package sbt
|
|||
import Types.idFun
|
||||
|
||||
import Command.applyEffect
|
||||
import Keys.{analysis,historyPath,logged,shellPrompt}
|
||||
import Keys.{analysis,historyPath,globalLogging,shellPrompt}
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConversions._
|
||||
import Function.tupled
|
||||
|
|
@ -30,7 +30,7 @@ final class xMain extends xsbti.AppMain
|
|||
val initialCommandDefs = Seq(initialize, defaults)
|
||||
val commands = DefaultsCommand +: InitCommand +: (DefaultBootCommands ++ configuration.arguments.map(_.trim))
|
||||
val state = State( configuration, initialCommandDefs, Set.empty, None, commands, initialAttributes, None )
|
||||
MainLoop.run(state)
|
||||
MainLoop.runLogged(state)
|
||||
}
|
||||
}
|
||||
final class ScriptMain extends xsbti.AppMain
|
||||
|
|
@ -40,7 +40,7 @@ final class ScriptMain extends xsbti.AppMain
|
|||
import BuiltinCommands.{initialAttributes, ScriptCommands}
|
||||
val commands = Script.Name +: configuration.arguments.map(_.trim)
|
||||
val state = State( configuration, ScriptCommands, Set.empty, None, commands, initialAttributes, None )
|
||||
MainLoop.run(state)
|
||||
MainLoop.runLogged(state)
|
||||
}
|
||||
}
|
||||
final class ConsoleMain extends xsbti.AppMain
|
||||
|
|
@ -50,12 +50,32 @@ final class ConsoleMain extends xsbti.AppMain
|
|||
import BuiltinCommands.{initialAttributes, ConsoleCommands}
|
||||
val commands = IvyConsole.Name +: configuration.arguments.map(_.trim)
|
||||
val state = State( configuration, ConsoleCommands, Set.empty, None, commands, initialAttributes, None )
|
||||
MainLoop.run(state)
|
||||
MainLoop.runLogged(state)
|
||||
}
|
||||
}
|
||||
object MainLoop
|
||||
{
|
||||
@tailrec final def run(state: State): xsbti.MainResult =
|
||||
def runLogged(state: State): xsbti.MainResult =
|
||||
{
|
||||
val logFile = File.createTempFile("sbt", ".log")
|
||||
try {
|
||||
val result = runLogged(state, logFile)
|
||||
logFile.delete() // only delete when exiting normally
|
||||
result
|
||||
}
|
||||
catch {
|
||||
case e: xsbti.FullReload => throw e
|
||||
case e => System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logFile); throw e
|
||||
}
|
||||
}
|
||||
def runLogged(state: State, backing: File): xsbti.MainResult =
|
||||
Using.fileWriter()(backing) { writer =>
|
||||
val out = new java.io.PrintWriter(writer)
|
||||
val loggedState = state.put(globalLogging.key, LogManager.globalDefault(out, backing))
|
||||
try { run(loggedState) } finally { out.close() }
|
||||
}
|
||||
|
||||
@tailrec def run(state: State): xsbti.MainResult =
|
||||
state.result match
|
||||
{
|
||||
case None => run(next(state))
|
||||
|
|
@ -75,7 +95,7 @@ object MainLoop
|
|||
import CommandSupport._
|
||||
object BuiltinCommands
|
||||
{
|
||||
def initialAttributes = AttributeMap.empty.put(logged, ConsoleLogger())
|
||||
def initialAttributes = AttributeMap.empty
|
||||
|
||||
def ConsoleCommands: Seq[Command] = Seq(ignore, exit, IvyConsole.command, act, nop)
|
||||
def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, act, nop)
|
||||
|
|
@ -176,7 +196,9 @@ object BuiltinCommands
|
|||
val reader = new FullReader(history, s.combinedParser)
|
||||
val line = reader.readLine(prompt)
|
||||
line match {
|
||||
case Some(line) => s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands)
|
||||
case Some(line) =>
|
||||
if(!line.trim.isEmpty) CommandSupport.globalLogging(s).backed.out.println(Output.DefaultTail + line)
|
||||
s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands)
|
||||
case None => s
|
||||
}
|
||||
}
|
||||
|
|
@ -308,10 +330,14 @@ object BuiltinCommands
|
|||
logger(s).info(detailString)
|
||||
s
|
||||
}
|
||||
def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { case (s,(pattern,sk)) =>
|
||||
val (str, ref) = extractLast(s)
|
||||
Output.lastGrep(sk, str, pattern, ref)
|
||||
s
|
||||
def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
|
||||
case (s, (pattern,Some(sk))) =>
|
||||
val (str, ref) = extractLast(s)
|
||||
Output.lastGrep(sk, str, pattern)
|
||||
s
|
||||
case (s, (pattern, None)) =>
|
||||
Output.lastGrep(CommandSupport.globalLogging(s).backing, pattern)
|
||||
s
|
||||
}
|
||||
def extractLast(s: State) = {
|
||||
val ext = Project.extract(s)
|
||||
|
|
@ -321,10 +347,14 @@ object BuiltinCommands
|
|||
val spacedKeyParser = (s: State) => Act.requireSession(s, token(Space) ~> Act.scopedKeyParser(s))
|
||||
val optSpacedKeyParser = (s: State) => spacedKeyParser(s).?
|
||||
def lastGrepParser(s: State) = Act.requireSession(s, (token(Space) ~> token(NotSpace, "<pattern>")) ~ optSpacedKeyParser(s))
|
||||
def last = Command(LastCommand, lastBrief, lastDetailed)(optSpacedKeyParser) { (s,sk) =>
|
||||
val (str, ref) = extractLast(s)
|
||||
Output.last(sk, str, ref)
|
||||
s
|
||||
def last = Command(LastCommand, lastBrief, lastDetailed)(optSpacedKeyParser) {
|
||||
case (s,Some(sk)) =>
|
||||
val (str, ref) = extractLast(s)
|
||||
Output.last(sk, str)
|
||||
s
|
||||
case (s, None) =>
|
||||
Output.last( CommandSupport.globalLogging(s).backing )
|
||||
s
|
||||
}
|
||||
|
||||
def autoImports(extracted: Extracted): EvalImports = new EvalImports(imports(extracted), "<auto-imports>")
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
package sbt
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import java.io.File
|
||||
import Keys.{Streams, TaskStreams}
|
||||
import Project.ScopedKey
|
||||
import annotation.tailrec
|
||||
|
||||
object Output
|
||||
{
|
||||
|
|
@ -22,17 +24,27 @@ object Output
|
|||
else
|
||||
None
|
||||
}
|
||||
final val DefaultTail = "> "
|
||||
|
||||
def last(key: ScopedKey[_], mgr: Streams): Unit = printLines(lastLines(key, mgr))
|
||||
def last(file: File, tailDelim: String = DefaultTail): Unit = printLines(tailLines(file, tailDelim))
|
||||
|
||||
def lastGrep(key: ScopedKey[_], mgr: Streams, patternString: String): Unit =
|
||||
lastGrep(lastLines(key, mgr), patternString )
|
||||
def lastGrep(file: File, patternString: String, tailDelim: String = DefaultTail): Unit =
|
||||
lastGrep( tailLines(file, tailDelim), patternString)
|
||||
def lastGrep(lines: Seq[String], patternString: String): Unit =
|
||||
printLines(lines flatMap showMatches(Pattern compile patternString))
|
||||
|
||||
def last(key: Option[ScopedKey[_]], mgr: Streams, ref: ScopeAxis[ProjectRef]): Unit =
|
||||
printLines(lastLines(key, mgr, ref))
|
||||
def printLines(lines: Seq[String]) = lines foreach println
|
||||
def lastGrep(key: Option[ScopedKey[_]], mgr: Streams, patternString: String, ref: ScopeAxis[ProjectRef])
|
||||
{
|
||||
val pattern = Pattern.compile(patternString)
|
||||
printLines(lastLines(key, mgr, ref).flatMap(showMatches(pattern)) )
|
||||
}
|
||||
def lastLines(key: Option[ScopedKey[_]], mgr: Streams, ref: ScopeAxis[ProjectRef]): Seq[String] =
|
||||
lastLines(key getOrElse Project.globalLoggerKey(ref), mgr)
|
||||
def lastLines(key: ScopedKey[_], mgr: Streams): Seq[String] =
|
||||
mgr.use(key) { s => IO.readLines(s.readText( Project.fillTaskAxis(key) )) }
|
||||
def lastLines(key: ScopedKey[_], mgr: Streams): Seq[String] = mgr.use(key) { s => IO.readLines(s.readText( Project.fillTaskAxis(key) )) }
|
||||
def tailLines(file: File, tailDelim: String): Seq[String] = headLines(IO.readLines(file).reverse, tailDelim).reverse
|
||||
@tailrec def headLines(lines: Seq[String], tailDelim: String): Seq[String] =
|
||||
if(lines.isEmpty)
|
||||
lines
|
||||
else
|
||||
{
|
||||
val (first, tail) = lines.span { line => ! (line startsWith tailDelim) }
|
||||
if(first.isEmpty) headLines(tail drop 1, tailDelim) else first
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package sbt
|
|||
import java.io.File
|
||||
import java.net.URI
|
||||
import Project._
|
||||
import Keys.{appConfiguration, stateBuildStructure, commands, configuration, historyPath, logged, projectCommand, sessionSettings, shellPrompt, streams, thisProject, thisProjectRef, watch}
|
||||
import Keys.{appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, streams, thisProject, thisProjectRef, watch}
|
||||
import Scope.{GlobalScope,ThisScope}
|
||||
import Load.BuildStructure
|
||||
import CommandSupport.logger
|
||||
|
|
@ -151,11 +151,10 @@ object Project extends Init[Scope] with ProjectExtra
|
|||
def getHooks(data: Settings[Scope]): (State => State, State => State) = (getHook(Keys.onLoad, data), getHook(Keys.onUnload, data))
|
||||
|
||||
def current(state: State): ProjectRef = session(state).current
|
||||
def updateCurrent(s0: State): State =
|
||||
def updateCurrent(s: State): State =
|
||||
{
|
||||
val structure = Project.structure(s0)
|
||||
val ref = Project.current(s0)
|
||||
val s = installGlobalLogger(s0, structure, ref)
|
||||
val structure = Project.structure(s)
|
||||
val ref = Project.current(s)
|
||||
val project = Load.getProject(structure.units, ref.build, ref.project)
|
||||
val label = Keys.name in ref get structure.data getOrElse ref.project
|
||||
logger(s).info("Set current project to " + label + " (in build " + ref.build +")")
|
||||
|
|
@ -314,13 +313,6 @@ object Project extends Init[Scope] with ProjectExtra
|
|||
val extracted = Project.extract(state)
|
||||
EvaluateTask.evaluateTask(extracted.structure, taskKey, state, extracted.currentRef, checkCycles, maxWorkers)
|
||||
}
|
||||
def globalLoggerKey(ref: ScopeAxis[ResolvedReference]) = fillTaskAxis(ScopedKey(GlobalScope.copy(project = ref), streams.key))
|
||||
def installGlobalLogger(s: State, structure: BuildStructure, ref: ProjectRef): State =
|
||||
{
|
||||
val str = structure.streams(globalLoggerKey(Select(ref)))
|
||||
str.open()
|
||||
s.put(logged, str.log).addExitHook { str.close() }
|
||||
}
|
||||
// this is here instead of Scoped so that it is considered without need for import (because of Project.Initialize)
|
||||
implicit def richInitializeTask[T](init: Initialize[Task[T]]): Scoped.RichInitializeTask[T] = new Scoped.RichInitializeTask(init)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue