From 050f9db5014086560796ea742056e2dd18899305 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 4 Sep 2010 08:19:58 -0400 Subject: [PATCH] rework ConsoleLogger can send output to a PrintWriter control over color, still need custom formatter replace IvyLogger with normal Logger --- ivy/ComponentManager.scala | 2 +- ivy/Ivy.scala | 4 +-- ivy/IvyCache.scala | 12 +++---- ivy/IvyConfigurations.scala | 10 +++--- ivy/IvyLogger.scala | 11 +------ main/AggressiveCompiler.scala | 2 +- main/CommandSupport.scala | 2 +- main/build/Build.scala | 2 +- util/log/ConsoleLogger.scala | 62 ++++++++++++++++++++++++++--------- util/log/Level.scala | 2 ++ util/log/MultiLogger.scala | 3 +- 11 files changed, 68 insertions(+), 44 deletions(-) diff --git a/ivy/ComponentManager.scala b/ivy/ComponentManager.scala index 6e6e3d95e..bb1047b3d 100644 --- a/ivy/ComponentManager.scala +++ b/ivy/ComponentManager.scala @@ -15,7 +15,7 @@ import java.util.concurrent.Callable * This is used for compiled source jars so that the compilation need not be repeated for other projects on the same * machine. */ -class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentProvider, val log: IvyLogger) extends NotNull +class ComponentManager(globalLock: xsbti.GlobalLock, provider: xsbti.ComponentProvider, val log: Logger) extends NotNull { /** Get all of the files for component 'id', throwing an exception if no files exist for the component. */ def files(id: String)(ifMissing: IfMissing): Iterable[File] = diff --git a/ivy/Ivy.scala b/ivy/Ivy.scala index e0d880a94..1b6866d84 100644 --- a/ivy/Ivy.scala +++ b/ivy/Ivy.scala @@ -171,7 +171,7 @@ private object IvySbt /** Sets the resolvers for 'settings' to 'resolvers'. This is done by creating a new chain and making it the default. * 'other' is for resolvers that should be in a different chain. These are typically used for publishing or other actions. */ - private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], localOnly: Boolean, log: IvyLogger) + private def setResolvers(settings: IvySettings, resolvers: Seq[Resolver], other: Seq[Resolver], localOnly: Boolean, log: Logger) { def makeChain(label: String, name: String, rs: Seq[Resolver]) = { log.debug(label + " repositories:") @@ -183,7 +183,7 @@ private object IvySbt val mainChain = makeChain("Default", "sbt-chain", resolvers) settings.setDefaultResolver(mainChain.getName) } - private def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, log: IvyLogger): ChainResolver = + private def resolverChain(name: String, resolvers: Seq[Resolver], localOnly: Boolean, log: Logger): ChainResolver = { val newDefault = new ChainResolver newDefault.setName(name) diff --git a/ivy/IvyCache.scala b/ivy/IvyCache.scala index 43b714231..072be22a0 100644 --- a/ivy/IvyCache.scala +++ b/ivy/IvyCache.scala @@ -32,7 +32,7 @@ object IvyCache { def lockFile = new File(System.getProperty("user.home"), ".sbt.cache.lock") /** Caches the given 'file' with the given ID. It may be retrieved or cleared using this ID.*/ - def cacheJar(moduleID: ModuleID, file: File, lock: Option[xsbti.GlobalLock], log: IvyLogger) + def cacheJar(moduleID: ModuleID, file: File, lock: Option[xsbti.GlobalLock], log: Logger) { val artifact = defaultArtifact(moduleID) val resolved = new ResolvedResource(new FileResource(new IvyFileRepository, file), moduleID.revision) @@ -42,13 +42,13 @@ object IvyCache } } /** Clears the cache of the jar for the given ID.*/ - def clearCachedJar(id: ModuleID, lock: Option[xsbti.GlobalLock], log: IvyLogger) + def clearCachedJar(id: ModuleID, lock: Option[xsbti.GlobalLock], log: Logger) { try { withCachedJar(id, lock, log)(_.delete) } catch { case e: Exception => log.debug("Error cleaning cached jar: " + e.toString) } } /** Copies the cached jar for the given ID to the directory 'toDirectory'. If the jar is not in the cache, NotInCache is thrown.*/ - def retrieveCachedJar(id: ModuleID, toDirectory: File, lock: Option[xsbti.GlobalLock], log: IvyLogger) = + def retrieveCachedJar(id: ModuleID, toDirectory: File, lock: Option[xsbti.GlobalLock], log: Logger) = withCachedJar(id, lock, log) { cachedFile => val copyTo = new File(toDirectory, cachedFile.getName) FileUtil.copy(cachedFile, copyTo, null) @@ -56,7 +56,7 @@ object IvyCache } /** Get the location of the cached jar for the given ID in the Ivy cache. If the jar is not in the cache, NotInCache is thrown .*/ - def withCachedJar[T](id: ModuleID, lock: Option[xsbti.GlobalLock], log: IvyLogger)(f: File => T): T = + def withCachedJar[T](id: ModuleID, lock: Option[xsbti.GlobalLock], log: Logger)(f: File => T): T = { val cachedFile = try @@ -71,7 +71,7 @@ object IvyCache if(cachedFile.exists) f(cachedFile) else throw new NotInCache(id) } /** Calls the given function with the default Ivy cache.*/ - def withDefaultCache[T](lock: Option[xsbti.GlobalLock], log: IvyLogger)(f: DefaultRepositoryCacheManager => T): T = + def withDefaultCache[T](lock: Option[xsbti.GlobalLock], log: Logger)(f: DefaultRepositoryCacheManager => T): T = { val (ivy, local) = basicLocalIvy(lock, log) ivy.withIvy { ivy => @@ -82,7 +82,7 @@ object IvyCache } private def unknownOrigin(artifact: IvyArtifact) = ArtifactOrigin.unkwnown(artifact) /** A minimal Ivy setup with only a local resolver and the current directory as the base directory.*/ - private def basicLocalIvy(lock: Option[xsbti.GlobalLock], log: IvyLogger) = + private def basicLocalIvy(lock: Option[xsbti.GlobalLock], log: Logger) = { val local = Resolver.defaultLocal(None) val paths = new IvyPaths(new File("."), None) diff --git a/ivy/IvyConfigurations.scala b/ivy/IvyConfigurations.scala index c047f1dda..7a7531b13 100644 --- a/ivy/IvyConfigurations.scala +++ b/ivy/IvyConfigurations.scala @@ -15,19 +15,19 @@ sealed trait IvyConfiguration extends NotNull type This <: IvyConfiguration def lock: Option[xsbti.GlobalLock] def baseDirectory: File - def log: IvyLogger + def log: Logger def withBase(newBaseDirectory: File): This } final class InlineIvyConfiguration(val paths: IvyPaths, val resolvers: Seq[Resolver], val otherResolvers: Seq[Resolver], val moduleConfigurations: Seq[ModuleConfiguration], val localOnly: Boolean, val lock: Option[xsbti.GlobalLock], - val log: IvyLogger) extends IvyConfiguration + val log: Logger) extends IvyConfiguration { type This = InlineIvyConfiguration def baseDirectory = paths.baseDirectory def withBase(newBase: File) = new InlineIvyConfiguration(paths.withBase(newBase), resolvers, otherResolvers, moduleConfigurations, localOnly, lock, log) def changeResolvers(newResolvers: Seq[Resolver]) = new InlineIvyConfiguration(paths, newResolvers, otherResolvers, moduleConfigurations, localOnly, lock, log) } -final class ExternalIvyConfiguration(val baseDirectory: File, val file: File, val lock: Option[xsbti.GlobalLock], val log: IvyLogger) extends IvyConfiguration +final class ExternalIvyConfiguration(val baseDirectory: File, val file: File, val lock: Option[xsbti.GlobalLock], val log: Logger) extends IvyConfiguration { type This = ExternalIvyConfiguration def withBase(newBase: File) = new ExternalIvyConfiguration(newBase, file, lock, log) @@ -37,7 +37,7 @@ object IvyConfiguration { /** Called to configure Ivy when inline resolvers are not specified. * This will configure Ivy with an 'ivy-settings.xml' file if there is one or else use default resolvers.*/ - def apply(paths: IvyPaths, lock: Option[xsbti.GlobalLock], localOnly: Boolean, log: IvyLogger): IvyConfiguration = + def apply(paths: IvyPaths, lock: Option[xsbti.GlobalLock], localOnly: Boolean, log: Logger): IvyConfiguration = { log.debug("Autodetecting configuration.") val defaultIvyConfigFile = IvySbt.defaultIvyConfiguration(paths.baseDirectory) @@ -93,7 +93,7 @@ object InlineConfiguration } object ModuleSettings { - def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: IvyLogger) = + def apply(ivyScala: Option[IvyScala], validate: Boolean, module: => ModuleID)(baseDirectory: File, log: Logger) = { log.debug("Autodetecting dependencies.") val defaultPOMFile = IvySbt.defaultPOM(baseDirectory) diff --git a/ivy/IvyLogger.scala b/ivy/IvyLogger.scala index 51f9afbb9..b06d477a2 100644 --- a/ivy/IvyLogger.scala +++ b/ivy/IvyLogger.scala @@ -5,17 +5,8 @@ package sbt import org.apache.ivy.util.{Message, MessageLogger, MessageLoggerEngine} -trait IvyLogger extends NotNull -{ - def info(msg: => String) - def debug(msg: => String) - def warn(msg: => String) - def error(msg: => String) - def verbose(msg: => String) -} - /** Interface to Ivy logging. */ -private final class IvyLoggerInterface(logger: IvyLogger) extends MessageLogger +private final class IvyLoggerInterface(logger: Logger) extends MessageLogger { def rawlog(msg: String, level: Int) = log(msg, level) def log(msg: String, level: Int) diff --git a/main/AggressiveCompiler.scala b/main/AggressiveCompiler.scala index 4efbc3100..89a272900 100644 --- a/main/AggressiveCompiler.scala +++ b/main/AggressiveCompiler.scala @@ -31,7 +31,7 @@ class AggressiveCompiler extends xsbti.AppMain val classpath = outputDirectory +++ (cwd * "*.jar") +++(cwd * (-"project")).descendentsExcept( "*.jar", "project" || HiddenFileFilter) val cacheDirectory = target / "cache" val options = args.tail.toSeq - val log = new ConsoleLogger with Logger with sbt.IvyLogger + val log = ConsoleLogger() val componentManager = new ComponentManager(launcher.globalLock, app.components, log) val compiler = new AnalyzingCompiler(ScalaInstance(args.head, launcher), componentManager, log) val javac = JavaCompiler.directOrFork(compiler.cp, compiler.scalaInstance)( (args: Seq[String], log: Logger) => Process("javac", args) ! log ) diff --git a/main/CommandSupport.scala b/main/CommandSupport.scala index 46586eaa5..1ff871e97 100644 --- a/main/CommandSupport.scala +++ b/main/CommandSupport.scala @@ -13,7 +13,7 @@ object CommandSupport { def logger(s: State) = s match { case State(p: Logged) => p.log - case _ => new ConsoleLogger //TODO: add a default logger to State + case _ => ConsoleLogger() //TODO: add a default logger to State } def notReadable(files: Seq[File]): Seq[File] = files filter { !_.canRead } def readable(files: Seq[File]): Seq[File] = files filter { _.canRead } diff --git a/main/build/Build.scala b/main/build/Build.scala index ff089a6d0..8cceed1db 100644 --- a/main/build/Build.scala +++ b/main/build/Build.scala @@ -60,7 +60,7 @@ object Build { import conf._ // TODO: accept Logger as an argument - val log = new ConsoleLogger with Logger with sbt.IvyLogger + val log = ConsoleLogger() val componentManager = new ComponentManager(launcher.globalLock, configuration.provider.components, log) val compiler = new AnalyzingCompiler(instance, componentManager, log) diff --git a/util/log/ConsoleLogger.scala b/util/log/ConsoleLogger.scala index 49db31b66..59bc680c3 100644 --- a/util/log/ConsoleLogger.scala +++ b/util/log/ConsoleLogger.scala @@ -3,8 +3,24 @@ */ package sbt + import java.io.{PrintStream, PrintWriter} + object ConsoleLogger { + def systemOut: ConsoleOut = printStreamOut(System.out) + def printStreamOut(out: PrintStream): ConsoleOut = new ConsoleOut { + val lockObject = out + def print(s: String) = out.print(s) + def println(s: String) = out.println(s) + def println() = out.println() + } + def printWriterOut(out: PrintWriter): ConsoleOut = new ConsoleOut { + val lockObject = out + def print(s: String) = out.print(s) + def println(s: String) = out.println(s) + def println() = out.println() + } + private val formatEnabled = ansiSupported && !formatExplicitlyDisabled private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat") @@ -14,15 +30,20 @@ object ConsoleLogger private[this] def os = System.getProperty("os.name") private[this] def isWindows = os.toLowerCase.indexOf("windows") >= 0 + + def apply(): ConsoleLogger = apply(systemOut) + def apply(out: PrintStream): ConsoleLogger = apply(printStreamOut(out)) + def apply(out: PrintWriter): ConsoleLogger = apply(printWriterOut(out)) + def apply(out: ConsoleOut, ansiCodesSupported: Boolean = formatEnabled, useColor: Boolean = true): ConsoleLogger = + new ConsoleLogger(out, ansiCodesSupported, useColor) } /** A logger that logs to the console. On supported systems, the level labels are * colored. * * This logger is not thread-safe.*/ -class ConsoleLogger extends BasicLogger +class ConsoleLogger private[ConsoleLogger](val out: ConsoleOut, override val ansiCodesSupported: Boolean, val useColor: Boolean) extends BasicLogger { - override def ansiCodesSupported = ConsoleLogger.formatEnabled def messageColor(level: Level.Value) = Console.RESET def labelColor(level: Level.Value) = level match @@ -39,41 +60,50 @@ class ConsoleLogger extends BasicLogger log(successLabelColor, Level.SuccessLabel, successMessageColor, message) } def trace(t: => Throwable): Unit = - System.out.synchronized + out.lockObject.synchronized { val traceLevel = getTrace if(traceLevel >= 0) - System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) } + out.print(StackTrace.trimmed(t, traceLevel)) } def log(level: Level.Value, message: => String) { if(atLevel(level)) log(labelColor(level), level.toString, messageColor(level), message) } + private def reset(): Unit = setColor(Console.RESET) + private def setColor(color: String) { - if(ansiCodesSupported) - System.out.synchronized { System.out.print(color) } + if(ansiCodesSupported && useColor) + out.lockObject.synchronized { out.print(color) } } private def log(labelColor: String, label: String, messageColor: String, message: String): Unit = - System.out.synchronized + out.lockObject.synchronized { for(line <- message.split("""\n""")) { - setColor(Console.RESET) - System.out.print('[') + reset() + out.print("[") setColor(labelColor) - System.out.print(label) - setColor(Console.RESET) - System.out.print("] ") + out.print(label) + reset() + out.print("] ") setColor(messageColor) - System.out.print(line) - setColor(Console.RESET) - System.out.println() + out.print(line) + reset() + out.println() } } - def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) } + def logAll(events: Seq[LogEvent]) = out.lockObject.synchronized { events.foreach(log) } def control(event: ControlEvent.Value, message: => String) { log(labelColor(Level.Info), Level.Info.toString, Console.BLUE, message) } +} +sealed trait ConsoleOut +{ + val lockObject: AnyRef + def print(s: String): Unit + def println(s: String): Unit + def println(): Unit } \ No newline at end of file diff --git a/util/log/Level.scala b/util/log/Level.scala index ad4e51759..bc1156729 100644 --- a/util/log/Level.scala +++ b/util/log/Level.scala @@ -16,6 +16,8 @@ object Level extends Enumeration * label is also defined here. */ val SuccessLabel = "success" + def union(a: Value, b: Value) = if(a.id < b.id) a else b + /** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */ def apply(s: String) = values.find(s == _.toString) /** Same as apply, defined for use in pattern matching. */ diff --git a/util/log/MultiLogger.scala b/util/log/MultiLogger.scala index 800d170ed..525e3ef9d 100644 --- a/util/log/MultiLogger.scala +++ b/util/log/MultiLogger.scala @@ -4,7 +4,8 @@ */ package sbt - +// note that setting the logging level on this logger has no effect on its behavior, only +// on the behavior of the delegates. class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger { override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported)