From 9e9f587be2d921c31adf991035bd99ebb757dea3 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 7 Jun 2010 10:50:51 -0400 Subject: [PATCH 1/4] cache updates --- cache/Cache.scala | 10 +- cache/FileInfo.scala | 15 +- cache/src/test/scala/CacheTest.scala | 21 +-- cache/tracking/Tracked.scala | 209 ++++++++++++++++----------- 4 files changed, 146 insertions(+), 109 deletions(-) diff --git a/cache/Cache.scala b/cache/Cache.scala index 2f35d2c96..e7ba310dc 100644 --- a/cache/Cache.scala +++ b/cache/Cache.scala @@ -2,7 +2,6 @@ package xsbt import sbinary.{CollectionTypes, Format, JavaFormats} import java.io.File -import scala.reflect.Manifest trait Cache[I,O] { @@ -23,13 +22,6 @@ object Cache extends BasicCacheImplicits with SBinaryFormats with HListCacheImpl def wrapOutputCache[O,DO](implicit convert: O => DO, reverse: DO => O, base: OutputCache[DO]): OutputCache[O] = new WrappedOutputCache[O,DO](convert, reverse, base) - def apply[I,O](file: File)(f: I => Task[O])(implicit cache: Cache[I,O]): I => Task[O] = - in => - cache(file)(in) match - { - case Left(value) => Task(value) - case Right(store) => f(in) map { out => store(out); out } - } def cached[I,O](file: File)(f: I => O)(implicit cache: Cache[I,O]): I => O = in => cache(file)(in) match @@ -61,4 +53,4 @@ trait HListCacheImplicits extends HLists implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[HCons[H,T]] = new HConsOutputCache(headCache, tailCache) implicit lazy val hNilOutputCache: OutputCache[HNil] = new HNilOutputCache -} \ No newline at end of file +} diff --git a/cache/FileInfo.scala b/cache/FileInfo.scala index 66c8c496b..d1b350fa8 100644 --- a/cache/FileInfo.scala +++ b/cache/FileInfo.scala @@ -18,8 +18,10 @@ sealed trait ModifiedFileInfo extends FileInfo { val lastModified: Long } +sealed trait PlainFileInfo extends FileInfo sealed trait HashModifiedFileInfo extends HashFileInfo with ModifiedFileInfo +private final case class PlainFile(file: File) extends PlainFileInfo private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo @@ -32,8 +34,6 @@ object FileInfo implicit def apply(file: File): F implicit def unapply(info: F): File = info.file implicit val format: Format[F] - /*val manifest: Manifest[F] - def formatManifest: Manifest[Format[F]] = CacheIO.manifest[Format[F]]*/ import Cache._ implicit def infoInputCache: InputCache[File] = wrapInputCache[File,F] implicit def infoOutputCache: OutputCache[File] = wrapOutputCache[File,F] @@ -41,7 +41,6 @@ object FileInfo object full extends Style { type F = HashModifiedFileInfo - //val manifest: Manifest[F] = CacheIO.manifest[HashModifiedFileInfo] implicit def apply(file: File): HashModifiedFileInfo = make(file, Hash(file).toList, file.lastModified) def make(file: File, hash: List[Byte], lastModified: Long): HashModifiedFileInfo = FileHashModified(file.getAbsoluteFile, hash, lastModified) implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), tupled(make _)) @@ -49,7 +48,6 @@ object FileInfo object hash extends Style { type F = HashFileInfo - //val manifest: Manifest[F] = CacheIO.manifest[HashFileInfo] implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList) def make(file: File, hash: List[Byte]): HashFileInfo = FileHash(file.getAbsoluteFile, hash) implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), tupled(make _)) @@ -58,11 +56,17 @@ object FileInfo object lastModified extends Style { type F = ModifiedFileInfo - //val manifest: Manifest[F] = CacheIO.manifest[ModifiedFileInfo] implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified) def make(file: File, lastModified: Long): ModifiedFileInfo = FileModified(file.getAbsoluteFile, lastModified) implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), tupled(make _)) } + object exists extends Style + { + type F = PlainFileInfo + implicit def apply(file: File): PlainFileInfo = make(file) + def make(file: File): PlainFileInfo = PlainFile(file.getAbsoluteFile) + implicit val format: Format[PlainFileInfo] = wrap(_.file, make) + } } final case class FilesInfo[F <: FileInfo] private(files: Set[F]) extends NotNull @@ -92,4 +96,5 @@ object FilesInfo lazy val full: Style = new BasicStyle(FileInfo.full) lazy val hash: Style = new BasicStyle(FileInfo.hash) lazy val lastModified: Style = new BasicStyle(FileInfo.lastModified) + lazy val exists: Style = new BasicStyle(FileInfo.exists) } \ No newline at end of file diff --git a/cache/src/test/scala/CacheTest.scala b/cache/src/test/scala/CacheTest.scala index 7bba6ec79..65703ecaa 100644 --- a/cache/src/test/scala/CacheTest.scala +++ b/cache/src/test/scala/CacheTest.scala @@ -7,22 +7,23 @@ object CacheTest// extends Properties("Cache test") val lengthCache = new File("/tmp/length-cache") val cCache = new File("/tmp/c-cache") - import Task._ import Cache._ import FileInfo.hash._ def test { - val createTask = Task { new File("test") } + lazy val create = new File("test") - val length = (f: File) => { println("File length: " + f.length); f.length } - val cachedLength = cached(lengthCache) ( length ) + val length = cached(lengthCache) { + (f: File) => { println("File length: " + f.length); f.length } + } - val lengthTask = createTask map cachedLength + lazy val fileLength = length(create) - val c = (file: File, len: Long) => { println("File: " + file + ", length: " + len); len :: file :: HNil } - val cTask = (createTask :: lengthTask :: TNil) map cached(cCache) { case (file :: len :: HNil) => c(file, len) } - - try { TaskRunner(cTask) } - catch { case TasksFailed(failures) => failures.foreach(_.exception.printStackTrace) } + val c = cached(cCache) { (in: (File :: Long :: HNil)) => + val file :: len :: HNil = in + println("File: " + file + " (" + file.exists + "), length: " + len) + (len+1) :: file :: HNil + } + c(create :: fileLength :: HNil) } } \ No newline at end of file diff --git a/cache/tracking/Tracked.scala b/cache/tracking/Tracked.scala index f79a2a7ee..49d33c622 100644 --- a/cache/tracking/Tracked.scala +++ b/cache/tracking/Tracked.scala @@ -7,112 +7,152 @@ import java.io.{File,IOException} import CacheIO.{fromFile, toFile} import sbinary.Format import scala.reflect.Manifest -import Task.{iterableToBuilder, iterableToForkBuilder} +import xsbt.FileUtilities.{delete, read, write} + +/* A proper implementation of fileTask that tracks inputs and outputs properly + +def fileTask(cacheBaseDirectory: Path)(inputs: PathFinder, outputs: PathFinder)(action: => Unit): Task = + fileTask(cacheBaseDirectory, FilesInfo.hash, FilesInfo.lastModified) +def fileTask(cacheBaseDirectory: Path, inStyle: FilesInfo.Style, outStyle: FilesInfo.Style)(inputs: PathFinder, outputs: PathFinder)(action: => Unit): Task = +{ + lazy val inCache = diffInputs(base / "in-cache", inStyle)(inputs) + lazy val outCache = diffOutputs(base / "out-cache", outStyle)(outputs) + task + { + inCache { inReport => + outCache { outReport => + if(inReport.modified.isEmpty && outReport.modified.isEmpty) () else action + } + } + } +} +*/ + +object Tracked +{ + /** Creates a tracker that provides the last time it was evaluated. + * If 'useStartTime' is true, the recorded time is the start of the evaluated function. + * If 'useStartTime' is false, the recorded time is when the evaluated function completes. + * In both cases, the timestamp is not updated if the function throws an exception.*/ + def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp = new Timestamp(cacheFile) + /** Creates a tracker that only evaluates a function when the input has changed.*/ + def changed[O](cacheFile: File)(getValue: => O)(implicit input: InputCache[O]): Changed[O] = + new Changed[O](getValue, cacheFile) + + /** Creates a tracker that provides the difference between the set of input files provided for successive invocations.*/ + def diffInputs(cache: File, style: FilesInfo.Style)(files: => Set[File]): Difference = + Difference.inputs(files, style, cache) + /** Creates a tracker that provides the difference between the set of output files provided for successive invocations.*/ + def diffOutputs(cache: File, style: FilesInfo.Style)(files: => Set[File]): Difference = + Difference.outputs(files, style, cache) +} trait Tracked extends NotNull { - /** Cleans outputs. This operation might require information from the cache, so it should be called first if clear is also called.*/ - def clean: Task[Unit] - /** Clears the cache. If also cleaning, 'clean' should be called first as it might require information from the cache.*/ - def clear: Task[Unit] + /** Cleans outputs and clears the cache.*/ + def clean: Unit } -class Timestamp(val cacheFile: File) extends Tracked +class Timestamp(val cacheFile: File, useStartTime: Boolean) extends Tracked { - val clean = Clean(cacheFile) - def clear = Task.empty - def apply[T](f: Long => Task[T]): Task[T] = + def clean = delete(cacheFile) + /** Reads the previous timestamp, evaluates the provided function, and then updates the timestamp.*/ + def apply[T](f: Long => T): T = { - val getTimestamp = Task { readTimestamp } - getTimestamp bind f map { result => - FileUtilities.write(cacheFile, System.currentTimeMillis.toString) - result - } + val start = now() + val result = f(readTimestamp) + write(cacheFile, (if(useStartTime) start else now()).toString) + result } + private def now() = System.currentTimeMillis def readTimestamp: Long = - try { FileUtilities.read(cacheFile).toLong } + try { read(cacheFile).toLong } catch { case _: NumberFormatException | _: java.io.FileNotFoundException => 0 } } -object Clean -{ - def apply(src: Task[Set[File]]): Task[Unit] = src map FileUtilities.delete - def apply(srcs: File*): Task[Unit] = Task(FileUtilities.delete(srcs)) - def apply(srcs: Set[File]): Task[Unit] = Task(FileUtilities.delete(srcs)) -} -class Changed[O](val task: Task[O], val cacheFile: File)(implicit input: InputCache[O]) extends Tracked +class Changed[O](getValue: => O, val cacheFile: File)(implicit input: InputCache[O]) extends Tracked { - val clean = Clean(cacheFile) - def clear = Task.empty - def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): Task[O2] = - task map { value => - val cache = - try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) } - catch { case _: IOException => new ForceResult(input)(value) } - if(cache.uptodate) - ifUnchanged(value) - else - { - OpenResource.fileOutputStream(false)(cacheFile)(cache.update) - ifChanged(value) - } + def clean = delete(cacheFile) + def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O2 = + { + val value = getValue + val cache = + try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) } + catch { case _: IOException => new ForceResult(input)(value) } + if(cache.uptodate) + ifUnchanged(value) + else + { + OpenResource.fileOutputStream(false)(cacheFile)(cache.update) + ifChanged(value) } + } } object Difference { sealed class Constructor private[Difference](defineClean: Boolean, filesAreOutputs: Boolean) extends NotNull { - def apply(filesTask: Task[Set[File]], style: FilesInfo.Style, cache: File): Difference = new Difference(filesTask, style, cache, defineClean, filesAreOutputs) - def apply(files: Set[File], style: FilesInfo.Style, cache: File): Difference = apply(Task(files), style, cache) + def apply(files: => Set[File], style: FilesInfo.Style, cache: File): Difference = new Difference(files, style, cache, defineClean, filesAreOutputs) } + /** Provides a constructor for a Difference that removes the files from the previous run on a call to 'clean' and saves the + * hash/last modified time of the files as they are after running the function. This means that this information must be evaluated twice: + * before and after running the function.*/ object outputs extends Constructor(true, true) + /** Provides a constructor for a Difference that does nothing on a call to 'clean' and saves the + * hash/last modified time of the files as they were prior to running the function.*/ object inputs extends Constructor(false, false) } -class Difference(val filesTask: Task[Set[File]], val style: FilesInfo.Style, val cache: File, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked +class Difference(getFiles: => Set[File], val style: FilesInfo.Style, val cache: File, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked { - val clean = if(defineClean) Clean(Task(raw(cachedFilesInfo))) else Task.empty - val clear = Clean(cache) + def clean = + { + if(defineClean) delete(raw(cachedFilesInfo)) else () + clearCache() + } + private def clearCache = delete(cache) private def cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file) - def apply[T](f: ChangeReport[File] => Task[T]): Task[T] = - filesTask bind { files => - val lastFilesInfo = cachedFilesInfo - val lastFiles = raw(lastFilesInfo) - val currentFiles = files.map(_.getAbsoluteFile) - val currentFilesInfo = style(currentFiles) + def apply[T](f: ChangeReport[File] => T): T = + { + val files = getFiles + val lastFilesInfo = cachedFilesInfo + val lastFiles = raw(lastFilesInfo) + val currentFiles = files.map(_.getAbsoluteFile) + val currentFilesInfo = style(currentFiles) - val report = new ChangeReport[File] - { - lazy val checked = currentFiles - lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist. - lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist. - lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added - lazy val unmodified = checked -- modified - } - - f(report) map { result => - val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo - toFile(style.formats)(info)(cache)(style.manifest) - result - } + val report = new ChangeReport[File] + { + lazy val checked = currentFiles + lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist. + lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist. + lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added + lazy val unmodified = checked -- modified } + + val result = f(report) + val info = if(filesAreOutputs) style(currentFiles) else currentFilesInfo + toFile(style.formats)(info)(cache)(style.manifest) + result + } } class DependencyTracked[T](val cacheDirectory: File, val translateProducts: Boolean, cleanT: T => Unit)(implicit format: Format[T], mf: Manifest[T]) extends Tracked { private val trackFormat = new TrackingFormat[T](cacheDirectory, translateProducts) private def cleanAll(fs: Set[T]) = fs.foreach(cleanT) - val clean = Task(cleanAll(trackFormat.read.allProducts)) - val clear = Clean(cacheDirectory) + def clean = + { + cleanAll(trackFormat.read.allProducts) + delete(cacheDirectory) + } - def apply[R](f: UpdateTracking[T] => Task[R]): Task[R] = + def apply[R](f: UpdateTracking[T] => R): R = { val tracker = trackFormat.read - f(tracker) map { result => - trackFormat.write(tracker) - result - } + val result = f(tracker) + trackFormat.write(tracker) + result } } object InvalidateFiles @@ -179,30 +219,29 @@ class InvalidateTransitive[T](cacheDirectory: File, translateProducts: Boolean, this(cacheDirectory, translateProducts, (_: T) => ()) private val tracked = new DependencyTracked(cacheDirectory, translateProducts, cleanT) - def clean = tracked.clean - def clear = tracked.clear - - def apply[R](changes: ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => Task[R]): Task[R] = - apply(Task(changes))(f) - def apply[R](changesTask: Task[ChangeReport[T]])(f: (InvalidationReport[T], UpdateTracking[T]) => Task[R]): Task[R] = + def clean { - changesTask bind { changes => - tracked { tracker => - val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified) - f(report, tracker) - } + tracked.clean + tracked.clear + } + + def apply[R](getChanges: => ChangeReport[T])(f: (InvalidationReport[T], UpdateTracking[T]) => R): R = + { + val changes = getChanges + tracked { tracker => + val report = InvalidateTransitive.andClean[T](tracker, _.foreach(cleanT), changes.modified) + f(report, tracker) } } } -class BasicTracked(filesTask: Task[Set[File]], style: FilesInfo.Style, cacheDirectory: File) extends Tracked +class BasicTracked(files: => Set[File], style: FilesInfo.Style, cacheDirectory: File) extends Tracked { - private val changed = Difference.inputs(filesTask, style, new File(cacheDirectory, "files")) + private val changed = Difference.inputs(files, style, new File(cacheDirectory, "files")) private val invalidation = InvalidateFiles(new File(cacheDirectory, "invalidation")) - private def onTracked(f: Tracked => Task[Unit]) = Seq(invalidation, changed).forkTasks(f).joinIgnore - val clear = onTracked(_.clear) - val clean = onTracked(_.clean) + private def onTracked(f: Tracked => Unit) = { f(invalidation); f(changed) } + def clean = onTracked(_.clean) - def apply[R](f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => Task[R]): Task[R] = + def apply[R](f: (ChangeReport[File], InvalidationReport[File], UpdateTracking[File]) => R): R = changed { sourceChanges => invalidation(sourceChanges) { (report, tracking) => f(sourceChanges, report, tracking) From 93492a011ce051248e2f42d91bfe851d11ef504f Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 10 Jun 2010 08:14:50 -0400 Subject: [PATCH 2/4] conversions --- util/collection/HList.scala | 6 ++++++ util/collection/MList.scala | 9 ++++++++- util/collection/TypeFunctions.scala | 7 +++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/util/collection/HList.scala b/util/collection/HList.scala index a475c1194..a1e595eeb 100644 --- a/util/collection/HList.scala +++ b/util/collection/HList.scala @@ -25,4 +25,10 @@ final case class HCons[H, T <: HList](head : H, tail : T) extends HList type Up = MCons[H, tail.Up, Id] def up = MCons[H,tail.Up, Id](head, tail.up) def :+: [G](g: G): G :+: H :+: T = HCons(g, this) +} + +object HList +{ + // contains no type information: not even A + implicit def fromList[A](list: Traversable[A]): HList = ((HNil: HList) /: list) ( (hl,v) => HCons(v, hl) ) } \ No newline at end of file diff --git a/util/collection/MList.scala b/util/collection/MList.scala index b350858c3..7adfc1568 100644 --- a/util/collection/MList.scala +++ b/util/collection/MList.scala @@ -41,4 +41,11 @@ sealed class MNil extends MList[Nothing] def toList = Nil } -object MNil extends MNil \ No newline at end of file +object MNil extends MNil + + +object MList +{ + implicit def fromTCList[A[_]](list: Traversable[A[_]]): MList[A] = ((MNil: MList[A]) /: list) ( (hl,v) => MCons(v, hl) ) + implicit def fromList[A](list: Traversable[A]): MList[Const[A]#Apply] = ((MNil: MList[Const[A]#Apply]) /: list) ( (hl,v) => MCons[A, hl.type, Const[A]#Apply](v, hl) ) +} \ No newline at end of file diff --git a/util/collection/TypeFunctions.scala b/util/collection/TypeFunctions.scala index 00a6fc772..942a00d72 100644 --- a/util/collection/TypeFunctions.scala +++ b/util/collection/TypeFunctions.scala @@ -13,6 +13,10 @@ trait TypeFunctions final val left = new (Id ~> P1of2[Left, Nothing]#Flip) { def apply[T](t: T) = Left(t) } final val right = new (Id ~> P1of2[Right, Nothing]#Apply) { def apply[T](t: T) = Right(t) } final val some = new (Id ~> Some) { def apply[T](t: T) = Some(t) } + + implicit def toFn1[A,B](f: A => B): Fn1[A,B] = new Fn1[A,B] { + def ∙[C](g: C => A) = f compose g + } } object TypeFunctions extends TypeFunctions @@ -29,4 +33,7 @@ object ~> import TypeFunctions._ val Id: Id ~> Id = new (Id ~> Id) { def apply[T](a: T): T = a } implicit def tcIdEquals: (Id ~> Id) = Id +} +trait Fn1[A, B] { + def ∙[C](g: C => A): C => B } \ No newline at end of file From 1584f01de8088f529eeee0eff68572c8bcebab31 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 10 Jun 2010 21:25:37 -0400 Subject: [PATCH 3/4] wideConvert lets the serious errors pass through, use it in Execute --- util/control/ErrorHandling.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/util/control/ErrorHandling.scala b/util/control/ErrorHandling.scala index 98c1b1a73..f4d42993b 100644 --- a/util/control/ErrorHandling.scala +++ b/util/control/ErrorHandling.scala @@ -7,10 +7,17 @@ object ErrorHandling { def translate[T](msg: => String)(f: => T) = try { f } - catch { case e => throw new TranslatedException(msg + e.toString, e) } + catch { case e: Exception => throw new TranslatedException(msg + e.toString, e) } + def wideConvert[T](f: => T): Either[Throwable, T] = try { Right(f) } - catch { case e => Left(e) } // TODO: restrict type of e + catch + { + case ex @ (_: Exception | _: StackOverflowError) => Left(ex) + case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err + case x => Left(x) + } + def convert[T](f: => T): Either[Exception, T] = try { Right(f) } catch { case e: Exception => Left(e) } From e02adb06943678a51b5cc98967ac0642801f951b Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 10 Jun 2010 21:26:27 -0400 Subject: [PATCH 4/4] first round of logger cleanup/migration --- util/log/BasicLogger.scala | 14 ++++++------ util/log/BufferedLogger.scala | 15 +++++++------ util/log/ConsoleLogger.scala | 17 +++++++++------ util/log/FilterLogger.scala | 35 ++++++++++++++++++++++++++++++ util/log/Level.scala | 10 ++++----- util/log/LogEvent.scala | 4 ++-- util/log/Logger.scala | 14 +++++++----- util/log/LoggerWriter.scala | 40 +++++++++++++++++++++++++++++++++++ util/log/MultiLogger.scala | 27 +++++++++++++++++++++++ 9 files changed, 142 insertions(+), 34 deletions(-) create mode 100644 util/log/FilterLogger.scala create mode 100644 util/log/LoggerWriter.scala create mode 100644 util/log/MultiLogger.scala diff --git a/util/log/BasicLogger.scala b/util/log/BasicLogger.scala index 23607c8ed..a52d3b433 100644 --- a/util/log/BasicLogger.scala +++ b/util/log/BasicLogger.scala @@ -1,15 +1,15 @@ /* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah + * Copyright 2008, 2009, 2010 Mark Harrah */ - package xsbt +package sbt /** Implements the level-setting methods of Logger.*/ -abstract class BasicLogger extends Logger +abstract class BasicLogger extends AbstractLogger { - private var traceEnabledVar = true + private var traceEnabledVar = java.lang.Integer.MAX_VALUE private var level: Level.Value = Level.Info def getLevel = level def setLevel(newLevel: Level.Value) { level = newLevel } - def enableTrace(flag: Boolean) { traceEnabledVar = flag } - def traceEnabled = traceEnabledVar -} + def setTrace(level: Int) { traceEnabledVar = level } + def getTrace = traceEnabledVar +} \ No newline at end of file diff --git a/util/log/BufferedLogger.scala b/util/log/BufferedLogger.scala index 7e8348f2d..1a72e022d 100644 --- a/util/log/BufferedLogger.scala +++ b/util/log/BufferedLogger.scala @@ -1,8 +1,9 @@ /* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah + * Copyright 2008, 2009, 2010 Mark Harrah */ package xsbt + import sbt.{AbstractLogger, ControlEvent, Level, Log, LogEvent, SetLevel, SetTrace, Success, Trace} import scala.collection.mutable.ListBuffer /** A logger that can buffer the logging done on it and then can flush the buffer @@ -13,7 +14,7 @@ * * This class assumes that it is the only client of the delegate logger. * */ -class BufferedLogger(delegate: Logger) extends Logger +class BufferedLogger(delegate: AbstractLogger) extends AbstractLogger { private[this] val buffer = new ListBuffer[LogEvent] private[this] var recording = false @@ -54,10 +55,10 @@ class BufferedLogger(delegate: Logger) extends Logger } def getLevel = delegate.getLevel def traceEnabled = delegate.traceEnabled - def enableTrace(flag: Boolean) + def setTrace(level: Int) { - buffer += new SetTrace(flag) - delegate.enableTrace(flag) + buffer += new SetTrace(level) + delegate.setTrace(level) } def trace(t: => Throwable): Unit = @@ -73,9 +74,9 @@ class BufferedLogger(delegate: Logger) extends Logger delegate.logAll(events) def control(event: ControlEvent.Value, message: => String): Unit = doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message)) - private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit = + private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit = doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered) - private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: Logger => Unit): Unit = + private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit = if(condition) { if(recording) diff --git a/util/log/ConsoleLogger.scala b/util/log/ConsoleLogger.scala index a7217f026..49db31b66 100644 --- a/util/log/ConsoleLogger.scala +++ b/util/log/ConsoleLogger.scala @@ -1,7 +1,7 @@ /* sbt -- Simple Build Tool - * Copyright 2008, 2009 Mark Harrah + * Copyright 2008, 2009, 2010 Mark Harrah */ - package xsbt +package sbt object ConsoleLogger { @@ -17,10 +17,12 @@ object ConsoleLogger } /** A logger that logs to the console. On supported systems, the level labels are -* colored. */ +* colored. +* +* This logger is not thread-safe.*/ class ConsoleLogger extends BasicLogger { - import ConsoleLogger.formatEnabled + override def ansiCodesSupported = ConsoleLogger.formatEnabled def messageColor(level: Level.Value) = Console.RESET def labelColor(level: Level.Value) = level match @@ -39,8 +41,9 @@ class ConsoleLogger extends BasicLogger def trace(t: => Throwable): Unit = System.out.synchronized { - if(traceEnabled) - t.printStackTrace + val traceLevel = getTrace + if(traceLevel >= 0) + System.out.synchronized { System.out.print(StackTrace.trimmed(t, traceLevel)) } } def log(level: Level.Value, message: => String) { @@ -49,7 +52,7 @@ class ConsoleLogger extends BasicLogger } private def setColor(color: String) { - if(formatEnabled) + if(ansiCodesSupported) System.out.synchronized { System.out.print(color) } } private def log(labelColor: String, label: String, messageColor: String, message: String): Unit = diff --git a/util/log/FilterLogger.scala b/util/log/FilterLogger.scala new file mode 100644 index 000000000..152f6bdd5 --- /dev/null +++ b/util/log/FilterLogger.scala @@ -0,0 +1,35 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009, 2010 Mark Harrah + */ +package sbt + +/** A filter logger is used to delegate messages but not the logging level to another logger. This means +* that messages are logged at the higher of the two levels set by this logger and its delegate. +* */ +class FilterLogger(delegate: AbstractLogger) extends BasicLogger +{ + override lazy val ansiCodesSupported = delegate.ansiCodesSupported + def trace(t: => Throwable) + { + if(traceEnabled) + delegate.trace(t) + } + override def setTrace(level: Int) { delegate.setTrace(level) } + override def getTrace = delegate.getTrace + def log(level: Level.Value, message: => String) + { + if(atLevel(level)) + delegate.log(level, message) + } + def success(message: => String) + { + if(atLevel(Level.Info)) + delegate.success(message) + } + def control(event: ControlEvent.Value, message: => String) + { + if(atLevel(Level.Info)) + delegate.control(event, message) + } + def logAll(events: Seq[LogEvent]): Unit = delegate.logAll(events) +} diff --git a/util/log/Level.scala b/util/log/Level.scala index 86abc257d..ad4e51759 100644 --- a/util/log/Level.scala +++ b/util/log/Level.scala @@ -1,11 +1,11 @@ /* sbt -- Simple Build Tool * Copyright 2008, 2009 Mark Harrah */ - package xsbt + package sbt /** An enumeration defining the levels available for logging. A level includes all of the levels * with id larger than its own id. For example, Warn (id=3) includes Error (id=4).*/ -object Level extends Enumeration with NotNull +object Level extends Enumeration { val Debug = Value(1, "debug") val Info = Value(2, "info") @@ -16,10 +16,8 @@ object Level extends Enumeration with NotNull * label is also defined here. */ val SuccessLabel = "success" - // added because elements was renamed to iterator in 2.8.0 nightly - def levels = Debug :: Info :: Warn :: Error :: Nil /** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */ - def apply(s: String) = levels.find(s == _.toString) + def apply(s: String) = values.find(s == _.toString) /** Same as apply, defined for use in pattern matching. */ - private[xsbt] def unapply(s: String) = apply(s) + private[sbt] def unapply(s: String) = apply(s) } \ No newline at end of file diff --git a/util/log/LogEvent.scala b/util/log/LogEvent.scala index 19a5b24db..ffe6049d7 100644 --- a/util/log/LogEvent.scala +++ b/util/log/LogEvent.scala @@ -1,14 +1,14 @@ /* sbt -- Simple Build Tool * Copyright 2008, 2009 Mark Harrah */ - package xsbt + package sbt sealed trait LogEvent extends NotNull final class Success(val msg: String) extends LogEvent final class Log(val level: Level.Value, val msg: String) extends LogEvent final class Trace(val exception: Throwable) extends LogEvent final class SetLevel(val newLevel: Level.Value) extends LogEvent -final class SetTrace(val enabled: Boolean) extends LogEvent +final class SetTrace(val level: Int) extends LogEvent final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent object ControlEvent extends Enumeration diff --git a/util/log/Logger.scala b/util/log/Logger.scala index 153596f6f..1be353c4b 100644 --- a/util/log/Logger.scala +++ b/util/log/Logger.scala @@ -1,18 +1,22 @@ /* sbt -- Simple Build Tool * Copyright 2008, 2009 Mark Harrah */ - package xsbt + package sbt import xsbti.{Logger => xLogger, F0} -abstract class Logger extends xLogger with NotNull + +abstract class AbstractLogger extends xLogger with NotNull { def getLevel: Level.Value def setLevel(newLevel: Level.Value) - def enableTrace(flag: Boolean) - def traceEnabled: Boolean + def setTrace(flag: Int) + def getTrace: Int + final def traceEnabled = getTrace >= 0 + def ansiCodesSupported = false def atLevel(level: Level.Value) = level.id >= getLevel.id def trace(t: => Throwable): Unit + final def verbose(message: => String): Unit = debug(message) final def debug(message: => String): Unit = log(Level.Debug, message) final def info(message: => String): Unit = log(Level.Info, message) final def warn(message: => String): Unit = log(Level.Warn, message) @@ -31,7 +35,7 @@ abstract class Logger extends xLogger with NotNull case l: Log => log(l.level, l.msg) case t: Trace => trace(t.exception) case setL: SetLevel => setLevel(setL.newLevel) - case setT: SetTrace => enableTrace(setT.enabled) + case setT: SetTrace => setTrace(setT.level) case c: ControlEvent => control(c.event, c.msg) } } diff --git a/util/log/LoggerWriter.scala b/util/log/LoggerWriter.scala new file mode 100644 index 000000000..b3b2d54d3 --- /dev/null +++ b/util/log/LoggerWriter.scala @@ -0,0 +1,40 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009, 2010 Mark Harrah + */ +package sbt + +/** Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`. +* A line is delimited by `nl`, which is by default the platform line separator.*/ +class LoggerWriter(delegate: AbstractLogger, level: Level.Value, nl: String) extends java.io.Writer +{ + def this(delegate: AbstractLogger, level: Level.Value) = this(delegate, level, System.getProperty("line.separator")) + + private[this] val buffer = new StringBuilder + + override def close() = flush() + override def flush(): Unit = + synchronized { + if(buffer.length > 0) + { + log(buffer.toString) + buffer.clear() + } + } + override def write(content: Array[Char], offset: Int, length: Int): Unit = + synchronized { + buffer.append(content, offset, length) + process() + } + + private[this] def process() + { + val i = buffer.indexOf(nl) + if(i >= 0) + { + log(buffer.substring(0, i)) + buffer.delete(0, i + nl.length) + process() + } + } + private[this] def log(s: String): Unit = delegate.log(level, s) +} \ No newline at end of file diff --git a/util/log/MultiLogger.scala b/util/log/MultiLogger.scala new file mode 100644 index 000000000..800d170ed --- /dev/null +++ b/util/log/MultiLogger.scala @@ -0,0 +1,27 @@ + +/* sbt -- Simple Build Tool + * Copyright 2008, 2009, 2010 Mark Harrah + */ +package sbt + + +class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger +{ + override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported) + override def setLevel(newLevel: Level.Value) + { + super.setLevel(newLevel) + dispatch(new SetLevel(newLevel)) + } + override def setTrace(level: Int) + { + super.setTrace(level) + dispatch(new SetTrace(level)) + } + def trace(t: => Throwable) { dispatch(new Trace(t)) } + def log(level: Level.Value, message: => String) { dispatch(new Log(level, message)) } + def success(message: => String) { dispatch(new Success(message)) } + def logAll(events: Seq[LogEvent]) { delegates.foreach(_.logAll(events)) } + def control(event: ControlEvent.Value, message: => String) { delegates.foreach(_.control(event, message)) } + private def dispatch(event: LogEvent) { delegates.foreach(_.log(event)) } +} \ No newline at end of file