Add, configure & enable Scalafmt

This commit is contained in:
Dale Wijnand 2017-08-10 11:24:29 +01:00
parent 615c2e652f
commit d31b9c5093
No known key found for this signature in database
GPG Key ID: 4F256E3D151DF5EF
58 changed files with 1075 additions and 702 deletions

10
.scalafmt.conf Normal file
View File

@ -0,0 +1,10 @@
maxColumn = 100
project.git = true
project.excludeFilters = [ /sbt-test/, /input_sources/, /contraband-scala/ ]
# http://docs.scala-lang.org/style/scaladoc.html recommends the JavaDoc style.
# scala/scala is written that way too https://github.com/scala/scala/blob/v2.12.2/src/library/scala/Predef.scala
docstrings = JavaDoc
# This also seems more idiomatic to include whitespace in import x.{ yyy }
spaces.inImportCurlyBraces = true

View File

@ -6,7 +6,7 @@ scala:
- 2.12.3 - 2.12.3
script: script:
- sbt -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M "++$TRAVIS_SCALA_VERSION" mimaReportBinaryIssues test - sbt -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M "++$TRAVIS_SCALA_VERSION" mimaReportBinaryIssues scalafmt::test test:scalafmt::test sbt:scalafmt::test test
cache: cache:
directories: directories:

149
build.sbt
View File

@ -3,7 +3,7 @@ import Util._
//import com.typesafe.tools.mima.core._, ProblemFilters._ //import com.typesafe.tools.mima.core._, ProblemFilters._
def baseVersion = "1.0.0-SNAPSHOT" def baseVersion = "1.0.0-SNAPSHOT"
def internalPath = file("internal") def internalPath = file("internal")
def commonSettings: Seq[Setting[_]] = Seq( def commonSettings: Seq[Setting[_]] = Seq(
scalaVersion := scala212, scalaVersion := scala212,
@ -18,42 +18,53 @@ def commonSettings: Seq[Setting[_]] = Seq(
scalacOptions := { scalacOptions := {
val old = scalacOptions.value val old = scalacOptions.value
scalaVersion.value match { scalaVersion.value match {
case sv if sv.startsWith("2.10") => old diff List("-Xfuture", "-Ywarn-unused", "-Ywarn-unused-import") case sv if sv.startsWith("2.10") =>
old diff List("-Xfuture", "-Ywarn-unused", "-Ywarn-unused-import")
case sv if sv.startsWith("2.11") => old ++ List("-Ywarn-unused", "-Ywarn-unused-import") case sv if sv.startsWith("2.11") => old ++ List("-Ywarn-unused", "-Ywarn-unused-import")
case _ => old ++ List("-Ywarn-unused", "-Ywarn-unused-import", "-YdisableFlatCpCaching") case _ => old ++ List("-Ywarn-unused", "-Ywarn-unused-import", "-YdisableFlatCpCaching")
} }
}, },
scalacOptions in console in Compile -= "-Ywarn-unused-import", scalacOptions in console in Compile -= "-Ywarn-unused-import",
scalacOptions in console in Test -= "-Ywarn-unused-import", scalacOptions in console in Test -= "-Ywarn-unused-import",
publishArtifact in Compile := true, publishArtifact in Compile := true,
publishArtifact in Test := false publishArtifact in Test := false
) )
val mimaSettings = Def settings ( val mimaSettings = Def settings (
mimaPreviousArtifacts := Set(organization.value % moduleName.value % "1.0.0-RC3" mimaPreviousArtifacts := Set(
cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) organization.value % moduleName.value % "1.0.0-RC3"
cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled)
) )
) )
lazy val utilRoot: Project = (project in file(".")). lazy val utilRoot: Project = (project in file("."))
aggregate( .aggregate(
utilInterface, utilControl, utilPosition, utilInterface,
utilLogging, utilRelation, utilCache, utilTracking, utilTesting, utilControl,
utilPosition,
utilLogging,
utilRelation,
utilCache,
utilTracking,
utilTesting,
utilScripted utilScripted
). )
settings( .settings(
inThisBuild(Seq( inThisBuild(
git.baseVersion := baseVersion, Seq(
version := { git.baseVersion := baseVersion,
val v = version.value version := {
if (v contains "SNAPSHOT") git.baseVersion.value val v = version.value
else v if (v contains "SNAPSHOT") git.baseVersion.value
}, else v
bintrayPackage := "util", },
homepage := Some(url("https://github.com/sbt/util")), bintrayPackage := "util",
description := "Util module for sbt", homepage := Some(url("https://github.com/sbt/util")),
scmInfo := Some(ScmInfo(url("https://github.com/sbt/util"), "git@github.com:sbt/util.git")) description := "Util module for sbt",
)), scmInfo := Some(ScmInfo(url("https://github.com/sbt/util"), "git@github.com:sbt/util.git")),
scalafmtOnCompile := true,
scalafmtVersion := "1.1.0",
)),
commonSettings, commonSettings,
name := "Util Root", name := "Util Root",
publish := {}, publish := {},
@ -67,21 +78,19 @@ lazy val utilRoot: Project = (project in file(".")).
// defines Java structures used across Scala versions, such as the API structures and relationships extracted by // defines Java structures used across Scala versions, such as the API structures and relationships extracted by
// the analysis compiler phases and passed back to sbt. The API structures are defined in a simple // the analysis compiler phases and passed back to sbt. The API structures are defined in a simple
// format from which Java sources are generated by the datatype generator Projproject // format from which Java sources are generated by the datatype generator Projproject
lazy val utilInterface = (project in internalPath / "util-interface"). lazy val utilInterface = (project in internalPath / "util-interface").settings(
settings( commonSettings,
commonSettings, javaOnlySettings,
javaOnlySettings, name := "Util Interface",
name := "Util Interface", exportJars := true,
exportJars := true, mimaSettings,
mimaSettings, )
)
lazy val utilControl = (project in internalPath / "util-control"). lazy val utilControl = (project in internalPath / "util-control").settings(
settings( commonSettings,
commonSettings, name := "Util Control",
name := "Util Control", mimaSettings,
mimaSettings, )
)
val utilPosition = (project in file("internal") / "util-position").settings( val utilPosition = (project in file("internal") / "util-position").settings(
commonSettings, commonSettings,
@ -90,14 +99,15 @@ val utilPosition = (project in file("internal") / "util-position").settings(
) )
// logging // logging
lazy val utilLogging = (project in internalPath / "util-logging"). lazy val utilLogging = (project in internalPath / "util-logging")
enablePlugins(ContrabandPlugin, JsonCodecPlugin). .enablePlugins(ContrabandPlugin, JsonCodecPlugin)
dependsOn(utilInterface, utilTesting % Test). .dependsOn(utilInterface, utilTesting % Test)
settings( .settings(
commonSettings, commonSettings,
crossScalaVersions := Seq(scala210, scala211, scala212), crossScalaVersions := Seq(scala210, scala211, scala212),
name := "Util Logging", name := "Util Logging",
libraryDependencies ++= Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson.value, scalaReflect.value), libraryDependencies ++=
Seq(jline, log4jApi, log4jCore, disruptor, sjsonnewScalaJson.value, scalaReflect.value),
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
contrabandFormatsForType in generateContrabands in Compile := { tpe => contrabandFormatsForType in generateContrabands in Compile := { tpe =>
val old = (contrabandFormatsForType in generateContrabands in Compile).value val old = (contrabandFormatsForType in generateContrabands in Compile).value
@ -109,68 +119,69 @@ lazy val utilLogging = (project in internalPath / "util-logging").
) )
// Relation // Relation
lazy val utilRelation = (project in internalPath / "util-relation"). lazy val utilRelation = (project in internalPath / "util-relation")
dependsOn(utilTesting % Test). .dependsOn(utilTesting % Test)
settings( .settings(
commonSettings, commonSettings,
name := "Util Relation", name := "Util Relation",
mimaSettings, mimaSettings,
) )
// Persisted caching based on sjson-new // Persisted caching based on sjson-new
lazy val utilCache = (project in file("util-cache")). lazy val utilCache = (project in file("util-cache"))
dependsOn(utilTesting % Test). .dependsOn(utilTesting % Test)
settings( .settings(
commonSettings, commonSettings,
name := "Util Cache", name := "Util Cache",
libraryDependencies ++= Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value), libraryDependencies ++=
Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value),
mimaSettings, mimaSettings,
). )
configure(addSbtIO) .configure(addSbtIO)
// Builds on cache to provide caching for filesystem-related operations // Builds on cache to provide caching for filesystem-related operations
lazy val utilTracking = (project in file("util-tracking")). lazy val utilTracking = (project in file("util-tracking"))
dependsOn(utilCache, utilTesting % Test). .dependsOn(utilCache, utilTesting % Test)
settings( .settings(
commonSettings, commonSettings,
name := "Util Tracking", name := "Util Tracking",
mimaSettings, mimaSettings,
). )
configure(addSbtIO) .configure(addSbtIO)
// Internal utility for testing // Internal utility for testing
lazy val utilTesting = (project in internalPath / "util-testing"). lazy val utilTesting = (project in internalPath / "util-testing")
settings( .settings(
commonSettings, commonSettings,
crossScalaVersions := Seq(scala210, scala211, scala212), crossScalaVersions := Seq(scala210, scala211, scala212),
name := "Util Testing", name := "Util Testing",
libraryDependencies ++= Seq(scalaCheck, scalatest), libraryDependencies ++= Seq(scalaCheck, scalatest),
mimaSettings, mimaSettings,
). )
configure(addSbtIO) .configure(addSbtIO)
lazy val utilScripted = (project in internalPath / "util-scripted"). lazy val utilScripted = (project in internalPath / "util-scripted")
dependsOn(utilLogging, utilInterface). .dependsOn(utilLogging, utilInterface)
settings( .settings(
commonSettings, commonSettings,
name := "Util Scripted", name := "Util Scripted",
libraryDependencies ++= { libraryDependencies ++= {
scalaVersion.value match { scalaVersion.value match {
case sv if sv startsWith "2.11" => Seq(parserCombinator211) case sv if sv startsWith "2.11" => Seq(parserCombinator211)
case sv if sv startsWith "2.12" => Seq(parserCombinator211) case sv if sv startsWith "2.12" => Seq(parserCombinator211)
case _ => Seq() case _ => Seq()
} }
}, },
mimaSettings, mimaSettings,
). )
configure(addSbtIO) .configure(addSbtIO)
def customCommands: Seq[Setting[_]] = Seq( def customCommands: Seq[Setting[_]] = Seq(
commands += Command.command("release") { state => commands += Command.command("release") { state =>
// "clean" :: // "clean" ::
"+compile" :: "+compile" ::
"+publishSigned" :: "+publishSigned" ::
"reload" :: "reload" ::
state state
} }
) )

View File

@ -7,23 +7,20 @@ import java.io.IOException
object ErrorHandling { object ErrorHandling {
def translate[T](msg: => String)(f: => T) = def translate[T](msg: => String)(f: => T) =
try { f } try { f } catch {
catch {
case e: IOException => throw new TranslatedIOException(msg + e.toString, e) case e: IOException => throw new TranslatedIOException(msg + e.toString, e)
case e: Exception => throw new TranslatedException(msg + e.toString, e) case e: Exception => throw new TranslatedException(msg + e.toString, e)
} }
def wideConvert[T](f: => T): Either[Throwable, T] = def wideConvert[T](f: => T): Either[Throwable, T] =
try { Right(f) } try { Right(f) } catch {
catch {
case ex @ (_: Exception | _: StackOverflowError) => Left(ex) case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
case x: Throwable => Left(x) case x: Throwable => Left(x)
} }
def convert[T](f: => T): Either[Exception, T] = def convert[T](f: => T): Either[Exception, T] =
try { Right(f) } try { Right(f) } catch { case e: Exception => Left(e) }
catch { case e: Exception => Left(e) }
def reducedToString(e: Throwable): String = def reducedToString(e: Throwable): String =
if (e.getClass == classOf[RuntimeException]) { if (e.getClass == classOf[RuntimeException]) {
@ -32,7 +29,11 @@ object ErrorHandling {
} else } else
e.toString e.toString
} }
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable) extends RuntimeException(msg, cause) {
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable)
extends RuntimeException(msg, cause) {
override def toString = msg override def toString = msg
} }
final class TranslatedIOException private[sbt] (msg: String, cause: IOException) extends TranslatedException(msg, cause)
final class TranslatedIOException private[sbt] (msg: String, cause: IOException)
extends TranslatedException(msg, cause)

View File

@ -5,16 +5,20 @@ package sbt.internal.util
/** Defines a function to call as sbt exits.*/ /** Defines a function to call as sbt exits.*/
trait ExitHook { trait ExitHook {
/** Subclasses should implement this method, which is called when this hook is executed. */ /** Subclasses should implement this method, which is called when this hook is executed. */
def runBeforeExiting(): Unit def runBeforeExiting(): Unit
} }
object ExitHook { object ExitHook {
def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f } def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f }
} }
object ExitHooks { object ExitHooks {
/** Calls each registered exit hook, trapping any exceptions so that each hook is given a chance to run. */ /** Calls each registered exit hook, trapping any exceptions so that each hook is given a chance to run. */
def runExitHooks(exitHooks: Seq[ExitHook]): Seq[Throwable] = def runExitHooks(exitHooks: Seq[ExitHook]): Seq[Throwable] =
exitHooks.flatMap(hook => exitHooks.flatMap(hook => ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
} }

View File

@ -13,41 +13,43 @@ import java.util.concurrent.atomic.AtomicInteger
object BufferedAppender { object BufferedAppender {
def generateName: String = def generateName: String =
"buffered-" + generateId.incrementAndGet "buffered-" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger private val generateId: AtomicInteger = new AtomicInteger
def apply(delegate: Appender): BufferedAppender = def apply(delegate: Appender): BufferedAppender =
apply(generateName, delegate) apply(generateName, delegate)
def apply(name: String, delegate: Appender): BufferedAppender =
{ def apply(name: String, delegate: Appender): BufferedAppender = {
val appender = new BufferedAppender(name, delegate) val appender = new BufferedAppender(name, delegate)
appender.start appender.start
appender appender
} }
} }
/** /**
* Am appender that can buffer the logging done on it and then can flush the buffer * An appender that can buffer the logging done on it and then can flush the buffer
* to the delegate appender provided in the constructor. Use 'record()' to * to the delegate appender provided in the constructor. Use 'record()' to
* start buffering and then 'play' to flush the buffer to the backing appender. * start buffering and then 'play' to flush the buffer to the backing appender.
* The logging level set at the time a message is originally logged is used, not * The logging level set at the time a message is originally logged is used, not
* the level at the time 'play' is called. * the level at the time 'play' is called.
*/ */
class BufferedAppender private[BufferedAppender] (name: String, delegate: Appender) extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) { class BufferedAppender private[BufferedAppender] (name: String, delegate: Appender)
extends AbstractAppender(name, null, PatternLayout.createDefaultLayout(), true) {
private[this] val buffer = new ListBuffer[XLogEvent] private[this] val buffer = new ListBuffer[XLogEvent]
private[this] var recording = false private[this] var recording = false
def append(event: XLogEvent): Unit = def append(event: XLogEvent): Unit = {
{ if (recording) {
if (recording) { buffer += event.toImmutable
buffer += event.toImmutable } else delegate.append(event)
} else delegate.append(event) }
}
/** Enables buffering. */ /** Enables buffering. */
def record() = synchronized { recording = true } def record() = synchronized { recording = true }
def buffer[T](f: => T): T = { def buffer[T](f: => T): T = {
record() record()
try { f } try { f } finally { stopQuietly() }
finally { stopQuietly() }
} }
def bufferQuietly[T](f: => T): T = { def bufferQuietly[T](f: => T): T = {
record() record()
@ -70,10 +72,13 @@ class BufferedAppender private[BufferedAppender] (name: String, delegate: Append
} }
buffer.clear() buffer.clear()
} }
/** Clears buffered events and disables buffering. */ /** Clears buffered events and disables buffering. */
def clearBuffer(): Unit = synchronized { buffer.clear(); recording = false } def clearBuffer(): Unit = synchronized { buffer.clear(); recording = false }
/** Plays buffered events and disables buffering. */ /** Plays buffered events and disables buffering. */
def stopBuffer(): Unit = synchronized { play(); clearBuffer() } def stopBuffer(): Unit = synchronized { play(); clearBuffer() }
} }
/** /**
@ -93,8 +98,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
def record() = synchronized { recording = true } def record() = synchronized { recording = true }
def buffer[T](f: => T): T = { def buffer[T](f: => T): T = {
record() record()
try { f } try { f } finally { stopQuietly() }
finally { stopQuietly() }
} }
def bufferQuietly[T](f: => T): T = { def bufferQuietly[T](f: => T): T = {
record() record()
@ -111,8 +115,10 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
* so that the messages are written consecutively. The buffer is cleared in the process. * so that the messages are written consecutively. The buffer is cleared in the process.
*/ */
def play(): Unit = synchronized { delegate.logAll(buffer.toList); buffer.clear() } def play(): Unit = synchronized { delegate.logAll(buffer.toList); buffer.clear() }
/** Clears buffered events and disables buffering. */ /** Clears buffered events and disables buffering. */
def clear(): Unit = synchronized { buffer.clear(); recording = false } def clear(): Unit = synchronized { buffer.clear(); recording = false }
/** Plays buffered events and disables buffering. */ /** Plays buffered events and disables buffering. */
def stop(): Unit = synchronized { play(); clear() } def stop(): Unit = synchronized { play(); clear() }
@ -127,6 +133,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
delegate.setLevel(newLevel) delegate.setLevel(newLevel)
() ()
} }
override def setSuccessEnabled(flag: Boolean): Unit = synchronized { override def setSuccessEnabled(flag: Boolean): Unit = synchronized {
super.setSuccessEnabled(flag) super.setSuccessEnabled(flag)
if (recording) if (recording)
@ -135,6 +142,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
delegate.setSuccessEnabled(flag) delegate.setSuccessEnabled(flag)
() ()
} }
override def setTrace(level: Int): Unit = synchronized { override def setTrace(level: Int): Unit = synchronized {
super.setTrace(level) super.setTrace(level)
if (recording) if (recording)
@ -144,12 +152,14 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
() ()
} }
def trace(t: => Throwable): Unit = def trace(t: => Throwable): Unit = doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
def success(message: => String): Unit = def success(message: => String): Unit =
doBufferable(Level.Info, new Success(message), _.success(message)) doBufferable(Level.Info, new Success(message), _.success(message))
def log(level: Level.Value, message: => String): Unit = def log(level: Level.Value, message: => String): Unit =
doBufferable(level, new Log(level, message), _.log(level, message)) doBufferable(level, new Log(level, message), _.log(level, message))
def logAll(events: Seq[LogEvent]): Unit = synchronized { def logAll(events: Seq[LogEvent]): Unit = synchronized {
if (recording) if (recording)
buffer ++= events buffer ++= events
@ -157,11 +167,22 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
delegate.logAll(events) delegate.logAll(events)
() ()
} }
def control(event: ControlEvent.Value, message: => String): Unit = def control(event: ControlEvent.Value, message: => String): Unit =
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message)) doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
private def doBufferable(
level: Level.Value,
appendIfBuffered: => LogEvent,
doUnbuffered: AbstractLogger => Unit
): Unit =
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered) doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit = synchronized {
private def doBufferableIf(
condition: => Boolean,
appendIfBuffered: => LogEvent,
doUnbuffered: AbstractLogger => Unit
): Unit = synchronized {
if (condition) { if (condition) {
if (recording) if (recording)
buffer += appendIfBuffered buffer += appendIfBuffered

View File

@ -51,10 +51,13 @@ object ConsoleLogger {
* @param suppressedMessage How to show suppressed stack traces. * @param suppressedMessage How to show suppressed stack traces.
* @return A new `ConsoleLogger` that logs to `out`. * @return A new `ConsoleLogger` that logs to `out`.
*/ */
def apply(out: ConsoleOut = ConsoleOut.systemOut, def apply(
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv, out: ConsoleOut = ConsoleOut.systemOut,
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv, ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger = useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
suppressedMessage: SuppressedTraceContext => Option[String] =
ConsoleAppender.noSuppressedMessage
): ConsoleLogger =
new ConsoleLogger(out, ansiCodesSupported, useFormat, suppressedMessage) new ConsoleLogger(out, ansiCodesSupported, useFormat, suppressedMessage)
} }
@ -62,10 +65,12 @@ object ConsoleLogger {
* A logger that logs to the console. On supported systems, the level labels are * A logger that logs to the console. On supported systems, the level labels are
* colored. * colored.
*/ */
class ConsoleLogger private[ConsoleLogger] (out: ConsoleOut, class ConsoleLogger private[ConsoleLogger] (
override val ansiCodesSupported: Boolean, out: ConsoleOut,
useFormat: Boolean, override val ansiCodesSupported: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger { useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]
) extends BasicLogger {
private[sbt] val appender: ConsoleAppender = private[sbt] val appender: ConsoleAppender =
ConsoleAppender(generateName(), out, ansiCodesSupported, useFormat, suppressedMessage) ConsoleAppender(generateName(), out, ansiCodesSupported, useFormat, suppressedMessage)
@ -160,7 +165,11 @@ object ConsoleAppender {
* @param suppressedMessage How to handle stack traces. * @param suppressedMessage How to handle stack traces.
* @return A new `ConsoleAppender` that writes to `out`. * @return A new `ConsoleAppender` that writes to `out`.
*/ */
def apply(name: String, out: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = def apply(
name: String,
out: ConsoleOut,
suppressedMessage: SuppressedTraceContext => Option[String]
): ConsoleAppender =
apply(name, out, formatEnabledInEnv, formatEnabledInEnv, suppressedMessage) apply(name, out, formatEnabledInEnv, formatEnabledInEnv, suppressedMessage)
/** /**
@ -184,14 +193,16 @@ object ConsoleAppender {
* formatting. * formatting.
* @return A new `ConsoleAppender` that writes to `out`. * @return A new `ConsoleAppender` that writes to `out`.
*/ */
def apply(name: String, def apply(
out: ConsoleOut, name: String,
ansiCodesSupported: Boolean, out: ConsoleOut,
useFormat: Boolean, ansiCodesSupported: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = { useFormat: Boolean,
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage) suppressedMessage: SuppressedTraceContext => Option[String]
appender.start ): ConsoleAppender = {
appender val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
appender.start
appender
} }
/** /**
@ -242,7 +253,9 @@ object ConsoleAppender {
// this results in a linkage error as detected below. The detection is likely jvm specific, but the priority // this results in a linkage error as detected below. The detection is likely jvm specific, but the priority
// is avoiding mistakenly identifying something as a launcher incompatibility when it is not // is avoiding mistakenly identifying something as a launcher incompatibility when it is not
case e: IncompatibleClassChangeError if e.getMessage == jline1to2CompatMsg => case e: IncompatibleClassChangeError if e.getMessage == jline1to2CompatMsg =>
throw new IncompatibleClassChangeError("JLine incompatibility detected. Check that the sbt launcher is version 0.13.x or later.") throw new IncompatibleClassChangeError(
"JLine incompatibility detected. Check that the sbt launcher is version 0.13.x or later."
)
} }
private[this] def os = System.getProperty("os.name") private[this] def os = System.getProperty("os.name")
@ -262,11 +275,11 @@ object ConsoleAppender {
* This logger is not thread-safe. * This logger is not thread-safe.
*/ */
class ConsoleAppender private[ConsoleAppender] ( class ConsoleAppender private[ConsoleAppender] (
name: String, name: String,
out: ConsoleOut, out: ConsoleOut,
ansiCodesSupported: Boolean, ansiCodesSupported: Boolean,
useFormat: Boolean, useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String] suppressedMessage: SuppressedTraceContext => Option[String]
) extends AbstractAppender(name, null, LogExchange.dummyLayout, true) { ) extends AbstractAppender(name, null, LogExchange.dummyLayout, true) {
import scala.Console.{ BLUE, GREEN, RED, YELLOW } import scala.Console.{ BLUE, GREEN, RED, YELLOW }
@ -275,12 +288,12 @@ class ConsoleAppender private[ConsoleAppender] (
else "" else ""
} }
private val SUCCESS_LABEL_COLOR = GREEN private val SUCCESS_LABEL_COLOR = GREEN
private val SUCCESS_MESSAGE_COLOR = reset private val SUCCESS_MESSAGE_COLOR = reset
private val NO_COLOR = reset private val NO_COLOR = reset
private var traceEnabledVar: Int = Int.MaxValue private var traceEnabledVar: Int = Int.MaxValue
def setTrace(level: Int): Unit = synchronized { traceEnabledVar = level } def setTrace(level: Int): Unit = synchronized { traceEnabledVar = level }
/** /**
@ -307,9 +320,11 @@ class ConsoleAppender private[ConsoleAppender] (
out.lockObject.synchronized { out.lockObject.synchronized {
if (traceLevel >= 0) if (traceLevel >= 0)
write(StackTrace.trimmed(t, traceLevel)) write(StackTrace.trimmed(t, traceLevel))
if (traceLevel <= 2) if (traceLevel <= 2) {
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat))) val ctx = new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)
for (msg <- suppressedMessage(ctx))
appendLog(NO_COLOR, "trace", NO_COLOR, msg) appendLog(NO_COLOR, "trace", NO_COLOR, msg)
}
} }
/** /**
@ -365,10 +380,16 @@ class ConsoleAppender private[ConsoleAppender] (
* @param messageColor The color to use to format the message. * @param messageColor The color to use to format the message.
* @param message The message to write. * @param message The message to write.
*/ */
private def appendLog(labelColor: String, label: String, messageColor: String, message: String): Unit = private def appendLog(
labelColor: String,
label: String,
messageColor: String,
message: String
): Unit =
out.lockObject.synchronized { out.lockObject.synchronized {
message.lines.foreach { line => message.lines.foreach { line =>
val labeledLine = s"$reset[${formatted(labelColor, label)}] ${formatted(messageColor, line)}" val labeledLine =
s"$reset[${formatted(labelColor, label)}] ${formatted(messageColor, line)}"
write(labeledLine) write(labeledLine)
} }
} }
@ -395,31 +416,30 @@ class ConsoleAppender private[ConsoleAppender] (
private def appendTraceEvent(te: TraceEvent): Unit = { private def appendTraceEvent(te: TraceEvent): Unit = {
val traceLevel = getTrace val traceLevel = getTrace
val throwableShowLines: ShowLines[Throwable] = val throwableShowLines: ShowLines[Throwable] =
ShowLines[Throwable]( (t: Throwable) => { ShowLines[Throwable]((t: Throwable) => {
List(StackTrace.trimmed(t, traceLevel)) List(StackTrace.trimmed(t, traceLevel))
}) })
val codec: ShowLines[TraceEvent] = val codec: ShowLines[TraceEvent] =
ShowLines[TraceEvent]( (t: TraceEvent) => { ShowLines[TraceEvent]((t: TraceEvent) => {
throwableShowLines.showLines(t.message) throwableShowLines.showLines(t.message)
}) })
codec.showLines(te).toVector foreach { appendLog(Level.Error, _) } codec.showLines(te).toVector foreach { appendLog(Level.Error, _) }
} }
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = { private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = {
def appendEvent(oe: ObjectEvent[_]): Unit = def appendEvent(oe: ObjectEvent[_]): Unit = {
{ val contentType = oe.contentType
val contentType = oe.contentType if (contentType == "sbt.internal.util.TraceEvent") {
if (contentType == "sbt.internal.util.TraceEvent") { appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
appendTraceEvent(oe.message.asInstanceOf[TraceEvent]) } else
} LogExchange.stringCodec[AnyRef](contentType) match {
else LogExchange.stringCodec[AnyRef](contentType) match {
case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" => case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" =>
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) } codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) }
case Some(codec) => case Some(codec) =>
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { appendLog(level, _) } codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach (appendLog(level, _))
case _ => appendLog(level, oe.message.toString) case _ => appendLog(level, oe.message.toString)
} }
} }
o match { o match {
case x: StringEvent => Vector(x.message) foreach { appendLog(level, _) } case x: StringEvent => Vector(x.message) foreach { appendLog(level, _) }

View File

@ -12,8 +12,8 @@ sealed trait ConsoleOut {
object ConsoleOut { object ConsoleOut {
def systemOut: ConsoleOut = printStreamOut(System.out) def systemOut: ConsoleOut = printStreamOut(System.out)
def overwriteContaining(s: String): (String, String) => Boolean = (cur, prev) => def overwriteContaining(s: String): (String, String) => Boolean =
cur.contains(s) && prev.contains(s) (cur, prev) => cur.contains(s) && prev.contains(s)
/** Move to beginning of previous line and clear the line. */ /** Move to beginning of previous line and clear the line. */
private[this] final val OverwriteLine = "\u001B[A\r\u001B[2K" private[this] final val OverwriteLine = "\u001B[A\r\u001B[2K"

View File

@ -16,10 +16,21 @@ import org.apache.logging.log4j.core.Appender
* `backing` tracks the files that persist the global logging. * `backing` tracks the files that persist the global logging.
* `newLogger` creates a new global logging configuration from a sink and backing configuration. * `newLogger` creates a new global logging configuration from a sink and backing configuration.
*/ */
final case class GlobalLogging(full: ManagedLogger, console: ConsoleOut, backed: Appender, final case class GlobalLogging(
backing: GlobalLogBacking, newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging) full: ManagedLogger,
console: ConsoleOut,
backed: Appender,
backing: GlobalLogBacking,
newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging
)
final case class GlobalLogging1(full: Logger, console: ConsoleOut, backed: AbstractLogger, backing: GlobalLogBacking, newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1) final case class GlobalLogging1(
full: Logger,
console: ConsoleOut,
backed: AbstractLogger,
backing: GlobalLogBacking,
newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1
)
/** /**
* Tracks the files that persist the global logging. * Tracks the files that persist the global logging.
@ -27,6 +38,7 @@ final case class GlobalLogging1(full: Logger, console: ConsoleOut, backed: Abstr
* `newBackingFile` creates a new temporary location for the next backing file. * `newBackingFile` creates a new temporary location for the next backing file.
*/ */
final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile: () => File) { final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile: () => File) {
/** Shifts the current backing file to `last` and sets the current backing to `newFile`. */ /** Shifts the current backing file to `last` and sets the current backing to `newFile`. */
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file), newBackingFile) def shift(newFile: File) = GlobalLogBacking(newFile, Some(file), newBackingFile)
@ -38,32 +50,38 @@ final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile
* Otherwise, no changes are made. * Otherwise, no changes are made.
*/ */
def unshift = GlobalLogBacking(last getOrElse file, None, newBackingFile) def unshift = GlobalLogBacking(last getOrElse file, None, newBackingFile)
} }
object GlobalLogBacking { object GlobalLogBacking {
def apply(newBackingFile: => File): GlobalLogBacking = GlobalLogBacking(newBackingFile, None, newBackingFile _) def apply(newBackingFile: => File): GlobalLogBacking =
GlobalLogBacking(newBackingFile, None, newBackingFile _)
} }
object GlobalLogging { object GlobalLogging {
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
private def generateName: String =
"GlobalLogging" + generateId.incrementAndGet private def generateName: String = "GlobalLogging" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger private val generateId: AtomicInteger = new AtomicInteger
def initial1(newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1, newBackingFile: => File, console: ConsoleOut): GlobalLogging1 = def initial1(
{ newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1,
val log = ConsoleLogger(console) newBackingFile: => File,
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger) console: ConsoleOut
} ): GlobalLogging1 = {
val log = ConsoleLogger(console)
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
}
def initial(newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging, newBackingFile: => File, console: ConsoleOut): GlobalLogging = def initial(
{ newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging,
val loggerName = generateName newBackingFile: => File,
val log = LogExchange.logger(loggerName) console: ConsoleOut
val appender = ConsoleAppender(ConsoleAppender.generateName, console) ): GlobalLogging = {
LogExchange.bindLoggerAppenders( val loggerName = generateName
loggerName, List(appender -> Level.Info) val log = LogExchange.logger(loggerName)
) val appender = ConsoleAppender(ConsoleAppender.generateName, console)
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender) LogExchange.bindLoggerAppenders(loggerName, List(appender -> Level.Info))
} GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
}
} }

View File

@ -9,7 +9,11 @@ import sbt.util._
* Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`. * 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. * A line is delimited by `nl`, which is by default the platform line separator.
*/ */
class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: String = System.getProperty("line.separator")) extends java.io.Writer { class LoggerWriter(
delegate: Logger,
unbufferedLevel: Option[Level.Value],
nl: String = System.getProperty("line.separator")
) extends java.io.Writer {
def this(delegate: Logger, level: Level.Value) = this(delegate, Some(level)) def this(delegate: Logger, level: Level.Value) = this(delegate, Some(level))
def this(delegate: Logger) = this(delegate, None) def this(delegate: Logger) = this(delegate, None)
@ -17,6 +21,7 @@ class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: S
private[this] val lines = new collection.mutable.ListBuffer[String] private[this] val lines = new collection.mutable.ListBuffer[String]
override def close() = flush() override def close() = flush()
override def flush(): Unit = override def flush(): Unit =
synchronized { synchronized {
if (buffer.nonEmpty) { if (buffer.nonEmpty) {
@ -24,12 +29,14 @@ class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: S
buffer.clear() buffer.clear()
} }
} }
def flushLines(level: Level.Value): Unit = def flushLines(level: Level.Value): Unit =
synchronized { synchronized {
for (line <- lines) for (line <- lines)
delegate.log(level, line) delegate.log(level, line)
lines.clear() lines.clear()
} }
override def write(content: Array[Char], offset: Int, length: Int): Unit = override def write(content: Array[Char], offset: Int, length: Int): Unit =
synchronized { synchronized {
buffer.appendAll(content, offset, length) buffer.appendAll(content, offset, length)
@ -44,6 +51,7 @@ class LoggerWriter(delegate: Logger, unbufferedLevel: Option[Level.Value], nl: S
process() process()
} }
} }
private[this] def log(s: String): Unit = unbufferedLevel match { private[this] def log(s: String): Unit = unbufferedLevel match {
case None => case None =>
lines += s; () lines += s; ()

View File

@ -10,47 +10,78 @@ object MainAppender {
"GlobalBacking" + generateId.incrementAndGet "GlobalBacking" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger private val generateId: AtomicInteger = new AtomicInteger
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger = def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger = {
{ import config._
import config._ // TODO
// TODO // console setTrace screenTrace
// console setTrace screenTrace // backed setTrace backingTrace
// backed setTrace backingTrace // multi: Logger
// multi: Logger
// val log = LogExchange.logger(loggerName) // val log = LogExchange.logger(loggerName)
LogExchange.unbindLoggerAppenders(log.name) LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders( LogExchange.bindLoggerAppenders(
log.name, log.name,
(consoleOpt.toList map { _ -> screenLevel }) ::: (consoleOpt.toList map { _ -> screenLevel }) :::
List(backed -> backingLevel) ::: List(backed -> backingLevel) :::
(extra map { x => (x -> Level.Info) }) (extra map { x =>
) (x -> Level.Info)
log })
} )
log
}
def globalDefault(console: ConsoleOut): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = def globalDefault(
{ console: ConsoleOut
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = (log, writer, backing) => { ): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = {
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging =
(log, writer, backing) => {
val backed: Appender = defaultBacked(generateGlobalBackingName)(writer) val backed: Appender = defaultBacked(generateGlobalBackingName)(writer)
val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil)) val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil))
GlobalLogging(full, console, backed, backing, newAppender) GlobalLogging(full, console, backed, backing, newAppender)
} }
newAppender newAppender
} }
def defaultMultiConfig(consoleOpt: Option[ConsoleOut], backing: Appender, extra: List[Appender]): MainAppenderConfig = def defaultMultiConfig(
MainAppenderConfig(consoleOpt map { defaultScreen(_, ConsoleAppender.noSuppressedMessage) }, backing, extra, consoleOpt: Option[ConsoleOut],
Level.Info, Level.Debug, -1, Int.MaxValue) backing: Appender,
def defaultScreen(console: ConsoleOut): Appender = ConsoleAppender(ConsoleAppender.generateName, console) extra: List[Appender]
def defaultScreen(console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender = ): MainAppenderConfig =
MainAppenderConfig(
consoleOpt map { defaultScreen(_, ConsoleAppender.noSuppressedMessage) },
backing,
extra,
Level.Info,
Level.Debug,
-1,
Int.MaxValue
)
def defaultScreen(console: ConsoleOut): Appender =
ConsoleAppender(ConsoleAppender.generateName, console)
def defaultScreen(
console: ConsoleOut,
suppressedMessage: SuppressedTraceContext => Option[String]
): Appender =
ConsoleAppender(ConsoleAppender.generateName, console, suppressedMessage = suppressedMessage) ConsoleAppender(ConsoleAppender.generateName, console, suppressedMessage = suppressedMessage)
def defaultScreen(name: String, console: ConsoleOut, suppressedMessage: SuppressedTraceContext => Option[String]): Appender =
def defaultScreen(
name: String,
console: ConsoleOut,
suppressedMessage: SuppressedTraceContext => Option[String]
): Appender =
ConsoleAppender(name, console, suppressedMessage = suppressedMessage) ConsoleAppender(name, console, suppressedMessage = suppressedMessage)
def defaultBacked: PrintWriter => Appender = defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv) def defaultBacked: PrintWriter => Appender =
def defaultBacked(loggerName: String): PrintWriter => Appender = defaultBacked(loggerName, ConsoleAppender.formatEnabledInEnv) defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv)
def defaultBacked(useFormat: Boolean): PrintWriter => Appender = defaultBacked(generateGlobalBackingName, useFormat)
def defaultBacked(loggerName: String): PrintWriter => Appender =
defaultBacked(loggerName, ConsoleAppender.formatEnabledInEnv)
def defaultBacked(useFormat: Boolean): PrintWriter => Appender =
defaultBacked(generateGlobalBackingName, useFormat)
def defaultBacked(loggerName: String, useFormat: Boolean): PrintWriter => Appender = def defaultBacked(loggerName: String, useFormat: Boolean): PrintWriter => Appender =
to => { to => {
ConsoleAppender( ConsoleAppender(
@ -61,7 +92,12 @@ object MainAppender {
} }
final case class MainAppenderConfig( final case class MainAppenderConfig(
consoleOpt: Option[Appender], backed: Appender, extra: List[Appender], consoleOpt: Option[Appender],
screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int backed: Appender,
extra: List[Appender],
screenLevel: Level.Value,
backingLevel: Level.Value,
screenTrace: Int,
backingTrace: Int
) )
} }

View File

@ -14,33 +14,31 @@ import sbt.internal.util.codec.JsonProtocol._
* Delegates log events to the associated LogExchange. * Delegates log events to the associated LogExchange.
*/ */
class ManagedLogger( class ManagedLogger(
val name: String, val name: String,
val channelName: Option[String], val channelName: Option[String],
val execId: Option[String], val execId: Option[String],
xlogger: XLogger xlogger: XLogger
) extends Logger { ) extends Logger {
override def trace(t: => Throwable): Unit = override def trace(t: => Throwable): Unit =
logEvent(Level.Error, TraceEvent("Error", t, channelName, execId)) logEvent(Level.Error, TraceEvent("Error", t, channelName, execId))
override def log(level: Level.Value, message: => String): Unit = override def log(level: Level.Value, message: => String): Unit = {
{ xlogger.log(
xlogger.log( ConsoleAppender.toXLevel(level),
ConsoleAppender.toXLevel(level), new ObjectMessage(StringEvent(level.toString, message, channelName, execId))
new ObjectMessage(StringEvent(level.toString, message, channelName, execId)) )
) }
}
// send special event for success since it's not a real log level // send special event for success since it's not a real log level
override def success(message: => String): Unit = { override def success(message: => String): Unit = {
infoEvent[SuccessEvent](SuccessEvent(message)) infoEvent[SuccessEvent](SuccessEvent(message))
} }
def registerStringCodec[A: ShowLines: TypeTag]: Unit = def registerStringCodec[A: ShowLines: TypeTag]: Unit = {
{ val tag = StringTypeTag[A]
val tag = StringTypeTag[A] val ev = implicitly[ShowLines[A]]
val ev = implicitly[ShowLines[A]] // println(s"registerStringCodec ${tag.key}")
// println(s"registerStringCodec ${tag.key}") val _ = LogExchange.getOrElseUpdateStringCodec(tag.key, ev)
val _ = LogExchange.getOrElseUpdateStringCodec(tag.key, ev) }
}
registerStringCodec[Throwable] registerStringCodec[Throwable]
registerStringCodec[TraceEvent] registerStringCodec[TraceEvent]
registerStringCodec[SuccessEvent] registerStringCodec[SuccessEvent]
@ -48,18 +46,17 @@ class ManagedLogger(
final def infoEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Info, event) final def infoEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Info, event)
final def warnEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Warn, event) final def warnEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Warn, event)
final def errorEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Error, event) final def errorEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Error, event)
def logEvent[A: JsonFormat: TypeTag](level: Level.Value, event: => A): Unit = def logEvent[A: JsonFormat: TypeTag](level: Level.Value, event: => A): Unit = {
{ val v: A = event
val v: A = event val tag = StringTypeTag[A]
val tag = StringTypeTag[A] LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]])
LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]]) // println("logEvent " + tag.key)
// println("logEvent " + tag.key) val entry: ObjectEvent[A] = ObjectEvent(level, v, channelName, execId, tag.key)
val entry: ObjectEvent[A] = ObjectEvent(level, v, channelName, execId, tag.key) xlogger.log(
xlogger.log( ConsoleAppender.toXLevel(level),
ConsoleAppender.toXLevel(level), new ObjectMessage(entry)
new ObjectMessage(entry) )
) }
}
@deprecated("No longer used.", "1.0.0") @deprecated("No longer used.", "1.0.0")
override def ansiCodesSupported = ConsoleAppender.formatEnabledInEnv override def ansiCodesSupported = ConsoleAppender.formatEnabledInEnv

View File

@ -1,4 +1,3 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah * Copyright 2008, 2009, 2010 Mark Harrah
*/ */
@ -17,19 +16,25 @@ class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger {
super.setLevel(newLevel) super.setLevel(newLevel)
dispatch(new SetLevel(newLevel)) dispatch(new SetLevel(newLevel))
} }
override def setTrace(level: Int): Unit = { override def setTrace(level: Int): Unit = {
super.setTrace(level) super.setTrace(level)
dispatch(new SetTrace(level)) dispatch(new SetTrace(level))
} }
override def setSuccessEnabled(flag: Boolean): Unit = { override def setSuccessEnabled(flag: Boolean): Unit = {
super.setSuccessEnabled(flag) super.setSuccessEnabled(flag)
dispatch(new SetSuccess(flag)) dispatch(new SetSuccess(flag))
} }
def trace(t: => Throwable): Unit = dispatch(new Trace(t)) def trace(t: => Throwable): Unit = dispatch(new Trace(t))
def log(level: Level.Value, message: => String): Unit = dispatch(new Log(level, message)) def log(level: Level.Value, message: => String): Unit = dispatch(new Log(level, message))
def success(message: => String): Unit = dispatch(new Success(message)) def success(message: => String): Unit = dispatch(new Success(message))
def logAll(events: Seq[LogEvent]): Unit = delegates.foreach(_.logAll(events)) def logAll(events: Seq[LogEvent]): Unit = delegates.foreach(_.logAll(events))
def control(event: ControlEvent.Value, message: => String): Unit = delegates.foreach(_.control(event, message))
def control(event: ControlEvent.Value, message: => String): Unit =
delegates.foreach(_.control(event, message))
private[this] def dispatch(event: LogEvent): Unit = { private[this] def dispatch(event: LogEvent): Unit = {
for (d <- delegates) { for (d <- delegates) {
d.log(event) d.log(event)

View File

@ -8,12 +8,12 @@ import sjsonnew.support.scalajson.unsafe.Converter
import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.shaded.scalajson.ast.unsafe.JValue
final class ObjectEvent[A]( final class ObjectEvent[A](
val level: Level.Value, val level: Level.Value,
val message: A, val message: A,
val channelName: Option[String], val channelName: Option[String],
val execId: Option[String], val execId: Option[String],
val contentType: String, val contentType: String,
val json: JValue val json: JValue
) extends Serializable { ) extends Serializable {
override def toString: String = override def toString: String =
s"ObjectEvent($level, $message, $channelName, $execId, $contentType, $json)" s"ObjectEvent($level, $message, $channelName, $execId, $contentType, $json)"
@ -21,12 +21,18 @@ final class ObjectEvent[A](
object ObjectEvent { object ObjectEvent {
def apply[A: JsonFormat]( def apply[A: JsonFormat](
level: Level.Value, level: Level.Value,
message: A, message: A,
channelName: Option[String], channelName: Option[String],
execId: Option[String], execId: Option[String],
contentType: String contentType: String
): ObjectEvent[A] = ): ObjectEvent[A] =
new ObjectEvent(level, message, channelName, execId, contentType, new ObjectEvent(
Converter.toJsonUnsafe(message)) level,
message,
channelName,
execId,
contentType,
Converter.toJsonUnsafe(message)
)
} }

View File

@ -5,6 +5,7 @@ package sbt.internal.util
object StackTrace { object StackTrace {
def isSbtClass(name: String) = name.startsWith("sbt") || name.startsWith("xsbt") def isSbtClass(name: String) = name.startsWith("sbt") || name.startsWith("xsbt")
/** /**
* Return a printable representation of the stack trace associated * Return a printable representation of the stack trace associated
* with t. Information about t and its Throwable causes is included. * with t. Information about t and its Throwable causes is included.
@ -59,6 +60,6 @@ object StackTrace {
appendStackTrace(c, false) appendStackTrace(c, false)
} }
b.toString() b.toString()
} }
} }

View File

@ -8,14 +8,13 @@ final case class StringTypeTag[A](key: String) {
} }
object StringTypeTag { object StringTypeTag {
def apply[A: TypeTag]: StringTypeTag[A] = def apply[A: TypeTag]: StringTypeTag[A] = {
{ val tag = implicitly[TypeTag[A]]
val tag = implicitly[TypeTag[A]] val tpe = tag.tpe
val tpe = tag.tpe val k = typeToString(tpe)
val k = typeToString(tpe) // println(tpe.getClass.toString + " " + k)
// println(tpe.getClass.toString + " " + k) StringTypeTag[A](k)
StringTypeTag[A](k) }
}
def typeToString(tpe: Type): String = def typeToString(tpe: Type): String =
tpe match { tpe match {
case TypeRef(_, sym, args) => case TypeRef(_, sym, args) =>

View File

@ -17,7 +17,10 @@ trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
implicit val JBooleanFormat: JF[JBoolean] = projectFormat(_.get, (x: Boolean) => JBoolean(x)) implicit val JBooleanFormat: JF[JBoolean] = projectFormat(_.get, (x: Boolean) => JBoolean(x))
implicit val JStringFormat: JF[JString] = projectFormat(_.value, (x: String) => JString(x)) implicit val JStringFormat: JF[JString] = projectFormat(_.value, (x: String) => JString(x))
implicit val JNumberFormat: JF[JNumber] = projectFormat(x => BigDecimal(x.value), (x: BigDecimal) => JNumber(x.toString))
implicit val JNumberFormat: JF[JNumber] =
projectFormat(x => BigDecimal(x.value), (x: BigDecimal) => JNumber(x.toString))
implicit val JArrayFormat: JF[JArray] = projectFormat[JArray, Array[JValue]](_.value, JArray(_)) implicit val JArrayFormat: JF[JArray] = projectFormat[JArray, Array[JValue]](_.value, JArray(_))
implicit lazy val JObjectJsonWriter: JW[JObject] = new JW[JObject] { implicit lazy val JObjectJsonWriter: JW[JObject] = new JW[JObject] {
@ -43,5 +46,6 @@ trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
def read[J](j: Option[J], u: Unbuilder[J]) = ??? // Is this even possible? with no Manifest[J]? def read[J](j: Option[J], u: Unbuilder[J]) = ??? // Is this even possible? with no Manifest[J]?
} }
implicit lazy val JValueFormat: JF[JValue] = jsonFormat[JValue](JValueJsonReader, JValueJsonWriter) implicit lazy val JValueFormat: JF[JValue] =
jsonFormat[JValue](JValueJsonReader, JValueJsonWriter)
} }

View File

@ -1,7 +1,6 @@
/** /**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/ */
package sbt.internal.util.codec package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder } import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import xsbti.Position import xsbti.Position

View File

@ -1,7 +1,6 @@
/** /**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/ */
package sbt.internal.util.codec package sbt.internal.util.codec
import xsbti.{ Problem, Severity, Position } import xsbti.{ Problem, Severity, Position }

View File

@ -1,7 +1,6 @@
/** /**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/ */
package sbt.internal.util.codec package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder } import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }

View File

@ -6,7 +6,7 @@ import sbt.internal.util.SuccessEvent
trait SuccessEventShowLines { trait SuccessEventShowLines {
implicit val sbtSuccessEventShowLines: ShowLines[SuccessEvent] = implicit val sbtSuccessEventShowLines: ShowLines[SuccessEvent] =
ShowLines[SuccessEvent]( (e: SuccessEvent) => { ShowLines[SuccessEvent]((e: SuccessEvent) => {
Vector(e.message) Vector(e.message)
}) })
} }

View File

@ -6,7 +6,7 @@ import sbt.internal.util.{ StackTrace, TraceEvent }
trait ThrowableShowLines { trait ThrowableShowLines {
implicit val sbtThrowableShowLines: ShowLines[Throwable] = implicit val sbtThrowableShowLines: ShowLines[Throwable] =
ShowLines[Throwable]( (t: Throwable) => { ShowLines[Throwable]((t: Throwable) => {
// 0 means enabled with default behavior. See StackTrace.scala. // 0 means enabled with default behavior. See StackTrace.scala.
val traceLevel = 0 val traceLevel = 0
List(StackTrace.trimmed(t, traceLevel)) List(StackTrace.trimmed(t, traceLevel))
@ -17,7 +17,7 @@ object ThrowableShowLines extends ThrowableShowLines
trait TraceEventShowLines { trait TraceEventShowLines {
implicit val sbtTraceEventShowLines: ShowLines[TraceEvent] = implicit val sbtTraceEventShowLines: ShowLines[TraceEvent] =
ShowLines[TraceEvent]( (t: TraceEvent) => { ShowLines[TraceEvent]((t: TraceEvent) => {
ThrowableShowLines.sbtThrowableShowLines.showLines(t.message) ThrowableShowLines.sbtThrowableShowLines.showLines(t.message)
}) })
} }

View File

@ -13,6 +13,7 @@ abstract class AbstractLogger extends Logger {
def control(event: ControlEvent.Value, message: => String): Unit def control(event: ControlEvent.Value, message: => String): Unit
def logAll(events: Seq[LogEvent]): Unit def logAll(events: Seq[LogEvent]): Unit
/** Defined in terms of other methods in Logger and should not be called from them. */ /** Defined in terms of other methods in Logger and should not be called from them. */
final def log(event: LogEvent): Unit = { final def log(event: LogEvent): Unit = {
event match { event match {

View File

@ -36,8 +36,15 @@ object InterfaceUtil {
case None => Optional.empty[A]() case None => Optional.empty[A]()
} }
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer], def position(
pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position = line0: Option[Integer],
content: String,
offset0: Option[Integer],
pointer0: Option[Integer],
pointerSpace0: Option[String],
sourcePath0: Option[String],
sourceFile0: Option[File]
): Position =
new ConcretePosition(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0) new ConcretePosition(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0)
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem = def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
@ -53,23 +60,22 @@ object InterfaceUtil {
this.get2 == o.get2 this.get2 == o.get2
case _ => false case _ => false
} }
override def hashCode: Int = override def hashCode: Int = {
{ var hash = 1
var hash = 1 hash = hash * 31 + this.get1.##
hash = hash * 31 + this.get1.## hash = hash * 31 + this.get2.##
hash = hash * 31 + this.get2.## hash
hash }
}
} }
private final class ConcretePosition( private final class ConcretePosition(
line0: Option[Integer], line0: Option[Integer],
content: String, content: String,
offset0: Option[Integer], offset0: Option[Integer],
pointer0: Option[Integer], pointer0: Option[Integer],
pointerSpace0: Option[String], pointerSpace0: Option[String],
sourcePath0: Option[String], sourcePath0: Option[String],
sourceFile0: Option[File] sourceFile0: Option[File]
) extends Position { ) extends Position {
val line = o2jo(line0) val line = o2jo(line0)
val lineContent = content val lineContent = content
@ -81,10 +87,10 @@ object InterfaceUtil {
} }
private final class ConcreteProblem( private final class ConcreteProblem(
cat: String, cat: String,
pos: Position, pos: Position,
msg: String, msg: String,
sev: Severity sev: Severity
) extends Problem { ) extends Problem {
val category = cat val category = cat
val position = pos val position = pos

View File

@ -12,6 +12,7 @@ object Level extends Enumeration {
val Info = Value(2, "info") val Info = Value(2, "info")
val Warn = Value(3, "warn") val Warn = Value(3, "warn")
val Error = Value(4, "error") val Error = Value(4, "error")
/** /**
* Defines the label to use for success messages. * Defines the label to use for success messages.
* Because the label for levels is defined in this module, the success label is also defined here. * Because the label for levels is defined in this module, the success label is also defined here.
@ -23,6 +24,7 @@ object Level extends Enumeration {
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */ /** 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) def apply(s: String) = values.find(s == _.toString)
/** Same as apply, defined for use in pattern matching. */ /** Same as apply, defined for use in pattern matching. */
private[sbt] def unapply(s: String) = apply(s) private[sbt] def unapply(s: String) = apply(s)
} }

View File

@ -14,4 +14,4 @@ final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends
object ControlEvent extends Enumeration { object ControlEvent extends Enumeration {
val Start, Header, Finish = Value val Start, Header, Finish = Value
} }

View File

@ -24,8 +24,16 @@ sealed abstract class LogExchange {
val _ = context val _ = context
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x } val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
val config = ctx.getConfiguration val config = ctx.getConfiguration
val loggerConfig = LoggerConfig.createLogger(false, XLevel.DEBUG, name, val loggerConfig = LoggerConfig.createLogger(
"true", Array[AppenderRef](), null, config, null) false,
XLevel.DEBUG,
name,
"true",
Array[AppenderRef](),
null,
config,
null
)
config.addLogger(name, loggerConfig) config.addLogger(name, loggerConfig)
ctx.updateLoggers ctx.updateLoggers
val logger = ctx.getLogger(name) val logger = ctx.getLogger(name)

View File

@ -22,6 +22,7 @@ abstract class Logger extends xLogger {
final def info(message: => String): Unit = log(Level.Info, message) final def info(message: => String): Unit = log(Level.Info, message)
final def warn(message: => String): Unit = log(Level.Warn, message) final def warn(message: => String): Unit = log(Level.Warn, message)
final def error(message: => String): Unit = log(Level.Error, message) final def error(message: => String): Unit = log(Level.Error, message)
// Added by sys.process.ProcessLogger // Added by sys.process.ProcessLogger
final def err(message: => String): Unit = log(Level.Error, message) final def err(message: => String): Unit = log(Level.Error, message)
// sys.process.ProcessLogger // sys.process.ProcessLogger
@ -62,12 +63,16 @@ object Logger {
def log(level: Level.Value, message: => String): Unit = () def log(level: Level.Value, message: => String): Unit = ()
} }
implicit def absLog2PLog(log: AbstractLogger): ProcessLogger = new BufferedLogger(log) with ProcessLogger implicit def absLog2PLog(log: AbstractLogger): ProcessLogger =
new BufferedLogger(log) with ProcessLogger
implicit def log2PLog(log: Logger): ProcessLogger = absLog2PLog(new FullLogger(log)) implicit def log2PLog(log: Logger): ProcessLogger = absLog2PLog(new FullLogger(log))
implicit def xlog2Log(lg: xLogger): Logger = lg match { implicit def xlog2Log(lg: xLogger): Logger = lg match {
case l: Logger => l case l: Logger => l
case _ => wrapXLogger(lg) case _ => wrapXLogger(lg)
} }
private[this] def wrapXLogger(lg: xLogger): Logger = new Logger { private[this] def wrapXLogger(lg: xLogger): Logger = new Logger {
import InterfaceUtil.toSupplier import InterfaceUtil.toSupplier
override def debug(msg: Supplier[String]): Unit = lg.debug(msg) override def debug(msg: Supplier[String]): Unit = lg.debug(msg)
@ -78,23 +83,39 @@ object Logger {
override def log(level: Level.Value, msg: Supplier[String]): Unit = lg.log(level, msg) override def log(level: Level.Value, msg: Supplier[String]): Unit = lg.log(level, msg)
def trace(t: => Throwable): Unit = trace(toSupplier(t)) def trace(t: => Throwable): Unit = trace(toSupplier(t))
def success(s: => String): Unit = info(toSupplier(s)) def success(s: => String): Unit = info(toSupplier(s))
def log(level: Level.Value, msg: => String): Unit = def log(level: Level.Value, msg: => String): Unit = {
{ val fmsg = toSupplier(msg)
val fmsg = toSupplier(msg) level match {
level match { case Level.Debug => lg.debug(fmsg)
case Level.Debug => lg.debug(fmsg) case Level.Info => lg.info(fmsg)
case Level.Info => lg.info(fmsg) case Level.Warn => lg.warn(fmsg)
case Level.Warn => lg.warn(fmsg) case Level.Error => lg.error(fmsg)
case Level.Error => lg.error(fmsg)
}
} }
}
} }
def jo2o[A](o: Optional[A]): Option[A] = InterfaceUtil.jo2o(o) def jo2o[A](o: Optional[A]): Option[A] = InterfaceUtil.jo2o(o)
def o2jo[A](o: Option[A]): Optional[A] = InterfaceUtil.o2jo(o) def o2jo[A](o: Option[A]): Optional[A] = InterfaceUtil.o2jo(o)
def position(line0: Option[Integer], content: String, offset0: Option[Integer], pointer0: Option[Integer],
pointerSpace0: Option[String], sourcePath0: Option[String], sourceFile0: Option[File]): Position = def position(
InterfaceUtil.position(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0) line0: Option[Integer],
content: String,
offset0: Option[Integer],
pointer0: Option[Integer],
pointerSpace0: Option[String],
sourcePath0: Option[String],
sourceFile0: Option[File]
): Position =
InterfaceUtil.position(
line0,
content,
offset0,
pointer0,
pointerSpace0,
sourcePath0,
sourceFile0
)
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem = def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
InterfaceUtil.problem(cat, pos, msg, sev) InterfaceUtil.problem(cat, pos, msg, sev)
} }

View File

@ -8,26 +8,25 @@ import EscHelpers.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequ
object Escapes extends Properties("Escapes") { object Escapes extends Properties("Escapes") {
property("genTerminator only generates terminators") = property("genTerminator only generates terminators") =
forAllNoShrink(genTerminator) { (c: Char) => isEscapeTerminator(c) } forAllNoShrink(genTerminator)((c: Char) => isEscapeTerminator(c))
property("genWithoutTerminator only generates terminators") = property("genWithoutTerminator only generates terminators") =
forAllNoShrink(genWithoutTerminator) { (s: String) => forAllNoShrink(genWithoutTerminator) { (s: String) =>
s.forall { c => !isEscapeTerminator(c) } s.forall(c => !isEscapeTerminator(c))
} }
property("hasEscapeSequence is false when no escape character is present") = forAllNoShrink(genWithoutEscape) { (s: String) => property("hasEscapeSequence is false when no escape character is present") =
!hasEscapeSequence(s) forAllNoShrink(genWithoutEscape)((s: String) => !hasEscapeSequence(s))
}
property("hasEscapeSequence is true when escape character is present") = forAllNoShrink(genWithRandomEscapes) { (s: String) => property("hasEscapeSequence is true when escape character is present") =
hasEscapeSequence(s) forAllNoShrink(genWithRandomEscapes)((s: String) => hasEscapeSequence(s))
}
property("removeEscapeSequences is the identity when no escape character is present") = forAllNoShrink(genWithoutEscape) { (s: String) => property("removeEscapeSequences is the identity when no escape character is present") =
val removed: String = removeEscapeSequences(s) forAllNoShrink(genWithoutEscape) { (s: String) =>
("Escape sequence removed: '" + removed + "'") |: val removed: String = removeEscapeSequences(s)
(removed == s) ("Escape sequence removed: '" + removed + "'") |:
} (removed == s)
}
property("No escape characters remain after removeEscapeSequences") = forAll { (s: String) => property("No escape characters remain after removeEscapeSequences") = forAll { (s: String) =>
val removed: String = removeEscapeSequences(s) val removed: String = removeEscapeSequences(s)
@ -36,22 +35,26 @@ object Escapes extends Properties("Escapes") {
} }
property("removeEscapeSequences returns string without escape sequences") = property("removeEscapeSequences returns string without escape sequences") =
forAllNoShrink(genWithoutEscape, genEscapePairs) { (start: String, escapes: List[EscapeAndNot]) => forAllNoShrink(genWithoutEscape, genEscapePairs) {
val withEscapes: String = start + (escapes.map { ean => ean.escape.makeString + ean.notEscape }).mkString("") (start: String, escapes: List[EscapeAndNot]) =>
val removed: String = removeEscapeSequences(withEscapes) val withEscapes: String =
val original = start + escapes.map(_.notEscape).mkString("") start + escapes.map(ean => ean.escape.makeString + ean.notEscape).mkString("")
val diffCharString = diffIndex(original, removed) val removed: String = removeEscapeSequences(withEscapes)
("Input string : '" + withEscapes + "'") |: val original = start + escapes.map(_.notEscape).mkString("")
("Expected : '" + original + "'") |: val diffCharString = diffIndex(original, removed)
("Escapes removed : '" + removed + "'") |: ("Input string : '" + withEscapes + "'") |:
(diffCharString) |: ("Expected : '" + original + "'") |:
(original == removed) ("Escapes removed : '" + removed + "'") |:
(diffCharString) |:
(original == removed)
} }
def diffIndex(expect: String, original: String): String = { def diffIndex(expect: String, original: String): String = {
var i = 0; var i = 0;
while (i < expect.length && i < original.length) { while (i < expect.length && i < original.length) {
if (expect.charAt(i) != original.charAt(i)) return ("Differing character, idx: " + i + ", char: " + original.charAt(i) + ", expected: " + expect.charAt(i)) if (expect.charAt(i) != original.charAt(i))
return ("Differing character, idx: " + i + ", char: " + original.charAt(i) +
", expected: " + expect.charAt(i))
i += 1 i += 1
} }
if (expect.length != original.length) return s"Strings are different lengths!" if (expect.length != original.length) return s"Strings are different lengths!"
@ -59,13 +62,21 @@ object Escapes extends Properties("Escapes") {
} }
final case class EscapeAndNot(escape: EscapeSequence, notEscape: String) { final case class EscapeAndNot(escape: EscapeSequence, notEscape: String) {
override def toString = s"EscapeAntNot(escape = [$escape], notEscape = [${notEscape.map(_.toInt)}])" override def toString =
s"EscapeAntNot(escape = [$escape], notEscape = [${notEscape.map(_.toInt)}])"
} }
// 2.10.5 warns on "implicit numeric widening" but it looks like a bug: https://issues.scala-lang.org/browse/SI-8450 // 2.10.5 warns on "implicit numeric widening" but it looks like a bug: https://issues.scala-lang.org/browse/SI-8450
final case class EscapeSequence(content: String, terminator: Char) { final case class EscapeSequence(content: String, terminator: Char) {
if (!content.isEmpty) { if (!content.isEmpty) {
assert(content.tail.forall(c => !isEscapeTerminator(c)), "Escape sequence content contains an escape terminator: '" + content + "'") assert(
assert((content.head == '[') || !isEscapeTerminator(content.head), "Escape sequence content contains an escape terminator: '" + content.headOption + "'") content.tail.forall(c => !isEscapeTerminator(c)),
"Escape sequence content contains an escape terminator: '" + content + "'"
)
assert(
(content.head == '[') || !isEscapeTerminator(content.head),
"Escape sequence content contains an escape terminator: '" + content.headOption + "'"
)
} }
assert(isEscapeTerminator(terminator)) assert(isEscapeTerminator(terminator))
def makeString: String = ESC + content + terminator def makeString: String = ESC + content + terminator
@ -74,14 +85,20 @@ object Escapes extends Properties("Escapes") {
if (content.isEmpty) s"ESC (${terminator.toInt})" if (content.isEmpty) s"ESC (${terminator.toInt})"
else s"ESC ($content) (${terminator.toInt})" else s"ESC ($content) (${terminator.toInt})"
} }
private[this] def noEscape(s: String): String = s.replace(ESC, ' ') private[this] def noEscape(s: String): String = s.replace(ESC, ' ')
lazy val genEscapeSequence: Gen[EscapeSequence] = oneOf(genKnownSequence, genTwoCharacterSequence, genArbitraryEscapeSequence) lazy val genEscapeSequence: Gen[EscapeSequence] =
lazy val genEscapePair: Gen[EscapeAndNot] = for (esc <- genEscapeSequence; not <- genWithoutEscape) yield EscapeAndNot(esc, not) oneOf(genKnownSequence, genTwoCharacterSequence, genArbitraryEscapeSequence)
lazy val genEscapePair: Gen[EscapeAndNot] =
for (esc <- genEscapeSequence; not <- genWithoutEscape) yield EscapeAndNot(esc, not)
lazy val genEscapePairs: Gen[List[EscapeAndNot]] = listOf(genEscapePair) lazy val genEscapePairs: Gen[List[EscapeAndNot]] = listOf(genEscapePair)
lazy val genArbitraryEscapeSequence: Gen[EscapeSequence] = lazy val genArbitraryEscapeSequence: Gen[EscapeSequence] =
for (content <- genWithoutTerminator if !content.isEmpty; term <- genTerminator) yield new EscapeSequence("[" + content, term) for (content <- genWithoutTerminator if !content.isEmpty; term <- genTerminator)
yield new EscapeSequence("[" + content, term)
lazy val genKnownSequence: Gen[EscapeSequence] = lazy val genKnownSequence: Gen[EscapeSequence] =
oneOf((misc ++ setGraphicsMode ++ setMode ++ resetMode).map(toEscapeSequence)) oneOf((misc ++ setGraphicsMode ++ setMode ++ resetMode).map(toEscapeSequence))
@ -91,14 +108,15 @@ object Escapes extends Properties("Escapes") {
lazy val misc = Seq("14;23H", "5;3f", "2A", "94B", "19C", "85D", "s", "u", "2J", "K") lazy val misc = Seq("14;23H", "5;3f", "2A", "94B", "19C", "85D", "s", "u", "2J", "K")
lazy val setGraphicsMode: Seq[String] = lazy val setGraphicsMode: Seq[String] =
for (txt <- 0 to 8; fg <- 30 to 37; bg <- 40 to 47) yield txt.toString + ";" + fg.toString + ";" + bg.toString + "m" for (txt <- 0 to 8; fg <- 30 to 37; bg <- 40 to 47)
yield txt.toString + ";" + fg.toString + ";" + bg.toString + "m"
lazy val resetMode = setModeLike('I') lazy val resetMode = setModeLike('I')
lazy val setMode = setModeLike('h') lazy val setMode = setModeLike('h')
def setModeLike(term: Char): Seq[String] = (0 to 19).map(i => "=" + i.toString + term) def setModeLike(term: Char): Seq[String] = (0 to 19).map(i => "=" + i.toString + term)
lazy val genWithoutTerminator = lazy val genWithoutTerminator =
genRawString.map(_.filter { c => !isEscapeTerminator(c) && (c != '[') }) genRawString.map(_.filter(c => !isEscapeTerminator(c) && (c != '[')))
lazy val genTwoCharacterSequence = lazy val genTwoCharacterSequence =
// 91 == [ which is the CSI escape sequence. // 91 == [ which is the CSI escape sequence.
@ -108,7 +126,8 @@ object Escapes extends Properties("Escapes") {
lazy val genWithoutEscape: Gen[String] = genRawString.map(noEscape) lazy val genWithoutEscape: Gen[String] = genRawString.map(noEscape)
def genWithRandomEscapes: Gen[String] = def genWithRandomEscapes: Gen[String] =
for (ls <- listOf(genRawString); end <- genRawString) yield ls.mkString("", ESC.toString, ESC.toString + end) for (ls <- listOf(genRawString); end <- genRawString)
yield ls.mkString("", ESC.toString, ESC.toString + end)
private def genRawString = Arbitrary.arbString.arbitrary private def genRawString = Arbitrary.arbString.arbitrary
} }

View File

@ -36,6 +36,7 @@ object LogWriterTest extends Properties("Log Writer") {
case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")" case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")"
case _ => "Not Log" case _ => "Not Log"
} }
/** /**
* Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are * Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are
* represented as separately written segments (ToLog instances). ToLog.`byCharacter` * represented as separately written segments (ToLog instances). ToLog.`byCharacter`
@ -46,7 +47,7 @@ object LogWriterTest extends Properties("Log Writer") {
val content = section.content val content = section.content
val normalized = Escape.newline(content, newLine) val normalized = Escape.newline(content, newLine)
if (section.byCharacter) if (section.byCharacter)
normalized.foreach { c => writer.write(c.toInt) } normalized.foreach(c => writer.write(c.toInt))
else else
writer.write(normalized) writer.write(normalized)
} }
@ -56,6 +57,7 @@ object LogWriterTest extends Properties("Log Writer") {
/** Converts the given lines in segments to lines as Strings for checking the results of the test.*/ /** Converts the given lines in segments to lines as Strings for checking the results of the test.*/
def toLines(lines: List[List[ToLog]]): List[String] = def toLines(lines: List[List[ToLog]]): List[String] =
lines.map(_.map(_.contentOnly).mkString) lines.map(_.map(_.contentOnly).mkString)
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/ /** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/
def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean = def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean =
(lines zip events) forall { (lines zip events) forall {
@ -64,10 +66,10 @@ object LogWriterTest extends Properties("Log Writer") {
} }
/* The following are implicit generators to build up a write sequence. /* The following are implicit generators to build up a write sequence.
* ToLog represents a written segment. NewLine represents one of the possible * ToLog represents a written segment. NewLine represents one of the possible
* newline separators. A List[ToLog] represents a full line and always includes a * newline separators. A List[ToLog] represents a full line and always includes a
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in * final ToLog with a trailing '\n'. Newline characters are otherwise not present in
* the `content` of a ToLog instance.*/ * the `content` of a ToLog instance.*/
implicit lazy val arbOut: Arbitrary[Output] = Arbitrary(genOutput) implicit lazy val arbOut: Arbitrary[Output] = Arbitrary(genOutput)
implicit lazy val arbLog: Arbitrary[ToLog] = Arbitrary(genLog) implicit lazy val arbLog: Arbitrary[ToLog] = Arbitrary(genLog)
@ -76,7 +78,8 @@ object LogWriterTest extends Properties("Log Writer") {
implicit lazy val arbLevel: Arbitrary[Level.Value] = Arbitrary(genLevel) implicit lazy val arbLevel: Arbitrary[Level.Value] = Arbitrary(genLevel)
implicit def genLine(implicit logG: Gen[ToLog]): Gen[List[ToLog]] = implicit def genLine(implicit logG: Gen[ToLog]): Gen[List[ToLog]] =
for (l <- listOf[ToLog](MaxSegments); last <- logG) yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse for (l <- listOf[ToLog](MaxSegments); last <- logG)
yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
implicit def genLog(implicit content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] = implicit def genLog(implicit content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] =
for (c <- content.arbitrary; by <- byChar.arbitrary) yield { for (c <- content.arbitrary; by <- byChar.arbitrary) yield {
@ -98,7 +101,7 @@ object LogWriterTest extends Properties("Log Writer") {
new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines
def listOf[T](max: Int)(implicit content: Arbitrary[T]): Gen[List[T]] = def listOf[T](max: Int)(implicit content: Arbitrary[T]): Gen[List[T]] =
Gen.choose(0, max) flatMap { sz => listOfN(sz, content.arbitrary) } Gen.choose(0, max) flatMap (sz => listOfN(sz, content.arbitrary))
} }
/* Helper classes*/ /* Helper classes*/
@ -107,35 +110,43 @@ final class Output(val lines: List[List[ToLog]], val level: Level.Value) {
override def toString = override def toString =
"Level: " + level + "\n" + lines.map(_.mkString).mkString("\n") "Level: " + level + "\n" + lines.map(_.mkString).mkString("\n")
} }
final class NewLine(val str: String) { final class NewLine(val str: String) {
override def toString = Escape(str) override def toString = Escape(str)
} }
final class ToLog(val content: String, val byCharacter: Boolean) { final class ToLog(val content: String, val byCharacter: Boolean) {
def contentOnly = Escape.newline(content, "") def contentOnly = Escape.newline(content, "")
override def toString = if (content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
override def toString =
if (content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
} }
/** Defines some utility methods for escaping unprintable characters.*/ /** Defines some utility methods for escaping unprintable characters.*/
object Escape { object Escape {
/** Escapes characters with code less than 20 by printing them as unicode escapes.*/ /** Escapes characters with code less than 20 by printing them as unicode escapes.*/
def apply(s: String): String = def apply(s: String): String = {
{ val builder = new StringBuilder(s.length)
val builder = new StringBuilder(s.length) for (c <- s) {
for (c <- s) { val char = c.toInt
val char = c.toInt def escaped = pad(char.toHexString.toUpperCase, 4, '0')
def escaped = pad(char.toHexString.toUpperCase, 4, '0') if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
}
builder.toString
}
def pad(s: String, minLength: Int, extra: Char) =
{
val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
} }
builder.toString
}
def pad(s: String, minLength: Int, extra: Char) = {
val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
}
/** Replaces a \n character at the end of a string `s` with `nl`.*/ /** Replaces a \n character at the end of a string `s` with `nl`.*/
def newline(s: String, nl: String): String = def newline(s: String, nl: String): String =
if (s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s if (s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s
} }
/** Records logging events for later retrieval.*/ /** Records logging events for later retrieval.*/
final class RecordingLogger extends BasicLogger { final class RecordingLogger extends BasicLogger {
private var events: List[LogEvent] = Nil private var events: List[LogEvent] = Nil
@ -147,6 +158,7 @@ final class RecordingLogger extends BasicLogger {
def log(level: Level.Value, message: => String): Unit = { events ::= new Log(level, message) } def log(level: Level.Value, message: => String): Unit = { events ::= new Log(level, message) }
def success(message: => String): Unit = { events ::= new Success(message) } def success(message: => String): Unit = { events ::= new Success(message) }
def logAll(es: Seq[LogEvent]): Unit = { events :::= es.toList } def logAll(es: Seq[LogEvent]): Unit = { events :::= es.toList }
def control(event: ControlEvent.Value, message: => String): Unit = { events ::= new ControlEvent(event, message) }
def control(event: ControlEvent.Value, message: => String): Unit =
events ::= new ControlEvent(event, message)
} }

View File

@ -24,7 +24,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
import sjsonnew.BasicJsonProtocol._ import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo") val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info)) LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
implicit val intShow: ShowLines[Int] = ShowLines({ (x: Int) => Vector(s"String representation of $x") }) implicit val intShow: ShowLines[Int] =
ShowLines((x: Int) => Vector(s"String representation of $x"))
log.registerStringCodec[Int] log.registerStringCodec[Int]
log.infoEvent(1) log.infoEvent(1)
} }
@ -33,7 +34,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
import sjsonnew.BasicJsonProtocol._ import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo") val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info)) LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
implicit val intArrayShow: ShowLines[Array[Int]] = ShowLines({ (x: Array[Int]) => Vector(s"String representation of ${x.mkString}") }) implicit val intArrayShow: ShowLines[Array[Int]] =
ShowLines((x: Array[Int]) => Vector(s"String representation of ${x.mkString}"))
log.registerStringCodec[Array[Int]] log.registerStringCodec[Array[Int]]
log.infoEvent(Array(1, 2, 3)) log.infoEvent(Array(1, 2, 3))
} }
@ -42,7 +44,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
import sjsonnew.BasicJsonProtocol._ import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo") val log = LogExchange.logger("foo")
LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info)) LogExchange.bindLoggerAppenders("foo", List(LogExchange.asyncStdout -> Level.Info))
implicit val intVectorShow: ShowLines[Vector[Vector[Int]]] = ShowLines({ (xss: Vector[Vector[Int]]) => Vector(s"String representation of $xss") }) implicit val intVectorShow: ShowLines[Vector[Vector[Int]]] =
ShowLines((xss: Vector[Vector[Int]]) => Vector(s"String representation of $xss"))
log.registerStringCodec[Vector[Vector[Int]]] log.registerStringCodec[Vector[Vector[Int]]]
log.infoEvent(Vector(Vector(1, 2, 3))) log.infoEvent(Vector(Vector(1, 2, 3)))
} }
@ -51,7 +54,9 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
// this is passed into State normally // this is passed into State normally
val global0 = initialGlobalLogging val global0 = initialGlobalLogging
val full = global0.full val full = global0.full
(1 to 3).toList foreach { x => full.info(s"test$x") } (1 to 3).toList foreach { x =>
full.info(s"test$x")
}
} }
// This is done in Mainloop.scala // This is done in Mainloop.scala
@ -62,7 +67,7 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
val out = new PrintWriter(writer) val out = new PrintWriter(writer)
val g = global0.newAppender(global0.full, out, logBacking0) val g = global0.newAppender(global0.full, out, logBacking0)
val full = g.full val full = g.full
(1 to 3).toList foreach { x => full.info(s"newAppender $x") } (1 to 3).toList foreach (x => full.info(s"newAppender $x"))
assert(logBacking0.file.exists) assert(logBacking0.file.exists)
g g
} }
@ -71,7 +76,7 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
val out = new PrintWriter(writer) val out = new PrintWriter(writer)
val g = global1.newAppender(global1.full, out, logBacking1) val g = global1.newAppender(global1.full, out, logBacking1)
val full = g.full val full = g.full
(1 to 3).toList foreach { x => full.info(s"newAppender $x") } (1 to 3).toList foreach (x => full.info(s"newAppender $x"))
// println(logBacking.file) // println(logBacking.file)
// print("Press enter to continue. ") // print("Press enter to continue. ")
// System.console.readLine // System.console.readLine
@ -81,6 +86,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
val console = ConsoleOut.systemOut val console = ConsoleOut.systemOut
def initialGlobalLogging: GlobalLogging = GlobalLogging.initial( def initialGlobalLogging: GlobalLogging = GlobalLogging.initial(
MainAppender.globalDefault(console), File.createTempFile("sbt", ".log"), console MainAppender.globalDefault(console),
File.createTempFile("sbt", ".log"),
console
) )
} }

View File

@ -3,10 +3,9 @@ package sbt.internal.util
import sbt.util._ import sbt.util._
object TestLogger { object TestLogger {
def apply[T](f: Logger => T): T = def apply[T](f: Logger => T): T = {
{ val log = new BufferedLogger(ConsoleLogger())
val log = new BufferedLogger(ConsoleLogger()) log.setLevel(Level.Debug)
log.setLevel(Level.Debug) log.bufferQuietly(f(log))
log.bufferQuietly(f(log)) }
} }
}

View File

@ -6,6 +6,7 @@ package sbt.internal.util
import Relation._ import Relation._
object Relation { object Relation {
/** Constructs a new immutable, finite relation that is initially empty. */ /** Constructs a new immutable, finite relation that is initially empty. */
def empty[A, B]: Relation[A, B] = make(Map.empty, Map.empty) def empty[A, B]: Relation[A, B] = make(Map.empty, Map.empty)
@ -13,17 +14,18 @@ object Relation {
* Constructs a [[Relation]] from underlying `forward` and `reverse` representations, without checking that they are consistent. * Constructs a [[Relation]] from underlying `forward` and `reverse` representations, without checking that they are consistent.
* This is a low-level constructor and the alternatives [[empty]] and [[reconstruct]] should be preferred. * This is a low-level constructor and the alternatives [[empty]] and [[reconstruct]] should be preferred.
*/ */
def make[A, B](forward: Map[A, Set[B]], reverse: Map[B, Set[A]]): Relation[A, B] = new MRelation(forward, reverse) def make[A, B](forward: Map[A, Set[B]], reverse: Map[B, Set[A]]): Relation[A, B] =
new MRelation(forward, reverse)
/** Constructs a relation such that for every entry `_1 -> _2s` in `forward` and every `_2` in `_2s`, `(_1, _2)` is in the relation. */ /** Constructs a relation such that for every entry `_1 -> _2s` in `forward` and every `_2` in `_2s`, `(_1, _2)` is in the relation. */
def reconstruct[A, B](forward: Map[A, Set[B]]): Relation[A, B] = def reconstruct[A, B](forward: Map[A, Set[B]]): Relation[A, B] = {
{ val reversePairs = for ((a, bs) <- forward.view; b <- bs.view) yield (b, a)
val reversePairs = for ((a, bs) <- forward.view; b <- bs.view) yield (b, a) val reverse = (Map.empty[B, Set[A]] /: reversePairs) { case (m, (b, a)) => add(m, b, a :: Nil) }
val reverse = (Map.empty[B, Set[A]] /: reversePairs) { case (m, (b, a)) => add(m, b, a :: Nil) } make(forward filter { case (a, bs) => bs.nonEmpty }, reverse)
make(forward filter { case (a, bs) => bs.nonEmpty }, reverse) }
}
def merge[A, B](rels: Traversable[Relation[A, B]]): Relation[A, B] = (Relation.empty[A, B] /: rels)(_ ++ _) def merge[A, B](rels: Traversable[Relation[A, B]]): Relation[A, B] =
(Relation.empty[A, B] /: rels)(_ ++ _)
private[sbt] def remove[X, Y](map: M[X, Y], from: X, to: Y): M[X, Y] = private[sbt] def remove[X, Y](map: M[X, Y], from: X, to: Y): M[X, Y] =
map.get(from) match { map.get(from) match {
@ -34,46 +36,61 @@ object Relation {
} }
private[sbt] def combine[X, Y](a: M[X, Y], b: M[X, Y]): M[X, Y] = private[sbt] def combine[X, Y](a: M[X, Y], b: M[X, Y]): M[X, Y] =
(a /: b) { (map, mapping) => add(map, mapping._1, mapping._2) } (a /: b)((map, mapping) => add(map, mapping._1, mapping._2))
private[sbt] def add[X, Y](map: M[X, Y], from: X, to: Traversable[Y]): M[X, Y] = private[sbt] def add[X, Y](map: M[X, Y], from: X, to: Traversable[Y]): M[X, Y] =
map.updated(from, get(map, from) ++ to) map.updated(from, get(map, from) ++ to)
private[sbt] def get[X, Y](map: M[X, Y], t: X): Set[Y] = map.getOrElse(t, Set.empty[Y]) private[sbt] def get[X, Y](map: M[X, Y], t: X): Set[Y] = map.getOrElse(t, Set.empty[Y])
private[sbt]type M[X, Y] = Map[X, Set[Y]] private[sbt] type M[X, Y] = Map[X, Set[Y]]
} }
/** Binary relation between A and B. It is a set of pairs (_1, _2) for _1 in A, _2 in B. */ /** Binary relation between A and B. It is a set of pairs (_1, _2) for _1 in A, _2 in B. */
trait Relation[A, B] { trait Relation[A, B] {
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */ /** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
def forward(_1: A): Set[B] def forward(_1: A): Set[B]
/** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */ /** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */
def reverse(_2: B): Set[A] def reverse(_2: B): Set[A]
/** Includes `pair` in the relation. */ /** Includes `pair` in the relation. */
def +(pair: (A, B)): Relation[A, B] def +(pair: (A, B)): Relation[A, B]
/** Includes `(a, b)` in the relation. */ /** Includes `(a, b)` in the relation. */
def +(a: A, b: B): Relation[A, B] def +(a: A, b: B): Relation[A, B]
/** Includes in the relation `(a, b)` for all `b` in `bs`. */ /** Includes in the relation `(a, b)` for all `b` in `bs`. */
def +(a: A, bs: Traversable[B]): Relation[A, B] def +(a: A, bs: Traversable[B]): Relation[A, B]
/** Returns the union of the relation `r` with this relation. */ /** Returns the union of the relation `r` with this relation. */
def ++(r: Relation[A, B]): Relation[A, B] def ++(r: Relation[A, B]): Relation[A, B]
/** Includes the given pairs in this relation. */ /** Includes the given pairs in this relation. */
def ++(rs: Traversable[(A, B)]): Relation[A, B] def ++(rs: Traversable[(A, B)]): Relation[A, B]
/** Removes all elements `(_1, _2)` for all `_1` in `_1s` from this relation. */ /** Removes all elements `(_1, _2)` for all `_1` in `_1s` from this relation. */
def --(_1s: Traversable[A]): Relation[A, B] def --(_1s: Traversable[A]): Relation[A, B]
/** Removes all `pairs` from this relation. */ /** Removes all `pairs` from this relation. */
def --(pairs: TraversableOnce[(A, B)]): Relation[A, B] def --(pairs: TraversableOnce[(A, B)]): Relation[A, B]
/** Removes all `relations` from this relation. */ /** Removes all `relations` from this relation. */
def --(relations: Relation[A, B]): Relation[A, B] def --(relations: Relation[A, B]): Relation[A, B]
/** Removes all pairs `(_1, _2)` from this relation. */ /** Removes all pairs `(_1, _2)` from this relation. */
def -(_1: A): Relation[A, B] def -(_1: A): Relation[A, B]
/** Removes `pair` from this relation. */ /** Removes `pair` from this relation. */
def -(pair: (A, B)): Relation[A, B] def -(pair: (A, B)): Relation[A, B]
/** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */ /** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */
def _1s: collection.Set[A] def _1s: collection.Set[A]
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */ /** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
def _2s: collection.Set[B] def _2s: collection.Set[B]
/** Returns the number of pairs in this relation */ /** Returns the number of pairs in this relation */
def size: Int def size: Int
@ -110,10 +127,12 @@ trait Relation[A, B] {
* The value associated with a given `_2` is the set of all `_1`s such that `(_1, _2)` is in this relation. * The value associated with a given `_2` is the set of all `_1`s such that `(_1, _2)` is in this relation.
*/ */
def reverseMap: Map[B, Set[A]] def reverseMap: Map[B, Set[A]]
} }
// Note that we assume without checking that fwd and rev are consistent. // Note that we assume without checking that fwd and rev are consistent.
private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) extends Relation[A, B] { private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]])
extends Relation[A, B] {
def forwardMap = fwd def forwardMap = fwd
def reverseMap = rev def reverseMap = rev
@ -125,25 +144,30 @@ private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ex
def size = (fwd.valuesIterator map (_.size)).sum def size = (fwd.valuesIterator map (_.size)).sum
def all: Traversable[(A, B)] = fwd.iterator.flatMap { case (a, bs) => bs.iterator.map(b => (a, b)) }.toTraversable def all: Traversable[(A, B)] =
fwd.iterator.flatMap { case (a, bs) => bs.iterator.map(b => (a, b)) }.toTraversable
def +(pair: (A, B)) = this + (pair._1, Set(pair._2)) def +(pair: (A, B)) = this + (pair._1, Set(pair._2))
def +(from: A, to: B) = this + (from, to :: Nil) def +(from: A, to: B) = this + (from, to :: Nil)
def +(from: A, to: Traversable[B]) = if (to.isEmpty) this else def +(from: A, to: Traversable[B]) =
new MRelation(add(fwd, from, to), (rev /: to) { (map, t) => add(map, t, from :: Nil) }) if (to.isEmpty) this
else new MRelation(add(fwd, from, to), (rev /: to)((map, t) => add(map, t, from :: Nil)))
def ++(rs: Traversable[(A, B)]) = ((this: Relation[A, B]) /: rs) { _ + _ } def ++(rs: Traversable[(A, B)]) = ((this: Relation[A, B]) /: rs) { _ + _ }
def ++(other: Relation[A, B]) = new MRelation[A, B](combine(fwd, other.forwardMap), combine(rev, other.reverseMap)) def ++(other: Relation[A, B]) =
new MRelation[A, B](combine(fwd, other.forwardMap), combine(rev, other.reverseMap))
def --(ts: Traversable[A]): Relation[A, B] = ((this: Relation[A, B]) /: ts) { _ - _ } def --(ts: Traversable[A]): Relation[A, B] = ((this: Relation[A, B]) /: ts) { _ - _ }
def --(pairs: TraversableOnce[(A, B)]): Relation[A, B] = ((this: Relation[A, B]) /: pairs) { _ - _ } def --(pairs: TraversableOnce[(A, B)]): Relation[A, B] = ((this: Relation[A, B]) /: pairs)(_ - _)
def --(relations: Relation[A, B]): Relation[A, B] = --(relations.all) def --(relations: Relation[A, B]): Relation[A, B] = --(relations.all)
def -(pair: (A, B)): Relation[A, B] = def -(pair: (A, B)): Relation[A, B] =
new MRelation(remove(fwd, pair._1, pair._2), remove(rev, pair._2, pair._1)) new MRelation(remove(fwd, pair._1, pair._2), remove(rev, pair._2, pair._1))
def -(t: A): Relation[A, B] = def -(t: A): Relation[A, B] =
fwd.get(t) match { fwd.get(t) match {
case Some(rs) => case Some(rs) =>
val upRev = (rev /: rs) { (map, r) => remove(map, r, t) } val upRev = (rev /: rs)((map, r) => remove(map, r, t))
new MRelation(fwd - t, upRev) new MRelation(fwd - t, upRev)
case None => this case None => this
} }
@ -155,18 +179,21 @@ private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]]) ex
(Relation.empty[A, B] ++ y, Relation.empty[A, B] ++ n) (Relation.empty[A, B] ++ y, Relation.empty[A, B] ++ n)
} }
def groupBy[K](discriminator: ((A, B)) => K): Map[K, Relation[A, B]] = all.groupBy(discriminator) mapValues { Relation.empty[A, B] ++ _ } def groupBy[K](discriminator: ((A, B)) => K): Map[K, Relation[A, B]] =
all.groupBy(discriminator) mapValues { Relation.empty[A, B] ++ _ }
def contains(a: A, b: B): Boolean = forward(a)(b) def contains(a: A, b: B): Boolean = forward(a)(b)
override def equals(other: Any) = other match { override def equals(other: Any) = other match {
// We assume that the forward and reverse maps are consistent, so we only use the forward map // We assume that the forward and reverse maps are consistent, so we only use the forward map
// for equality. Note that key -> Empty is semantically the same as key not existing. // for equality. Note that key -> Empty is semantically the same as key not existing.
case o: MRelation[A, B] => forwardMap.filterNot(_._2.isEmpty) == o.forwardMap.filterNot(_._2.isEmpty) case o: MRelation[A, B] =>
case _ => false forwardMap.filterNot(_._2.isEmpty) == o.forwardMap.filterNot(_._2.isEmpty)
case _ => false
} }
override def hashCode = fwd.filterNot(_._2.isEmpty).hashCode() override def hashCode = fwd.filterNot(_._2.isEmpty).hashCode()
override def toString = all.map { case (a, b) => a + " -> " + b }.mkString("Relation [", ", ", "]") override def toString =
all.map { case (a, b) => a + " -> " + b }.mkString("Relation [", ", ", "]")
} }

View File

@ -11,21 +11,20 @@ object RelationTest extends Properties("Relation") {
val r = Relation.empty[Int, Double] ++ pairs val r = Relation.empty[Int, Double] ++ pairs
check(r, pairs) check(r, pairs)
} }
def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) = def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) = {
{ val _1s = pairs.map(_._1).toSet
val _1s = pairs.map(_._1).toSet val _2s = pairs.map(_._2).toSet
val _2s = pairs.map(_._2).toSet
r._1s == _1s && r.forwardMap.keySet == _1s && r._1s == _1s && r.forwardMap.keySet == _1s &&
r._2s == _2s && r.reverseMap.keySet == _2s && r._2s == _2s && r.reverseMap.keySet == _2s &&
pairs.forall { pairs.forall {
case (a, b) => case (a, b) =>
(r.forward(a) contains b) && (r.forward(a) contains b) &&
(r.reverse(b) contains a) && (r.reverse(b) contains a) &&
(r.forwardMap(a) contains b) && (r.forwardMap(a) contains b) &&
(r.reverseMap(b) contains a) (r.reverseMap(b) contains a)
}
} }
}
property("Does not contain removed entries") = forAll { (pairs: List[(Int, Double, Boolean)]) => property("Does not contain removed entries") = forAll { (pairs: List[(Int, Double, Boolean)]) =>
val add = pairs.map { case (a, b, c) => (a, b) } val add = pairs.map { case (a, b, c) => (a, b) }
@ -39,17 +38,17 @@ object RelationTest extends Properties("Relation") {
all(removeCoarse) { rem => all(removeCoarse) { rem =>
("_1s does not contain removed" |: (!r._1s.contains(rem))) && ("_1s does not contain removed" |: (!r._1s.contains(rem))) &&
("Forward does not contain removed" |: r.forward(rem).isEmpty) && ("Forward does not contain removed" |: r.forward(rem).isEmpty) &&
("Forward map does not contain removed" |: !r.forwardMap.contains(rem)) && ("Forward map does not contain removed" |: !r.forwardMap.contains(rem)) &&
("Removed is not a value in reverse map" |: !r.reverseMap.values.toSet.contains(rem)) ("Removed is not a value in reverse map" |: !r.reverseMap.values.toSet.contains(rem))
} && } &&
all(removeFine) { all(removeFine) {
case (a, b) => case (a, b) =>
("Forward does not contain removed" |: (!r.forward(a).contains(b))) && ("Forward does not contain removed" |: (!r.forward(a).contains(b))) &&
("Reverse does not contain removed" |: (!r.reverse(b).contains(a))) && ("Reverse does not contain removed" |: (!r.reverse(b).contains(a))) &&
("Forward map does not contain removed" |: (notIn(r.forwardMap, a, b))) && ("Forward map does not contain removed" |: (notIn(r.forwardMap, a, b))) &&
("Reverse map does not contain removed" |: (notIn(r.reverseMap, b, a))) ("Reverse map does not contain removed" |: (notIn(r.reverseMap, b, a)))
} }
} }
property("Groups correctly") = forAll { (entries: List[(Int, Double)], randomInt: Int) => property("Groups correctly") = forAll { (entries: List[(Int, Double)], randomInt: Int) =>
@ -75,10 +74,10 @@ object RelationTest extends Properties("Relation") {
object EmptyRelationTest extends Properties("Empty relation") { object EmptyRelationTest extends Properties("Empty relation") {
lazy val e = Relation.empty[Int, Double] lazy val e = Relation.empty[Int, Double]
property("Forward empty") = forAll { (i: Int) => e.forward(i).isEmpty } property("Forward empty") = forAll((i: Int) => e.forward(i).isEmpty)
property("Reverse empty") = forAll { (i: Double) => e.reverse(i).isEmpty } property("Reverse empty") = forAll((i: Double) => e.reverse(i).isEmpty)
property("Forward map empty") = e.forwardMap.isEmpty property("Forward map empty") = e.forwardMap.isEmpty
property("Reverse map empty") = e.reverseMap.isEmpty property("Reverse map empty") = e.reverseMap.isEmpty
property("_1 empty") = e._1s.isEmpty property("_1 empty") = e._1s.isEmpty
property("_2 empty") = e._2s.isEmpty property("_2 empty") = e._2s.isEmpty
} }

View File

@ -7,4 +7,4 @@ package scripted
object CommentHandler extends BasicStatementHandler { object CommentHandler extends BasicStatementHandler {
def apply(command: String, args: List[String]) = () def apply(command: String, args: List[String]) = ()
} }

View File

@ -58,18 +58,20 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
val lines1 = IO.readLines(fromString(file1)) val lines1 = IO.readLines(fromString(file1))
val lines2 = IO.readLines(fromString(file2)) val lines2 = IO.readLines(fromString(file2))
if (lines1 != lines2) if (lines1 != lines2)
scriptError("File contents are different:\n" + lines1.mkString("\n") + "\nAnd:\n" + lines2.mkString("\n")) scriptError(
"File contents are different:\n" + lines1.mkString("\n") +
"\nAnd:\n" + lines2.mkString("\n")
)
} }
def newer(a: String, b: String): Unit = def newer(a: String, b: String): Unit = {
{ val pathA = fromString(a)
val pathA = fromString(a) val pathB = fromString(b)
val pathB = fromString(b) val isNewer = pathA.exists && (!pathB.exists || pathA.lastModified > pathB.lastModified)
val isNewer = pathA.exists && (!pathB.exists || pathA.lastModified > pathB.lastModified) if (!isNewer) {
if (!isNewer) { scriptError(s"$pathA is not newer than $pathB")
scriptError(s"$pathA is not newer than $pathB")
}
} }
}
def exists(paths: List[String]): Unit = { def exists(paths: List[String]): Unit = {
val notPresent = fromStrings(paths).filter(!_.exists) val notPresent = fromStrings(paths).filter(!_.exists)
if (notPresent.nonEmpty) if (notPresent.nonEmpty)
@ -127,9 +129,16 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
IO.copy(mapped.init pair map) IO.copy(mapped.init pair map)
() ()
} }
def wrongArguments(args: List[String]): Unit = def wrongArguments(args: List[String]): Unit =
scriptError("Command '" + commandName + "' does not accept arguments (found '" + spaced(args) + "').") scriptError(
"Command '" + commandName + "' does not accept arguments (found '" + spaced(args) + "')."
)
def wrongArguments(requiredArgs: String, args: List[String]): Unit = def wrongArguments(requiredArgs: String, args: List[String]): Unit =
scriptError("Wrong number of arguments to " + commandName + " command. " + requiredArgs + " required, found: '" + spaced(args) + "'.") scriptError(
"Wrong number of arguments to " + commandName + " command. " +
requiredArgs + " required, found: '" + spaced(args) + "'."
)
} }
} }

View File

@ -7,13 +7,12 @@ package scripted
final class FilteredLoader(parent: ClassLoader) extends ClassLoader(parent) { final class FilteredLoader(parent: ClassLoader) extends ClassLoader(parent) {
@throws(classOf[ClassNotFoundException]) @throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[_] = override final def loadClass(className: String, resolve: Boolean): Class[_] = {
{ if (className.startsWith("java.") || className.startsWith("javax."))
if (className.startsWith("java.") || className.startsWith("javax.")) super.loadClass(className, resolve)
super.loadClass(className, resolve) else
else throw new ClassNotFoundException(className)
throw new ClassNotFoundException(className) }
}
override def getResources(name: String) = null override def getResources(name: String) = null
override def getResource(name: String) = null override def getResource(name: String) = null
} }

View File

@ -2,4 +2,4 @@ package sbt.internal.scripted
trait HandlersProvider { trait HandlersProvider {
def getHandlers(config: ScriptConfig): Map[Char, StatementHandler] def getHandlers(config: ScriptConfig): Map[Char, StatementHandler]
} }

View File

@ -6,7 +6,7 @@ package internal
package scripted package scripted
final class TestException(statement: Statement, msg: String, exception: Throwable) final class TestException(statement: Statement, msg: String, exception: Throwable)
extends RuntimeException(statement.linePrefix + " " + msg, exception) extends RuntimeException(statement.linePrefix + " " + msg, exception)
class ScriptRunner { class ScriptRunner {
import scala.collection.mutable.HashMap import scala.collection.mutable.HashMap
@ -15,14 +15,16 @@ class ScriptRunner {
def processStatement(handler: StatementHandler, statement: Statement): Unit = { def processStatement(handler: StatementHandler, statement: Statement): Unit = {
val state = states(handler).asInstanceOf[handler.State] val state = states(handler).asInstanceOf[handler.State]
val nextState = val nextState =
try { Right(handler(statement.command, statement.arguments, state)) } try { Right(handler(statement.command, statement.arguments, state)) } catch {
catch { case e: Exception => Left(e) } case e: Exception => Left(e)
}
nextState match { nextState match {
case Left(err) => case Left(err) =>
if (statement.successExpected) { if (statement.successExpected) {
err match { err match {
case t: TestFailed => throw new TestException(statement, "Command failed: " + t.getMessage, null) case t: TestFailed =>
case _ => throw new TestException(statement, "Command failed", err) throw new TestException(statement, "Command failed: " + t.getMessage, null)
case _ => throw new TestException(statement, "Command failed", err)
} }
} else } else
() ()
@ -36,13 +38,12 @@ class ScriptRunner {
val handlers = Set() ++ statements.map(_._1) val handlers = Set() ++ statements.map(_._1)
try { try {
handlers.foreach { handler => states(handler) = handler.initialState } handlers.foreach(handler => states(handler) = handler.initialState)
statements foreach (Function.tupled(processStatement)) statements foreach (Function.tupled(processStatement))
} finally { } finally {
for (handler <- handlers; state <- states.get(handler)) { for (handler <- handlers; state <- states.get(handler)) {
try { handler.finish(state.asInstanceOf[handler.State]) } try { handler.finish(state.asInstanceOf[handler.State]) } catch { case e: Exception => () }
catch { case e: Exception => () }
} }
} }
} }
} }

View File

@ -12,7 +12,12 @@ import sbt.internal.io.Resources
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
object ScriptedRunnerImpl { object ScriptedRunnerImpl {
def run(resourceBaseDirectory: File, bufferLog: Boolean, tests: Array[String], handlersProvider: HandlersProvider): Unit = { def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
handlersProvider: HandlersProvider
): Unit = {
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, handlersProvider) val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, handlersProvider)
val logger = newLogger val logger = newLogger
val allTests = get(tests, resourceBaseDirectory, logger) flatMap { val allTests = get(tests, resourceBaseDirectory, logger) flatMap {
@ -36,15 +41,18 @@ object ScriptedRunnerImpl {
ScriptedTest(group, name) ScriptedTest(group, name)
} }
private[sbt] val generateId: AtomicInteger = new AtomicInteger private[sbt] val generateId: AtomicInteger = new AtomicInteger
private[sbt] def newLogger: ManagedLogger = private[sbt] def newLogger: ManagedLogger = {
{ val loggerName = "scripted-" + generateId.incrementAndGet
val loggerName = "scripted-" + generateId.incrementAndGet val x = LogExchange.logger(loggerName)
val x = LogExchange.logger(loggerName) x
x }
}
} }
final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handlersProvider: HandlersProvider) { final class ScriptedTests(
resourceBaseDirectory: File,
bufferLog: Boolean,
handlersProvider: HandlersProvider
) {
private val testResources = new Resources(resourceBaseDirectory) private val testResources = new Resources(resourceBaseDirectory)
private val consoleAppender: ConsoleAppender = ConsoleAppender() private val consoleAppender: ConsoleAppender = ConsoleAppender()
@ -53,85 +61,96 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[() => Option[String]] = def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[() => Option[String]] =
scriptedTest(group, name, Logger.xlog2Log(log)) scriptedTest(group, name, Logger.xlog2Log(log))
def scriptedTest(group: String, name: String, log: ManagedLogger): Seq[() => Option[String]] = def scriptedTest(group: String, name: String, log: ManagedLogger): Seq[() => Option[String]] =
scriptedTest(group, name, { _ => () }, log) scriptedTest(group, name, (_ => ()), log)
def scriptedTest(group: String, name: String, prescripted: File => Unit, log: ManagedLogger): Seq[() => Option[String]] = {
def scriptedTest(
group: String,
name: String,
prescripted: File => Unit,
log: ManagedLogger
): Seq[() => Option[String]] = {
for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield { for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield {
val g = groupDir.getName val g = groupDir.getName
val n = nme.getName val n = nme.getName
val str = s"$g / $n" val str = s"$g / $n"
() => { () =>
println("Running " + str) {
testResources.readWriteResourceDirectory(g, n) { testDirectory => println("Running " + str)
val disabled = new File(testDirectory, "disabled").isFile testResources.readWriteResourceDirectory(g, n) { testDirectory =>
if (disabled) { val disabled = new File(testDirectory, "disabled").isFile
log.info("D " + str + " [DISABLED]") if (disabled) {
None log.info("D " + str + " [DISABLED]")
} else { None
try { scriptedTest(str, testDirectory, prescripted, log); None } } else {
catch { case _: TestException | _: PendingTestSuccessException => Some(str) } try { scriptedTest(str, testDirectory, prescripted, log); None } catch {
case _: TestException | _: PendingTestSuccessException => Some(str)
}
}
} }
} }
}
} }
} }
private def scriptedTest(label: String, testDirectory: File, prescripted: File => Unit, log: ManagedLogger): Unit = private def scriptedTest(
{ label: String,
val buffered = BufferedAppender(consoleAppender) testDirectory: File,
LogExchange.unbindLoggerAppenders(log.name) prescripted: File => Unit,
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil) log: ManagedLogger
if (bufferLog) { ): Unit = {
buffered.record() val buffered = BufferedAppender(consoleAppender)
} LogExchange.unbindLoggerAppenders(log.name)
def createParser() = LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
{ if (bufferLog) {
// val fileHandler = new FileCommands(testDirectory) buffered.record()
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
val scriptConfig = new ScriptConfig(label, testDirectory, log)
new TestScriptParser(handlersProvider getHandlers scriptConfig)
}
val (file, pending) = {
val normal = new File(testDirectory, ScriptFilename)
val pending = new File(testDirectory, PendingScriptFilename)
if (pending.isFile) (pending, true) else (normal, false)
}
val pendingString = if (pending) " [PENDING]" else ""
def runTest() =
{
val run = new ScriptRunner
val parser = createParser()
run(parser.parse(file))
}
def testFailed(): Unit = {
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
log.error("x " + label + pendingString)
}
try {
prescripted(testDirectory)
runTest()
log.info("+ " + label + pendingString)
if (pending) throw new PendingTestSuccessException(label)
} catch {
case e: TestException =>
testFailed()
e.getCause match {
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
case _ => if (!pending) e.printStackTrace
}
if (!pending) throw e
case e: PendingTestSuccessException =>
testFailed()
log.error(" Mark as passing to remove this failure.")
throw e
case e: Exception =>
testFailed()
if (!pending) throw e
} finally { buffered.clearBuffer() }
} }
def createParser() = {
// val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
val scriptConfig = new ScriptConfig(label, testDirectory, log)
new TestScriptParser(handlersProvider getHandlers scriptConfig)
}
val (file, pending) = {
val normal = new File(testDirectory, ScriptFilename)
val pending = new File(testDirectory, PendingScriptFilename)
if (pending.isFile) (pending, true) else (normal, false)
}
val pendingString = if (pending) " [PENDING]" else ""
def runTest() = {
val run = new ScriptRunner
val parser = createParser()
run(parser.parse(file))
}
def testFailed(): Unit = {
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
log.error("x " + label + pendingString)
}
try {
prescripted(testDirectory)
runTest()
log.info("+ " + label + pendingString)
if (pending) throw new PendingTestSuccessException(label)
} catch {
case e: TestException =>
testFailed()
e.getCause match {
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
case _ => if (!pending) e.printStackTrace
}
if (!pending) throw e
case e: PendingTestSuccessException =>
testFailed()
log.error(" Mark as passing to remove this failure.")
throw e
case e: Exception =>
testFailed()
if (!pending) throw e
} finally { buffered.clearBuffer() }
}
} }
// object ScriptedTests extends ScriptedRunner { // object ScriptedTests extends ScriptedRunner {
@ -148,31 +167,30 @@ object ListTests {
import ListTests._ import ListTests._
final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) { final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) {
def filter = DirectoryFilter -- HiddenFileFilter def filter = DirectoryFilter -- HiddenFileFilter
def listTests: Seq[ScriptedTest] = def listTests: Seq[ScriptedTest] = {
{ list(baseDirectory, filter) flatMap { group =>
list(baseDirectory, filter) flatMap { group =>
val groupName = group.getName
listTests(group).map(ScriptedTest(groupName, _))
}
}
private[this] def listTests(group: File): Seq[String] =
{
val groupName = group.getName val groupName = group.getName
val allTests = list(group, filter).sortBy(_.getName) listTests(group).map(ScriptedTest(groupName, _))
if (allTests.isEmpty) {
log.warn("No tests in test group " + groupName)
Seq.empty
} else {
val (included, skipped) = allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty)
log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) {
log.warn("Tests skipped in group " + group.getName + ":")
skipped.foreach(testName => log.warn(" " + testName.getName))
}
Seq(included.map(_.getName): _*)
}
} }
}
private[this] def listTests(group: File): Seq[String] = {
val groupName = group.getName
val allTests = list(group, filter).sortBy(_.getName)
if (allTests.isEmpty) {
log.warn("No tests in test group " + groupName)
Seq.empty
} else {
val (included, skipped) =
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty)
log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) {
log.warn("Tests skipped in group " + group.getName + ":")
skipped.foreach(testName => log.warn(" " + testName.getName))
}
Seq(included.map(_.getName): _*)
}
}
} }
class PendingTestSuccessException(label: String) extends Exception { class PendingTestSuccessException(label: String) extends Exception {

View File

@ -15,7 +15,10 @@ trait StatementHandler {
trait BasicStatementHandler extends StatementHandler { trait BasicStatementHandler extends StatementHandler {
final type State = Unit final type State = Unit
final def initialState = () final def initialState = ()
final def apply(command: String, arguments: List[String], state: Unit): Unit = apply(command, arguments)
final def apply(command: String, arguments: List[String], state: Unit): Unit =
apply(command, arguments)
def apply(command: String, arguments: List[String]): Unit def apply(command: String, arguments: List[String]): Unit
def finish(state: Unit) = () def finish(state: Unit) = ()
} }
@ -23,4 +26,4 @@ trait BasicStatementHandler extends StatementHandler {
/** Use when a stack trace is not useful */ /** Use when a stack trace is not useful */
final class TestFailed(msg: String) extends RuntimeException(msg) { final class TestFailed(msg: String) extends RuntimeException(msg) {
override def fillInStackTrace = this override def fillInStackTrace = this
} }

View File

@ -19,8 +19,13 @@ successChar ::= '+' | '-'
word ::= [^ \[\]]+ word ::= [^ \[\]]+
comment ::= '#' \S* nl comment ::= '#' \S* nl
nl ::= '\r' \'n' | '\n' | '\r' | eof nl ::= '\r' \'n' | '\n' | '\r' | eof
*/ */
final case class Statement(command: String, arguments: List[String], successExpected: Boolean, line: Int) { final case class Statement(
command: String,
arguments: List[String],
successExpected: Boolean,
line: Int
) {
def linePrefix = "{line " + line + "} " def linePrefix = "{line " + line + "} "
} }
@ -41,43 +46,50 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
if (handlers.keys.exists(key => key == '+' || key == '-')) if (handlers.keys.exists(key => key == '+' || key == '-'))
sys.error("Start characters cannot be '+' or '-'") sys.error("Start characters cannot be '+' or '-'")
def parse(scriptFile: File): List[(StatementHandler, Statement)] = parse(read(scriptFile), Some(scriptFile.getAbsolutePath)) def parse(scriptFile: File): List[(StatementHandler, Statement)] =
parse(read(scriptFile), Some(scriptFile.getAbsolutePath))
def parse(script: String): List[(StatementHandler, Statement)] = parse(script, None) def parse(script: String): List[(StatementHandler, Statement)] = parse(script, None)
private def parse(script: String, label: Option[String]): List[(StatementHandler, Statement)] = private def parse(script: String, label: Option[String]): List[(StatementHandler, Statement)] = {
{ parseAll(statements, script) match {
parseAll(statements, script) match { case Success(result, next) => result
case Success(result, next) => result case err: NoSuccess => {
case err: NoSuccess => val labelString = label.map("'" + _ + "' ").getOrElse("")
{ sys.error("Could not parse test script, " + labelString + err.toString)
val labelString = label.map("'" + _ + "' ").getOrElse("")
sys.error("Could not parse test script, " + labelString + err.toString)
}
} }
} }
}
lazy val statements = rep1(space ~> statement <~ newline) lazy val statements = rep1(space ~> statement <~ newline)
def statement: Parser[(StatementHandler, Statement)] =
{ def statement: Parser[(StatementHandler, Statement)] = {
trait PositionalStatement extends Positional { trait PositionalStatement extends Positional {
def tuple: (StatementHandler, Statement) def tuple: (StatementHandler, Statement)
}
positioned {
val command = (word | err("expected command"))
val arguments = rep(space ~> (word | failure("expected argument")))
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^
{
case successExpected ~ start ~ command ~ arguments =>
new PositionalStatement {
def tuple = (handlers(start), new Statement(command, arguments, successExpected, pos.line))
}
}
} ^^ (_.tuple)
} }
positioned {
val command = (word | err("expected command"))
val arguments = rep(space ~> (word | failure("expected argument")))
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^ {
case successExpected ~ start ~ command ~ arguments =>
new PositionalStatement {
def tuple =
(handlers(start), new Statement(command, arguments, successExpected, pos.line))
}
}
} ^^ (_.tuple)
}
def successParser: Parser[Boolean] = ('+' ^^^ true) | ('-' ^^^ false) | success(true) def successParser: Parser[Boolean] = ('+' ^^^ true) | ('-' ^^^ false) | success(true)
def space: Parser[String] = """[ \t]*""".r def space: Parser[String] = """[ \t]*""".r
lazy val word: Parser[String] = ("\'" ~> "[^'\n\r]*".r <~ "\'") | ("\"" ~> "[^\"\n\r]*".r <~ "\"") | WordRegex
def startCharacterParser: Parser[Char] = elem("start character", handlers.contains _) | lazy val word: Parser[String] =
((newline | err("expected start character " + handlers.keys.mkString("(", "", ")"))) ~> failure("end of input")) ("\'" ~> "[^'\n\r]*".r <~ "\'") | ("\"" ~> "[^\"\n\r]*".r <~ "\"") | WordRegex
def startCharacterParser: Parser[Char] =
elem("start character", handlers.contains _) |
(
(newline | err("expected start character " + handlers.keys.mkString("(", "", ")")))
~> failure("end of input")
)
def newline = """\s*([\n\r]|$)""".r def newline = """\s*([\n\r]|$)""".r
} }

View File

@ -44,9 +44,15 @@ object Dependencies {
val scalatest = "org.scalatest" %% "scalatest" % "3.0.1" val scalatest = "org.scalatest" %% "scalatest" % "3.0.1"
val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4" val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value } val sjsonnew = Def.setting {
val sjsonnewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value } "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value
val sjsonnewMurmurhash = Def.setting { "com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value } }
val sjsonnewScalaJson = Def.setting {
"com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value
}
val sjsonnewMurmurhash = Def.setting {
"com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value
}
def log4jVersion = "2.8.1" def log4jVersion = "2.8.1"
val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4jVersion

View File

@ -1,3 +1,4 @@
addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.3") addSbtPlugin("org.scala-sbt" % "sbt-houserules" % "0.3.3")
addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0") addSbtPlugin("org.scala-sbt" % "sbt-contraband" % "0.3.0")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.17") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.17")
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.10")

View File

@ -21,6 +21,7 @@ case class Miss[O](update: O => Unit) extends CacheResult[O]
* A simple cache with keys of type `I` and values of type `O` * A simple cache with keys of type `I` and values of type `O`
*/ */
trait Cache[I, O] { trait Cache[I, O] {
/** /**
* Queries the cache backed with store `store` for key `key`. * Queries the cache backed with store `store` for key `key`.
*/ */
@ -59,7 +60,7 @@ object Cache {
val result = default(key) val result = default(key)
update(result) update(result)
result result
} }
def debug[I](label: String, cache: SingletonCache[I]): SingletonCache[I] = def debug[I](label: String, cache: SingletonCache[I]): SingletonCache[I] =
new SingletonCache[I] { new SingletonCache[I] {

View File

@ -3,5 +3,4 @@ package sbt.util
import sjsonnew.BasicJsonProtocol import sjsonnew.BasicJsonProtocol
object CacheImplicits extends CacheImplicits object CacheImplicits extends CacheImplicits
trait CacheImplicits extends BasicCacheImplicits trait CacheImplicits extends BasicCacheImplicits with BasicJsonProtocol
with BasicJsonProtocol

View File

@ -9,12 +9,15 @@ import sjsonnew.shaded.scalajson.ast.unsafe.JValue
/** A `CacheStore` is used by the caching infrastructure to persist cached information. */ /** A `CacheStore` is used by the caching infrastructure to persist cached information. */
abstract class CacheStore extends Input with Output { abstract class CacheStore extends Input with Output {
/** Delete the persisted information. */ /** Delete the persisted information. */
def delete(): Unit def delete(): Unit
} }
object CacheStore { object CacheStore {
implicit lazy val jvalueIsoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe) implicit lazy val jvalueIsoString: IsoString[JValue] =
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
/** Returns file-based CacheStore using standard JSON converter. */ /** Returns file-based CacheStore using standard JSON converter. */
def apply(cacheFile: File): CacheStore = file(cacheFile) def apply(cacheFile: File): CacheStore = file(cacheFile)
@ -25,6 +28,7 @@ object CacheStore {
/** Factory that can make new stores. */ /** Factory that can make new stores. */
abstract class CacheStoreFactory { abstract class CacheStoreFactory {
/** Create a new store. */ /** Create a new store. */
def make(identifier: String): CacheStore def make(identifier: String): CacheStore
@ -36,7 +40,8 @@ abstract class CacheStoreFactory {
} }
object CacheStoreFactory { object CacheStoreFactory {
implicit lazy val jvalueIsoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe) implicit lazy val jvalueIsoString: IsoString[JValue] =
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
/** Returns directory-based CacheStoreFactory using standard JSON converter. */ /** Returns directory-based CacheStoreFactory using standard JSON converter. */
def apply(base: File): CacheStoreFactory = directory(base) def apply(base: File): CacheStoreFactory = directory(base)
@ -46,29 +51,38 @@ object CacheStoreFactory {
} }
/** A factory that creates new stores persisted in `base`. */ /** A factory that creates new stores persisted in `base`. */
class DirectoryStoreFactory[J: IsoString](base: File, converter: SupportConverter[J]) extends CacheStoreFactory { class DirectoryStoreFactory[J: IsoString](base: File, converter: SupportConverter[J])
extends CacheStoreFactory {
IO.createDirectory(base) IO.createDirectory(base)
def make(identifier: String): CacheStore = new FileBasedStore(base / identifier, converter) def make(identifier: String): CacheStore = new FileBasedStore(base / identifier, converter)
def sub(identifier: String): CacheStoreFactory = new DirectoryStoreFactory(base / identifier, converter) def sub(identifier: String): CacheStoreFactory =
new DirectoryStoreFactory(base / identifier, converter)
} }
/** A `CacheStore` that persists information in `file`. */ /** A `CacheStore` that persists information in `file`. */
class FileBasedStore[J: IsoString](file: File, converter: SupportConverter[J]) extends CacheStore { class FileBasedStore[J: IsoString](file: File, converter: SupportConverter[J]) extends CacheStore {
IO.touch(file, setModified = false) IO.touch(file, setModified = false)
def read[T: JsonReader]() = Using.fileInputStream(file)(stream => new PlainInput(stream, converter).read()) def read[T: JsonReader]() =
Using.fileInputStream(file)(stream => new PlainInput(stream, converter).read())
def write[T: JsonWriter](value: T) = def write[T: JsonWriter](value: T) =
Using.fileOutputStream(append = false)(file)(stream => new PlainOutput(stream, converter).write(value)) Using.fileOutputStream(append = false)(file) { stream =>
new PlainOutput(stream, converter).write(value)
}
def delete() = IO.delete(file) def delete() = IO.delete(file)
def close() = () def close() = ()
} }
/** A store that reads from `inputStream` and writes to `outputStream`. */ /** A store that reads from `inputStream` and writes to `outputStream`. */
class StreamBasedStore[J: IsoString](inputStream: InputStream, outputStream: OutputStream, converter: SupportConverter[J]) extends CacheStore { class StreamBasedStore[J: IsoString](
inputStream: InputStream,
outputStream: OutputStream,
converter: SupportConverter[J]
) extends CacheStore {
def read[T: JsonReader]() = new PlainInput(inputStream, converter).read() def read[T: JsonReader]() = new PlainInput(inputStream, converter).read()
def write[T: JsonWriter](value: T) = new PlainOutput(outputStream, converter).write(value) def write[T: JsonWriter](value: T) = new PlainOutput(outputStream, converter).write(value)
def delete() = () def delete() = ()

View File

@ -32,7 +32,8 @@ object HashModifiedFileInfo {
private final case class PlainFile(file: File, exists: Boolean) extends PlainFileInfo private final case class PlainFile(file: File, exists: Boolean) extends PlainFileInfo
private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo private final case class FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo private final case class FileHash(file: File, hash: List[Byte]) extends HashFileInfo
private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long) extends HashModifiedFileInfo private final case class FileHashModified(file: File, hash: List[Byte], lastModified: Long)
extends HashModifiedFileInfo
final case class FilesInfo[F <: FileInfo] private (files: Set[F]) final case class FilesInfo[F <: FileInfo] private (files: Set[F])
object FilesInfo { object FilesInfo {
@ -52,7 +53,8 @@ object FileInfo {
type F <: FileInfo type F <: FileInfo
implicit def format: JsonFormat[F] implicit def format: JsonFormat[F]
implicit def formats: JsonFormat[FilesInfo[F]] = projectFormat(_.files, (fs: Set[F]) => FilesInfo(fs)) implicit def formats: JsonFormat[FilesInfo[F]] =
projectFormat(_.files, (fs: Set[F]) => FilesInfo(fs))
def apply(file: File): F def apply(file: File): F
def apply(files: Set[File]): FilesInfo[F] = FilesInfo(files map apply) def apply(files: Set[File]): FilesInfo[F] = FilesInfo(files map apply)
@ -113,7 +115,9 @@ object FileInfo {
implicit def apply(file: File): HashFileInfo = FileHash(file.getAbsoluteFile, computeHash(file)) implicit def apply(file: File): HashFileInfo = FileHash(file.getAbsoluteFile, computeHash(file))
private def computeHash(file: File): List[Byte] = try Hash(file).toList catch { case NonFatal(_) => Nil } private def computeHash(file: File): List[Byte] =
try Hash(file).toList
catch { case NonFatal(_) => Nil }
} }
object lastModified extends Style { object lastModified extends Style {
@ -140,7 +144,8 @@ object FileInfo {
} }
} }
implicit def apply(file: File): ModifiedFileInfo = FileModified(file.getAbsoluteFile, file.lastModified) implicit def apply(file: File): ModifiedFileInfo =
FileModified(file.getAbsoluteFile, file.lastModified)
} }
object exists extends Style { object exists extends Style {

View File

@ -7,7 +7,9 @@ import sbt.io.{ IO, Using }
trait Input extends Closeable { trait Input extends Closeable {
def read[T: JsonReader](): T def read[T: JsonReader](): T
def read[T: JsonReader](default: => T): T = try read[T]() catch { case NonFatal(_) => default } def read[T: JsonReader](default: => T): T =
try read[T]()
catch { case NonFatal(_) => default }
} }
class PlainInput[J: IsoString](input: InputStream, converter: SupportConverter[J]) extends Input { class PlainInput[J: IsoString](input: InputStream, converter: SupportConverter[J]) extends Input {

View File

@ -8,7 +8,8 @@ trait Output extends Closeable {
def write[T: JsonWriter](value: T): Unit def write[T: JsonWriter](value: T): Unit
} }
class PlainOutput[J: IsoString](output: OutputStream, converter: SupportConverter[J]) extends Output { class PlainOutput[J: IsoString](output: OutputStream, converter: SupportConverter[J])
extends Output {
val isoFormat: IsoString[J] = implicitly val isoFormat: IsoString[J] = implicitly
def write[T: JsonWriter](value: T) = { def write[T: JsonWriter](value: T) = {

View File

@ -14,11 +14,13 @@ import CacheImplicits._
* A cache that stores a single value. * A cache that stores a single value.
*/ */
trait SingletonCache[A] { trait SingletonCache[A] {
/** Reads the cache from the backing `from`. */ /** Reads the cache from the backing `from`. */
def read(from: Input): A def read(from: Input): A
/** Writes `value` to the backing `to`. */ /** Writes `value` to the backing `to`. */
def write(to: Output, value: A): Unit def write(to: Output, value: A): Unit
} }
object SingletonCache { object SingletonCache {

View File

@ -10,13 +10,17 @@ object StampedFormat extends BasicJsonProtocol {
withStamp(stamp(format))(format) withStamp(stamp(format))(format)
} }
def withStamp[T, S](stamp: S)(format: JsonFormat[T])(implicit formatStamp: JsonFormat[S], equivStamp: Equiv[S]): JsonFormat[T] = def withStamp[T, S](stamp: S)(format: JsonFormat[T])(
implicit formatStamp: JsonFormat[S],
equivStamp: Equiv[S]
): JsonFormat[T] =
new JsonFormat[T] { new JsonFormat[T] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T = override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T =
jsOpt match { jsOpt match {
case Some(js) => case Some(js) =>
val stampedLength = unbuilder.beginArray(js) val stampedLength = unbuilder.beginArray(js)
if (stampedLength != 2) sys.error(s"Expected JsArray of size 2, found JsArray of size $stampedLength.") if (stampedLength != 2)
sys.error(s"Expected JsArray of size 2, found JsArray of size $stampedLength.")
val readStamp = unbuilder.nextElement val readStamp = unbuilder.nextElement
val readValue = unbuilder.nextElement val readValue = unbuilder.nextElement
val actualStamp = formatStamp.read(Some(readStamp), unbuilder) val actualStamp = formatStamp.read(Some(readStamp), unbuilder)
@ -34,7 +38,10 @@ object StampedFormat extends BasicJsonProtocol {
builder.endArray() builder.endArray()
} }
} }
private def stamp[T](format: JsonFormat[T])(implicit mf: Manifest[JsonFormat[T]]): Int = typeHash(mf)
private def stamp[T](format: JsonFormat[T])(implicit mf: Manifest[JsonFormat[T]]): Int =
typeHash(mf)
private def typeHash[T](implicit mf: Manifest[T]) = mf.toString.hashCode private def typeHash[T](implicit mf: Manifest[T]) = mf.toString.hashCode
} }

View File

@ -13,7 +13,8 @@ import sbt.internal.util.UnitSpec
class CacheSpec extends UnitSpec { class CacheSpec extends UnitSpec {
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe) implicit val isoString: IsoString[JValue] =
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
"A cache" should "NOT throw an exception if read without being written previously" in { "A cache" should "NOT throw an exception if read without being written previously" in {
testCache[String, Int] { testCache[String, Int] {
@ -68,10 +69,12 @@ class CacheSpec extends UnitSpec {
} }
} }
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(implicit cache: Cache[K, V]): Unit = private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(
implicit cache: Cache[K, V]
): Unit =
IO.withTemporaryDirectory { tmp => IO.withTemporaryDirectory { tmp =>
val store = new FileBasedStore(tmp / "cache-store", Converter) val store = new FileBasedStore(tmp / "cache-store", Converter)
f(cache, store) f(cache, store)
} }
} }

View File

@ -42,7 +42,8 @@ class SingletonCacheSpec extends UnitSpec {
} }
} }
implicit val isoString: IsoString[JValue] = IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe) implicit val isoString: IsoString[JValue] =
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
"A singleton cache" should "throw an exception if read without being written previously" in { "A singleton cache" should "throw an exception if read without being written previously" in {
testCache[Int] { testCache[Int] {
@ -83,10 +84,12 @@ class SingletonCacheSpec extends UnitSpec {
} }
} }
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(implicit cache: SingletonCache[T]): Unit = private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(
implicit cache: SingletonCache[T]
): Unit =
IO.withTemporaryDirectory { tmp => IO.withTemporaryDirectory { tmp =>
val store = new FileBasedStore(tmp / "cache-store", Converter) val store = new FileBasedStore(tmp / "cache-store", Converter)
f(cache, store) f(cache, store)
} }
} }

View File

@ -10,28 +10,36 @@ object ChangeReport {
override def modified = files override def modified = files
override def markAllModified = this override def markAllModified = this
} }
def unmodified[T](files: Set[T]): ChangeReport[T] = def unmodified[T](files: Set[T]): ChangeReport[T] =
new EmptyChangeReport[T] { new EmptyChangeReport[T] {
override def checked = files override def checked = files
override def unmodified = files override def unmodified = files
} }
} }
/** The result of comparing some current set of objects against a previous set of objects.*/ /** The result of comparing some current set of objects against a previous set of objects.*/
trait ChangeReport[T] { trait ChangeReport[T] {
/** The set of all of the objects in the current set.*/ /** The set of all of the objects in the current set.*/
def checked: Set[T] def checked: Set[T]
/** All of the objects that are in the same state in the current and reference sets.*/ /** All of the objects that are in the same state in the current and reference sets.*/
def unmodified: Set[T] def unmodified: Set[T]
/** /**
* All checked objects that are not in the same state as the reference. This includes objects that are in both * All checked objects that are not in the same state as the reference. This includes objects that are in both
* sets but have changed and files that are only in one set. * sets but have changed and files that are only in one set.
*/ */
def modified: Set[T] // all changes, including added def modified: Set[T] // all changes, including added
/** All objects that are only in the current set.*/ /** All objects that are only in the current set.*/
def added: Set[T] def added: Set[T]
/** All objects only in the previous set*/ /** All objects only in the previous set*/
def removed: Set[T] def removed: Set[T]
def +++(other: ChangeReport[T]): ChangeReport[T] = new CompoundChangeReport(this, other) def +++(other: ChangeReport[T]): ChangeReport[T] = new CompoundChangeReport(this, other)
/** /**
* Generate a new report with this report's unmodified set included in the new report's modified set. The new report's * Generate a new report with this report's unmodified set included in the new report's modified set. The new report's
* unmodified set is empty. The new report's added, removed, and checked sets are the same as in this report. * unmodified set is empty. The new report's added, removed, and checked sets are the same as in this report.
@ -45,14 +53,16 @@ trait ChangeReport[T] {
def removed = ChangeReport.this.removed def removed = ChangeReport.this.removed
override def markAllModified = this override def markAllModified = this
} }
override def toString =
{ override def toString = {
val labels = List("Checked", "Modified", "Unmodified", "Added", "Removed") val labels = List("Checked", "Modified", "Unmodified", "Added", "Removed")
val sets = List(checked, modified, unmodified, added, removed) val sets = List(checked, modified, unmodified, added, removed)
val keyValues = labels.zip(sets).map { case (label, set) => label + ": " + set.mkString(", ") } val keyValues = labels.zip(sets).map { case (label, set) => label + ": " + set.mkString(", ") }
keyValues.mkString("Change report:\n\t", "\n\t", "") keyValues.mkString("Change report:\n\t", "\n\t", "")
} }
} }
class EmptyChangeReport[T] extends ChangeReport[T] { class EmptyChangeReport[T] extends ChangeReport[T] {
def checked = Set.empty[T] def checked = Set.empty[T]
def unmodified = Set.empty[T] def unmodified = Set.empty[T]
@ -61,7 +71,9 @@ class EmptyChangeReport[T] extends ChangeReport[T] {
def removed = Set.empty[T] def removed = Set.empty[T]
override def toString = "No changes" override def toString = "No changes"
} }
private class CompoundChangeReport[T](a: ChangeReport[T], b: ChangeReport[T]) extends ChangeReport[T] {
private class CompoundChangeReport[T](a: ChangeReport[T], b: ChangeReport[T])
extends ChangeReport[T] {
lazy val checked = a.checked ++ b.checked lazy val checked = a.checked ++ b.checked
lazy val unmodified = a.unmodified ++ b.unmodified lazy val unmodified = a.unmodified ++ b.unmodified
lazy val modified = a.modified ++ b.modified lazy val modified = a.modified ++ b.modified

View File

@ -43,7 +43,9 @@ object FileFunction {
* @param inStyle The strategy by which to detect state change in the input files from the previous run * @param inStyle The strategy by which to detect state change in the input files from the previous run
* @param action The work function, which receives a list of input files and returns a list of output files * @param action The work function, which receives a list of input files and returns a list of output files
*/ */
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style)(action: Set[File] => Set[File]): Set[File] => Set[File] = def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style)(
action: Set[File] => Set[File]
): Set[File] => Set[File] =
cached(cacheBaseDirectory, inStyle = inStyle, outStyle = defaultOutStyle)(action) cached(cacheBaseDirectory, inStyle = inStyle, outStyle = defaultOutStyle)(action)
/** /**
@ -64,8 +66,12 @@ object FileFunction {
* @param outStyle The strategy by which to detect state change in the output files from the previous run * @param outStyle The strategy by which to detect state change in the output files from the previous run
* @param action The work function, which receives a list of input files and returns a list of output files * @param action The work function, which receives a list of input files and returns a list of output files
*/ */
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(action: Set[File] => Set[File]): Set[File] => Set[File] = def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)((in, out) => action(in.checked)) action: Set[File] => Set[File]
): Set[File] => Set[File] =
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)(
(in, out) => action(in.checked)
)
/** /**
* Generic change-detection helper used to help build / artifact generation / * Generic change-detection helper used to help build / artifact generation /
@ -103,7 +109,9 @@ object FileFunction {
* @param inStyle The strategy by which to detect state change in the input files from the previous run * @param inStyle The strategy by which to detect state change in the input files from the previous run
* @param action The work function, which receives a list of input files and returns a list of output files * @param action The work function, which receives a list of input files and returns a list of output files
*/ */
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style)(action: UpdateFunction): Set[File] => Set[File] = def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style)(
action: UpdateFunction
): Set[File] => Set[File] =
cached(storeFactory, inStyle = inStyle, outStyle = defaultOutStyle)(action) cached(storeFactory, inStyle = inStyle, outStyle = defaultOutStyle)(action)
/** /**
@ -124,20 +132,21 @@ object FileFunction {
* @param outStyle The strategy by which to detect state change in the output files from the previous run * @param outStyle The strategy by which to detect state change in the output files from the previous run
* @param action The work function, which receives a list of input files and returns a list of output files * @param action The work function, which receives a list of input files and returns a list of output files
*/ */
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(action: UpdateFunction): Set[File] => Set[File] = def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
{ action: UpdateFunction
lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle) ): Set[File] => Set[File] = {
lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle) lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle)
inputs => lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle)
{ inputs =>
inCache(inputs) { inReport => {
outCache { outReport => inCache(inputs) { inReport =>
if (inReport.modified.isEmpty && outReport.modified.isEmpty) outCache { outReport =>
outReport.checked if (inReport.modified.isEmpty && outReport.modified.isEmpty)
else outReport.checked
action(inReport, outReport) else
} action(inReport, outReport)
} }
} }
} }
}
} }

View File

@ -13,6 +13,7 @@ import sjsonnew.JsonFormat
import sjsonnew.support.murmurhash.Hasher import sjsonnew.support.murmurhash.Hasher
object Tracked { object Tracked {
/** /**
* Creates a tracker that provides the last time it was evaluated. * Creates a tracker that provides the last time it was evaluated.
* If the function throws an exception. * If the function throws an exception.
@ -42,21 +43,24 @@ object Tracked {
* If 'useStartTime' is false, the recorded time is when the evaluated function completes. * 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. * In both cases, the timestamp is not updated if the function throws an exception.
*/ */
def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp = tstamp(CacheStore(cacheFile), useStartTime) def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp =
tstamp(CacheStore(cacheFile), useStartTime)
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/ /** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
def diffInputs(store: CacheStore, style: FileInfo.Style): Difference = def diffInputs(store: CacheStore, style: FileInfo.Style): Difference =
Difference.inputs(store, style) Difference.inputs(store, style)
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/ /** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
def diffInputs(cacheFile: File, style: FileInfo.Style): Difference = diffInputs(CacheStore(cacheFile), style) def diffInputs(cacheFile: File, style: FileInfo.Style): Difference =
diffInputs(CacheStore(cacheFile), style)
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/ /** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
def diffOutputs(store: CacheStore, style: FileInfo.Style): Difference = def diffOutputs(store: CacheStore, style: FileInfo.Style): Difference =
Difference.outputs(store, style) Difference.outputs(store, style)
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/ /** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
def diffOutputs(cacheFile: File, style: FileInfo.Style): Difference = diffOutputs(CacheStore(cacheFile), style) def diffOutputs(cacheFile: File, style: FileInfo.Style): Difference =
diffOutputs(CacheStore(cacheFile), style)
/** Creates a tracker that provides the output of the most recent invocation of the function */ /** Creates a tracker that provides the output of the most recent invocation of the function */
def lastOutput[I, O: JsonFormat](store: CacheStore)(f: (I, Option[O]) => O): I => O = { in => def lastOutput[I, O: JsonFormat](store: CacheStore)(f: (I, Option[O]) => O): I => O = { in =>
@ -84,7 +88,9 @@ object Tracked {
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
* }}} * }}}
*/ */
def outputChanged[A1: JsonFormat, A2](store: CacheStore)(f: (Boolean, A1) => A2): (() => A1) => A2 = p => { def outputChanged[A1: JsonFormat, A2](store: CacheStore)(
f: (Boolean, A1) => A2
): (() => A1) => A2 = p => {
val cache: SingletonCache[Long] = { val cache: SingletonCache[Long] = {
import CacheImplicits.LongJsonFormat import CacheImplicits.LongJsonFormat
implicitly implicitly
@ -131,7 +137,9 @@ object Tracked {
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
* }}} * }}}
*/ */
def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(f: (Boolean, I) => O): I => O = { in => def inputChanged[I: JsonFormat: SingletonCache, O](store: CacheStore)(
f: (Boolean, I) => O
): I => O = { in =>
val cache: SingletonCache[Long] = { val cache: SingletonCache[Long] = {
import CacheImplicits.LongJsonFormat import CacheImplicits.LongJsonFormat
implicitly implicitly
@ -159,7 +167,9 @@ object Tracked {
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet)) * cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
* }}} * }}}
*/ */
def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(f: (Boolean, I) => O): I => O = def inputChanged[I: JsonFormat: SingletonCache, O](cacheFile: File)(
f: (Boolean, I) => O
): I => O =
inputChanged(CacheStore(cacheFile))(f) inputChanged(CacheStore(cacheFile))(f)
private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) { private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) {
@ -186,23 +196,29 @@ object Tracked {
} }
trait Tracked { trait Tracked {
/** Cleans outputs and clears the cache.*/ /** Cleans outputs and clears the cache.*/
def clean(): Unit def clean(): Unit
} }
class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: JsonFormat[Long]) extends Tracked {
class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: JsonFormat[Long])
extends Tracked {
def clean() = store.delete() def clean() = store.delete()
/** /**
* Reads the previous timestamp, evaluates the provided function, * Reads the previous timestamp, evaluates the provided function,
* and then updates the timestamp if the function completes normally. * and then updates the timestamp if the function completes normally.
*/ */
def apply[T](f: Long => T): T = def apply[T](f: Long => T): T = {
{ val start = now()
val start = now() val result = f(readTimestamp)
val result = f(readTimestamp) store.write(if (useStartTime) start else now())
store.write(if (useStartTime) start else now()) result
result }
}
private def now() = System.currentTimeMillis private def now() = System.currentTimeMillis
def readTimestamp: Long = def readTimestamp: Long =
Try { store.read[Long] } getOrElse 0 Try { store.read[Long] } getOrElse 0
} }
@ -210,24 +226,30 @@ class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: J
@deprecated("Use Tracked.inputChanged and Tracked.outputChanged instead", "1.0.1") @deprecated("Use Tracked.inputChanged and Tracked.outputChanged instead", "1.0.1")
class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked { class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
def clean() = store.delete() def clean() = store.delete()
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value =>
{
if (uptodate(value))
ifUnchanged(value)
else {
update(value)
ifChanged(value)
}
}
def update(value: O): Unit = store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value)) def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value => {
if (uptodate(value))
ifUnchanged(value)
else {
update(value)
ifChanged(value)
}
}
def update(value: O): Unit =
store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
def uptodate(value: O): Boolean = { def uptodate(value: O): Boolean = {
val equiv: Equiv[O] = implicitly val equiv: Equiv[O] = implicitly
equiv.equiv(value, store.read[O]) equiv.equiv(value, store.read[O])
} }
} }
object Difference { object Difference {
def constructor(defineClean: Boolean, filesAreOutputs: Boolean): (CacheStore, FileInfo.Style) => Difference = def constructor(
defineClean: Boolean,
filesAreOutputs: Boolean
): (CacheStore, FileInfo.Style) => Difference =
(store, style) => new Difference(store, style, defineClean, filesAreOutputs) (store, style) => new Difference(store, style, defineClean, filesAreOutputs)
/** /**
@ -236,55 +258,63 @@ object Difference {
* before and after running the function. * before and after running the function.
*/ */
val outputs = constructor(true, true) val outputs = constructor(true, true)
/** /**
* Provides a constructor for a Difference that does nothing on a call to 'clean' and saves the * 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. * hash/last modified time of the files as they were prior to running the function.
*/ */
val inputs = constructor(false, false) val inputs = constructor(false, false)
} }
class Difference(val store: CacheStore, val style: FileInfo.Style, val defineClean: Boolean, val filesAreOutputs: Boolean) extends Tracked {
def clean() = class Difference(
{ val store: CacheStore,
if (defineClean) IO.delete(raw(cachedFilesInfo)) else () val style: FileInfo.Style,
clearCache() val defineClean: Boolean,
} val filesAreOutputs: Boolean
) extends Tracked {
def clean() = {
if (defineClean) IO.delete(raw(cachedFilesInfo)) else ()
clearCache()
}
private def clearCache() = store.delete() private def clearCache() = store.delete()
private def cachedFilesInfo = store.read(default = FilesInfo.empty[style.F])(style.formats).files private def cachedFilesInfo = store.read(default = FilesInfo.empty[style.F])(style.formats).files
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file) private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
def apply[T](files: Set[File])(f: ChangeReport[File] => T): T = def apply[T](files: Set[File])(f: ChangeReport[File] => T): T = {
{ val lastFilesInfo = cachedFilesInfo
val lastFilesInfo = cachedFilesInfo apply(files, lastFilesInfo)(f)(_ => files)
apply(files, lastFilesInfo)(f)(_ => files) }
}
def apply[T](f: ChangeReport[File] => T)(implicit toFiles: T => Set[File]): T = def apply[T](f: ChangeReport[File] => T)(implicit toFiles: T => Set[File]): T = {
{ val lastFilesInfo = cachedFilesInfo
val lastFilesInfo = cachedFilesInfo apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles)
apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles) }
}
private def abs(files: Set[File]) = files.map(_.getAbsoluteFile) private def abs(files: Set[File]) = files.map(_.getAbsoluteFile)
private[this] def apply[T](files: Set[File], lastFilesInfo: Set[style.F])(f: ChangeReport[File] => T)(extractFiles: T => Set[File]): T =
{
val lastFiles = raw(lastFilesInfo)
val currentFiles = abs(files)
val currentFilesInfo = style(currentFiles)
val report = new ChangeReport[File] { private[this] def apply[T](files: Set[File], lastFilesInfo: Set[style.F])(
lazy val checked = currentFiles f: ChangeReport[File] => T
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist. )(extractFiles: T => Set[File]): T = {
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist. val lastFiles = raw(lastFilesInfo)
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added val currentFiles = abs(files)
lazy val unmodified = checked -- modified val currentFilesInfo = style(currentFiles)
}
val result = f(report) val report = new ChangeReport[File] {
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo 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.
store.write(info)(style.formats) 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
result lazy val unmodified = checked -- modified
} }
val result = f(report)
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
store.write(info)(style.formats)
result
}
} }

View File

@ -11,7 +11,6 @@ import sbt.internal.util.UnitSpec
class TrackedSpec extends UnitSpec { class TrackedSpec extends UnitSpec {
"lastOutput" should "store the last output" in { "lastOutput" should "store the last output" in {
withStore { store => withStore { store =>
val value = 5 val value = 5
val otherValue = 10 val otherValue = 10
@ -132,7 +131,6 @@ class TrackedSpec extends UnitSpec {
} }
} }
"tstamp tracker" should "have a timestamp of 0 on first invocation" in { "tstamp tracker" should "have a timestamp of 0 on first invocation" in {
withStore { store => withStore { store =>
Tracked.tstamp(store) { last => Tracked.tstamp(store) { last =>
@ -143,7 +141,6 @@ class TrackedSpec extends UnitSpec {
it should "provide the last time a function has been evaluated" in { it should "provide the last time a function has been evaluated" in {
withStore { store => withStore { store =>
Tracked.tstamp(store) { last => Tracked.tstamp(store) { last =>
assert(last === 0) assert(last === 0)
} }
@ -161,4 +158,4 @@ class TrackedSpec extends UnitSpec {
f(store) f(store)
} }
} }