Merge pull request #118 from dwijnand/scalafmt

Add, configure & enable Scalafmt
This commit is contained in:
Dale Wijnand 2017-08-10 11:52:57 +01:00 committed by GitHub
commit 1527acc6c1
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:

View File

@ -18,7 +18,8 @@ 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")
} }
@ -30,19 +31,27 @@ def commonSettings: Seq[Setting[_]] = Seq(
) )
val mimaSettings = Def settings ( val mimaSettings = Def settings (
mimaPreviousArtifacts := Set(organization.value % moduleName.value % "1.0.0-RC3" mimaPreviousArtifacts := Set(
organization.value % moduleName.value % "1.0.0-RC3"
cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled) 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(
Seq(
git.baseVersion := baseVersion, git.baseVersion := baseVersion,
version := { version := {
val v = version.value val v = version.value
@ -52,7 +61,9 @@ lazy val utilRoot: Project = (project in file(".")).
bintrayPackage := "util", bintrayPackage := "util",
homepage := Some(url("https://github.com/sbt/util")), homepage := Some(url("https://github.com/sbt/util")),
description := "Util module for sbt", description := "Util module for sbt",
scmInfo := Some(ScmInfo(url("https://github.com/sbt/util"), "git@github.com:sbt/util.git")) 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",
@ -67,8 +78,7 @@ 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",
@ -76,8 +86,7 @@ lazy val utilInterface = (project in internalPath / "util-interface").
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,
@ -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,49 +119,50 @@ 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 ++= {
@ -162,8 +173,8 @@ lazy val utilScripted = (project in internalPath / "util-scripted").
} }
}, },
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 =>

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,11 +13,13 @@ 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
@ -25,18 +27,19 @@ object BufferedAppender {
} }
/** /**
* 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)
@ -46,8 +49,7 @@ class BufferedAppender private[BufferedAppender] (name: String, delegate: Append
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(
out: ConsoleOut = ConsoleOut.systemOut,
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv, ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv, useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger = 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] (
out: ConsoleOut,
override val ansiCodesSupported: Boolean, override val ansiCodesSupported: Boolean,
useFormat: Boolean, useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger { 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,11 +193,13 @@ 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(
name: String,
out: ConsoleOut, out: ConsoleOut,
ansiCodesSupported: Boolean, ansiCodesSupported: Boolean,
useFormat: Boolean, useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = { suppressedMessage: SuppressedTraceContext => Option[String]
): ConsoleAppender = {
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage) val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
appender.start appender.start
appender 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")
@ -307,10 +320,12 @@ 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)
} }
}
/** /**
* Logs a `ControlEvent` to the log. * Logs a `ControlEvent` to the log.
@ -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)
} }
} }
@ -406,17 +427,16 @@ class ConsoleAppender private[ConsoleAppender] (
} }
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
else LogExchange.stringCodec[AnyRef](contentType) match { 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)
} }
} }

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,
newBackingFile: => File,
console: ConsoleOut
): GlobalLogging1 = {
val log = ConsoleLogger(console) val log = ConsoleLogger(console)
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger) 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,
newBackingFile: => File,
console: ConsoleOut
): GlobalLogging = {
val loggerName = generateName val loggerName = generateName
val log = LogExchange.logger(loggerName) val log = LogExchange.logger(loggerName)
val appender = ConsoleAppender(ConsoleAppender.generateName, console) val appender = ConsoleAppender(ConsoleAppender.generateName, console)
LogExchange.bindLoggerAppenders( LogExchange.bindLoggerAppenders(loggerName, List(appender -> Level.Info))
loggerName, List(appender -> Level.Info)
)
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender) 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,8 +10,7 @@ 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
@ -24,14 +23,18 @@ object MainAppender {
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)
@ -39,18 +42,46 @@ object MainAppender {
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

@ -21,8 +21,7 @@ class ManagedLogger(
) 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))
@ -34,8 +33,7 @@ class ManagedLogger(
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}")
@ -48,8 +46,7 @@ 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]])

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

@ -27,6 +27,12 @@ object ObjectEvent {
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,8 +8,7 @@ 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)

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

@ -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,8 +60,7 @@ 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.##

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

@ -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,8 +83,7 @@ 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)
@ -92,9 +96,26 @@ object Logger {
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,22 +8,21 @@ 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") =
forAllNoShrink(genWithoutEscape) { (s: String) =>
val removed: String = removeEscapeSequences(s) val removed: String = removeEscapeSequences(s)
("Escape sequence removed: '" + removed + "'") |: ("Escape sequence removed: '" + removed + "'") |:
(removed == s) (removed == s)
@ -36,8 +35,10 @@ 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 withEscapes: String =
start + escapes.map(ean => ean.escape.makeString + ean.notEscape).mkString("")
val removed: String = removeEscapeSequences(withEscapes) val removed: String = removeEscapeSequences(withEscapes)
val original = start + escapes.map(_.notEscape).mkString("") val original = start + escapes.map(_.notEscape).mkString("")
val diffCharString = diffIndex(original, removed) val diffCharString = diffIndex(original, removed)
@ -51,7 +52,9 @@ object Escapes extends Properties("Escapes") {
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 {
@ -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,18 +110,23 @@ 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
@ -127,15 +135,18 @@ object Escape {
} }
builder.toString builder.toString
} }
def pad(s: String, minLength: Int, extra: Char) =
{ def pad(s: String, minLength: Int, extra: Char) = {
val diff = minLength - s.length val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s) 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,8 +3,7 @@ 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,7 +36,7 @@ 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)
@ -46,34 +48,49 @@ object Relation {
/** 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] =>
forwardMap.filterNot(_._2.isEmpty) == o.forwardMap.filterNot(_._2.isEmpty)
case _ => false 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,8 +11,7 @@ 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
@ -75,8 +74,8 @@ 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

View File

@ -58,11 +58,13 @@ 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)
@ -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,8 +7,7 @@ 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

View File

@ -15,13 +15,15 @@ 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 =>
throw new TestException(statement, "Command failed: " + t.getMessage, null)
case _ => throw new TestException(statement, "Command failed", err) case _ => throw new TestException(statement, "Command failed", err)
} }
} else } else
@ -36,12 +38,11 @@ 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,14 +61,22 @@ 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) println("Running " + str)
testResources.readWriteResourceDirectory(g, n) { testDirectory => testResources.readWriteResourceDirectory(g, n) { testDirectory =>
val disabled = new File(testDirectory, "disabled").isFile val disabled = new File(testDirectory, "disabled").isFile
@ -68,24 +84,28 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
log.info("D " + str + " [DISABLED]") log.info("D " + str + " [DISABLED]")
None None
} else { } else {
try { scriptedTest(str, testDirectory, prescripted, log); None } try { scriptedTest(str, testDirectory, prescripted, log); None } catch {
catch { case _: TestException | _: PendingTestSuccessException => Some(str) } case _: TestException | _: PendingTestSuccessException => Some(str)
}
} }
} }
} }
} }
} }
private def scriptedTest(label: String, testDirectory: File, prescripted: File => Unit, log: ManagedLogger): Unit = private def scriptedTest(
{ label: String,
testDirectory: File,
prescripted: File => Unit,
log: ManagedLogger
): Unit = {
val buffered = BufferedAppender(consoleAppender) val buffered = BufferedAppender(consoleAppender)
LogExchange.unbindLoggerAppenders(log.name) LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil) LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
if (bufferLog) { if (bufferLog) {
buffered.record() buffered.record()
} }
def createParser() = def createParser() = {
{
// val fileHandler = new FileCommands(testDirectory) // val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts) // // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler)) // new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
@ -99,8 +119,7 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
} }
val pendingString = if (pending) " [PENDING]" else "" val pendingString = if (pending) " [PENDING]" else ""
def runTest() = def runTest() = {
{
val run = new ScriptRunner val run = new ScriptRunner
val parser = createParser() val parser = createParser()
run(parser.parse(file)) run(parser.parse(file))
@ -148,22 +167,21 @@ 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 val groupName = group.getName
listTests(group).map(ScriptedTest(groupName, _)) listTests(group).map(ScriptedTest(groupName, _))
} }
} }
private[this] def listTests(group: File): Seq[String] = private[this] def listTests(group: File): Seq[String] = {
{
val groupName = group.getName val groupName = group.getName
val allTests = list(group, filter).sortBy(_.getName) val allTests = list(group, filter).sortBy(_.getName)
if (allTests.isEmpty) { if (allTests.isEmpty) {
log.warn("No tests in test group " + groupName) log.warn("No tests in test group " + groupName)
Seq.empty Seq.empty
} else { } else {
val (included, skipped) = allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName))) val (included, skipped) =
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty) if (included.isEmpty)
log.warn("Test group " + groupName + " skipped.") log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) { else if (skipped.nonEmpty) {

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) = ()
} }

View File

@ -20,7 +20,12 @@ 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,14 +46,13 @@ 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("") val labelString = label.map("'" + _ + "' ").getOrElse("")
sys.error("Could not parse test script, " + labelString + err.toString) sys.error("Could not parse test script, " + labelString + err.toString)
} }
@ -56,28 +60,36 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
} }
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 { positioned {
val command = (word | err("expected command")) val command = (word | err("expected command"))
val arguments = rep(space ~> (word | failure("expected argument"))) val arguments = rep(space ~> (word | failure("expected argument")))
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^ (successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^ {
{
case successExpected ~ start ~ command ~ arguments => case successExpected ~ start ~ command ~ arguments =>
new PositionalStatement { new PositionalStatement {
def tuple = (handlers(start), new Statement(command, arguments, successExpected, pos.line)) def tuple =
(handlers(start), new Statement(command, arguments, successExpected, pos.line))
} }
} }
} ^^ (_.tuple) } ^^ (_.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`.
*/ */

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,7 +69,9 @@ 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,7 +84,9 @@ 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,8 +132,9 @@ 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
): Set[File] => Set[File] = {
lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle) lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle)
lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle) lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle)
inputs => inputs =>

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,8 +226,8 @@ 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 =>
{ def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value => {
if (uptodate(value)) if (uptodate(value))
ifUnchanged(value) ifUnchanged(value)
else { else {
@ -220,14 +236,20 @@ class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
} }
} }
def update(value: O): Unit = store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, 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,38 +258,46 @@ 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,
val style: FileInfo.Style,
val defineClean: Boolean,
val filesAreOutputs: Boolean
) extends Tracked {
def clean() = {
if (defineClean) IO.delete(raw(cachedFilesInfo)) else () if (defineClean) IO.delete(raw(cachedFilesInfo)) else ()
clearCache() 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 =
{ 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 lastFiles = raw(lastFilesInfo)
val currentFiles = abs(files) val currentFiles = abs(files)
val currentFilesInfo = style(currentFiles) val currentFilesInfo = style(currentFiles)

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)
} }