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
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:
directories:

View File

@ -18,7 +18,8 @@ def commonSettings: Seq[Setting[_]] = Seq(
scalacOptions := {
val old = scalacOptions.value
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 _ => old ++ List("-Ywarn-unused", "-Ywarn-unused-import", "-YdisableFlatCpCaching")
}
@ -30,19 +31,27 @@ def commonSettings: Seq[Setting[_]] = Seq(
)
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)
)
)
lazy val utilRoot: Project = (project in file(".")).
aggregate(
utilInterface, utilControl, utilPosition,
utilLogging, utilRelation, utilCache, utilTracking, utilTesting,
lazy val utilRoot: Project = (project in file("."))
.aggregate(
utilInterface,
utilControl,
utilPosition,
utilLogging,
utilRelation,
utilCache,
utilTracking,
utilTesting,
utilScripted
).
settings(
inThisBuild(Seq(
)
.settings(
inThisBuild(
Seq(
git.baseVersion := baseVersion,
version := {
val v = version.value
@ -52,7 +61,9 @@ lazy val utilRoot: Project = (project in file(".")).
bintrayPackage := "util",
homepage := Some(url("https://github.com/sbt/util")),
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,
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
// 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
lazy val utilInterface = (project in internalPath / "util-interface").
settings(
lazy val utilInterface = (project in internalPath / "util-interface").settings(
commonSettings,
javaOnlySettings,
name := "Util Interface",
@ -76,8 +86,7 @@ lazy val utilInterface = (project in internalPath / "util-interface").
mimaSettings,
)
lazy val utilControl = (project in internalPath / "util-control").
settings(
lazy val utilControl = (project in internalPath / "util-control").settings(
commonSettings,
name := "Util Control",
mimaSettings,
@ -90,14 +99,15 @@ val utilPosition = (project in file("internal") / "util-position").settings(
)
// logging
lazy val utilLogging = (project in internalPath / "util-logging").
enablePlugins(ContrabandPlugin, JsonCodecPlugin).
dependsOn(utilInterface, utilTesting % Test).
settings(
lazy val utilLogging = (project in internalPath / "util-logging")
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
.dependsOn(utilInterface, utilTesting % Test)
.settings(
commonSettings,
crossScalaVersions := Seq(scala210, scala211, scala212),
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",
contrabandFormatsForType in generateContrabands in Compile := { tpe =>
val old = (contrabandFormatsForType in generateContrabands in Compile).value
@ -109,49 +119,50 @@ lazy val utilLogging = (project in internalPath / "util-logging").
)
// Relation
lazy val utilRelation = (project in internalPath / "util-relation").
dependsOn(utilTesting % Test).
settings(
lazy val utilRelation = (project in internalPath / "util-relation")
.dependsOn(utilTesting % Test)
.settings(
commonSettings,
name := "Util Relation",
mimaSettings,
)
// Persisted caching based on sjson-new
lazy val utilCache = (project in file("util-cache")).
dependsOn(utilTesting % Test).
settings(
lazy val utilCache = (project in file("util-cache"))
.dependsOn(utilTesting % Test)
.settings(
commonSettings,
name := "Util Cache",
libraryDependencies ++= Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value),
libraryDependencies ++=
Seq(sjsonnewScalaJson.value, sjsonnewMurmurhash.value, scalaReflect.value),
mimaSettings,
).
configure(addSbtIO)
)
.configure(addSbtIO)
// Builds on cache to provide caching for filesystem-related operations
lazy val utilTracking = (project in file("util-tracking")).
dependsOn(utilCache, utilTesting % Test).
settings(
lazy val utilTracking = (project in file("util-tracking"))
.dependsOn(utilCache, utilTesting % Test)
.settings(
commonSettings,
name := "Util Tracking",
mimaSettings,
).
configure(addSbtIO)
)
.configure(addSbtIO)
// Internal utility for testing
lazy val utilTesting = (project in internalPath / "util-testing").
settings(
lazy val utilTesting = (project in internalPath / "util-testing")
.settings(
commonSettings,
crossScalaVersions := Seq(scala210, scala211, scala212),
name := "Util Testing",
libraryDependencies ++= Seq(scalaCheck, scalatest),
mimaSettings,
).
configure(addSbtIO)
)
.configure(addSbtIO)
lazy val utilScripted = (project in internalPath / "util-scripted").
dependsOn(utilLogging, utilInterface).
settings(
lazy val utilScripted = (project in internalPath / "util-scripted")
.dependsOn(utilLogging, utilInterface)
.settings(
commonSettings,
name := "Util Scripted",
libraryDependencies ++= {
@ -162,8 +173,8 @@ lazy val utilScripted = (project in internalPath / "util-scripted").
}
},
mimaSettings,
).
configure(addSbtIO)
)
.configure(addSbtIO)
def customCommands: Seq[Setting[_]] = Seq(
commands += Command.command("release") { state =>

View File

@ -7,23 +7,20 @@ import java.io.IOException
object ErrorHandling {
def translate[T](msg: => String)(f: => T) =
try { f }
catch {
try { f } catch {
case e: IOException => throw new TranslatedIOException(msg + e.toString, e)
case e: Exception => throw new TranslatedException(msg + e.toString, e)
}
def wideConvert[T](f: => T): Either[Throwable, T] =
try { Right(f) }
catch {
try { Right(f) } catch {
case ex @ (_: Exception | _: StackOverflowError) => Left(ex)
case err @ (_: ThreadDeath | _: VirtualMachineError) => throw err
case x: Throwable => Left(x)
}
def convert[T](f: => T): Either[Exception, T] =
try { Right(f) }
catch { case e: Exception => Left(e) }
try { Right(f) } catch { case e: Exception => Left(e) }
def reducedToString(e: Throwable): String =
if (e.getClass == classOf[RuntimeException]) {
@ -32,7 +29,11 @@ object ErrorHandling {
} else
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
}
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.*/
trait ExitHook {
/** Subclasses should implement this method, which is called when this hook is executed. */
def runBeforeExiting(): Unit
}
object ExitHook {
def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f }
}
object ExitHooks {
/** 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] =
exitHooks.flatMap(hook =>
ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
exitHooks.flatMap(hook => ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
}

View File

@ -13,11 +13,13 @@ import java.util.concurrent.atomic.AtomicInteger
object BufferedAppender {
def generateName: String =
"buffered-" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger
def apply(delegate: Appender): BufferedAppender =
apply(generateName, delegate)
def apply(name: String, delegate: Appender): BufferedAppender =
{
def apply(name: String, delegate: Appender): BufferedAppender = {
val appender = new BufferedAppender(name, delegate)
appender.start
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
* 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 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] var recording = false
def append(event: XLogEvent): Unit =
{
def append(event: XLogEvent): Unit = {
if (recording) {
buffer += event.toImmutable
} else delegate.append(event)
@ -46,8 +49,7 @@ class BufferedAppender private[BufferedAppender] (name: String, delegate: Append
def record() = synchronized { recording = true }
def buffer[T](f: => T): T = {
record()
try { f }
finally { stopQuietly() }
try { f } finally { stopQuietly() }
}
def bufferQuietly[T](f: => T): T = {
record()
@ -70,10 +72,13 @@ class BufferedAppender private[BufferedAppender] (name: String, delegate: Append
}
buffer.clear()
}
/** Clears buffered events and disables buffering. */
def clearBuffer(): Unit = synchronized { buffer.clear(); recording = false }
/** Plays buffered events and disables buffering. */
def stopBuffer(): Unit = synchronized { play(); clearBuffer() }
}
/**
@ -93,8 +98,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
def record() = synchronized { recording = true }
def buffer[T](f: => T): T = {
record()
try { f }
finally { stopQuietly() }
try { f } finally { stopQuietly() }
}
def bufferQuietly[T](f: => T): T = {
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.
*/
def play(): Unit = synchronized { delegate.logAll(buffer.toList); buffer.clear() }
/** Clears buffered events and disables buffering. */
def clear(): Unit = synchronized { buffer.clear(); recording = false }
/** Plays buffered events and disables buffering. */
def stop(): Unit = synchronized { play(); clear() }
@ -127,6 +133,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
delegate.setLevel(newLevel)
()
}
override def setSuccessEnabled(flag: Boolean): Unit = synchronized {
super.setSuccessEnabled(flag)
if (recording)
@ -135,6 +142,7 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
delegate.setSuccessEnabled(flag)
()
}
override def setTrace(level: Int): Unit = synchronized {
super.setTrace(level)
if (recording)
@ -144,12 +152,14 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
()
}
def trace(t: => Throwable): Unit =
doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
def trace(t: => Throwable): Unit = doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
def success(message: => String): Unit =
doBufferable(Level.Info, new Success(message), _.success(message))
def log(level: Level.Value, message: => String): Unit =
doBufferable(level, new Log(level, message), _.log(level, message))
def logAll(events: Seq[LogEvent]): Unit = synchronized {
if (recording)
buffer ++= events
@ -157,11 +167,22 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
delegate.logAll(events)
()
}
def control(event: ControlEvent.Value, message: => String): Unit =
doBufferable(Level.Info, new ControlEvent(event, message), _.control(event, message))
private def doBufferable(level: Level.Value, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit =
private def doBufferable(
level: Level.Value,
appendIfBuffered: => LogEvent,
doUnbuffered: AbstractLogger => Unit
): Unit =
doBufferableIf(atLevel(level), appendIfBuffered, doUnbuffered)
private def doBufferableIf(condition: => Boolean, appendIfBuffered: => LogEvent, doUnbuffered: AbstractLogger => Unit): Unit = synchronized {
private def doBufferableIf(
condition: => Boolean,
appendIfBuffered: => LogEvent,
doUnbuffered: AbstractLogger => Unit
): Unit = synchronized {
if (condition) {
if (recording)
buffer += appendIfBuffered

View File

@ -51,10 +51,13 @@ object ConsoleLogger {
* @param suppressedMessage How to show suppressed stack traces.
* @return A new `ConsoleLogger` that logs to `out`.
*/
def apply(out: ConsoleOut = ConsoleOut.systemOut,
def apply(
out: ConsoleOut = ConsoleOut.systemOut,
ansiCodesSupported: 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)
}
@ -62,10 +65,12 @@ object ConsoleLogger {
* A logger that logs to the console. On supported systems, the level labels are
* colored.
*/
class ConsoleLogger private[ConsoleLogger] (out: ConsoleOut,
class ConsoleLogger private[ConsoleLogger] (
out: ConsoleOut,
override val ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
suppressedMessage: SuppressedTraceContext => Option[String]
) extends BasicLogger {
private[sbt] val appender: ConsoleAppender =
ConsoleAppender(generateName(), out, ansiCodesSupported, useFormat, suppressedMessage)
@ -160,7 +165,11 @@ object ConsoleAppender {
* @param suppressedMessage How to handle stack traces.
* @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)
/**
@ -184,11 +193,13 @@ object ConsoleAppender {
* formatting.
* @return A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String,
def apply(
name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = {
suppressedMessage: SuppressedTraceContext => Option[String]
): ConsoleAppender = {
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
appender.start
appender
@ -242,7 +253,9 @@ object ConsoleAppender {
// this results in a linkage error as detected below. The detection is likely jvm specific, but the priority
// is avoiding mistakenly identifying something as a launcher incompatibility when it is not
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")
@ -307,10 +320,12 @@ class ConsoleAppender private[ConsoleAppender] (
out.lockObject.synchronized {
if (traceLevel >= 0)
write(StackTrace.trimmed(t, traceLevel))
if (traceLevel <= 2)
for (msg <- suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)))
if (traceLevel <= 2) {
val ctx = new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)
for (msg <- suppressedMessage(ctx))
appendLog(NO_COLOR, "trace", NO_COLOR, msg)
}
}
/**
* 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 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 {
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)
}
}
@ -406,17 +427,16 @@ class ConsoleAppender private[ConsoleAppender] (
}
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = {
def appendEvent(oe: ObjectEvent[_]): Unit =
{
def appendEvent(oe: ObjectEvent[_]): Unit = {
val contentType = oe.contentType
if (contentType == "sbt.internal.util.TraceEvent") {
appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
}
else LogExchange.stringCodec[AnyRef](contentType) match {
} else
LogExchange.stringCodec[AnyRef](contentType) match {
case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" =>
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) }
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)
}
}

View File

@ -12,8 +12,8 @@ sealed trait ConsoleOut {
object ConsoleOut {
def systemOut: ConsoleOut = printStreamOut(System.out)
def overwriteContaining(s: String): (String, String) => Boolean = (cur, prev) =>
cur.contains(s) && prev.contains(s)
def overwriteContaining(s: String): (String, String) => Boolean =
(cur, prev) => cur.contains(s) && prev.contains(s)
/** Move to beginning of previous line and clear the line. */
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.
* `newLogger` creates a new global logging configuration from a sink and backing configuration.
*/
final case class GlobalLogging(full: ManagedLogger, console: ConsoleOut, backed: Appender,
backing: GlobalLogBacking, newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging)
final case class 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.
@ -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.
*/
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`. */
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.
*/
def unshift = GlobalLogBacking(last getOrElse file, None, newBackingFile)
}
object GlobalLogBacking {
def apply(newBackingFile: => File): GlobalLogBacking = GlobalLogBacking(newBackingFile, None, newBackingFile _)
def apply(newBackingFile: => File): GlobalLogBacking =
GlobalLogBacking(newBackingFile, None, newBackingFile _)
}
object GlobalLogging {
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
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)
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 log = LogExchange.logger(loggerName)
val appender = ConsoleAppender(ConsoleAppender.generateName, console)
LogExchange.bindLoggerAppenders(
loggerName, List(appender -> Level.Info)
)
LogExchange.bindLoggerAppenders(loggerName, List(appender -> Level.Info))
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
}
}

View File

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

View File

@ -10,8 +10,7 @@ object MainAppender {
"GlobalBacking" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger =
{
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger = {
import config._
// TODO
// console setTrace screenTrace
@ -24,14 +23,18 @@ object MainAppender {
log.name,
(consoleOpt.toList map { _ -> screenLevel }) :::
List(backed -> backingLevel) :::
(extra map { x => (x -> Level.Info) })
(extra map { x =>
(x -> Level.Info)
})
)
log
}
def globalDefault(console: ConsoleOut): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging =
{
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = (log, writer, backing) => {
def globalDefault(
console: ConsoleOut
): (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging = {
lazy val newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking) => GlobalLogging =
(log, writer, backing) => {
val backed: Appender = defaultBacked(generateGlobalBackingName)(writer)
val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil))
GlobalLogging(full, console, backed, backing, newAppender)
@ -39,18 +42,46 @@ object MainAppender {
newAppender
}
def defaultMultiConfig(consoleOpt: Option[ConsoleOut], backing: Appender, extra: List[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 =
def defaultMultiConfig(
consoleOpt: Option[ConsoleOut],
backing: Appender,
extra: List[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)
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)
def defaultBacked: PrintWriter => Appender = defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv)
def defaultBacked(loggerName: String): PrintWriter => Appender = defaultBacked(loggerName, ConsoleAppender.formatEnabledInEnv)
def defaultBacked(useFormat: Boolean): PrintWriter => Appender = defaultBacked(generateGlobalBackingName, useFormat)
def defaultBacked: PrintWriter => Appender =
defaultBacked(generateGlobalBackingName, ConsoleAppender.formatEnabledInEnv)
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 =
to => {
ConsoleAppender(
@ -61,7 +92,12 @@ object MainAppender {
}
final case class MainAppenderConfig(
consoleOpt: Option[Appender], backed: Appender, extra: List[Appender],
screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int
consoleOpt: Option[Appender],
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 {
override def trace(t: => Throwable): Unit =
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(
ConsoleAppender.toXLevel(level),
new ObjectMessage(StringEvent(level.toString, message, channelName, execId))
@ -34,8 +33,7 @@ class ManagedLogger(
infoEvent[SuccessEvent](SuccessEvent(message))
}
def registerStringCodec[A: ShowLines: TypeTag]: Unit =
{
def registerStringCodec[A: ShowLines: TypeTag]: Unit = {
val tag = StringTypeTag[A]
val ev = implicitly[ShowLines[A]]
// 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 warnEvent[A: JsonFormat: TypeTag](event: => A): Unit = logEvent(Level.Warn, 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 tag = StringTypeTag[A]
LogExchange.getOrElseUpdateJsonCodec(tag.key, implicitly[JsonFormat[A]])

View File

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

View File

@ -27,6 +27,12 @@ object ObjectEvent {
execId: Option[String],
contentType: String
): ObjectEvent[A] =
new ObjectEvent(level, message, channelName, execId, contentType,
Converter.toJsonUnsafe(message))
new ObjectEvent(
level,
message,
channelName,
execId,
contentType,
Converter.toJsonUnsafe(message)
)
}

View File

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

View File

@ -8,8 +8,7 @@ final case class StringTypeTag[A](key: String) {
}
object StringTypeTag {
def apply[A: TypeTag]: StringTypeTag[A] =
{
def apply[A: TypeTag]: StringTypeTag[A] = {
val tag = implicitly[TypeTag[A]]
val tpe = tag.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 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 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]?
}
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]].
*/
package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import xsbti.Position

View File

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

View File

@ -1,7 +1,6 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
package sbt.internal.util.codec
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 logAll(events: Seq[LogEvent]): Unit
/** Defined in terms of other methods in Logger and should not be called from them. */
final def log(event: LogEvent): Unit = {
event match {

View File

@ -36,8 +36,15 @@ object InterfaceUtil {
case None => Optional.empty[A]()
}
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(
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)
def problem(cat: String, pos: Position, msg: String, sev: Severity): Problem =
@ -53,8 +60,7 @@ object InterfaceUtil {
this.get2 == o.get2
case _ => false
}
override def hashCode: Int =
{
override def hashCode: Int = {
var hash = 1
hash = hash * 31 + this.get1.##
hash = hash * 31 + this.get2.##

View File

@ -12,6 +12,7 @@ object Level extends Enumeration {
val Info = Value(2, "info")
val Warn = Value(3, "warn")
val Error = Value(4, "error")
/**
* 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.
@ -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. */
def apply(s: String) = values.find(s == _.toString)
/** Same as apply, defined for use in pattern matching. */
private[sbt] def unapply(s: String) = apply(s)
}

View File

@ -24,8 +24,16 @@ sealed abstract class LogExchange {
val _ = context
val ctx = XLogManager.getContext(false) match { case x: LoggerContext => x }
val config = ctx.getConfiguration
val loggerConfig = LoggerConfig.createLogger(false, XLevel.DEBUG, name,
"true", Array[AppenderRef](), null, config, null)
val loggerConfig = LoggerConfig.createLogger(
false,
XLevel.DEBUG,
name,
"true",
Array[AppenderRef](),
null,
config,
null
)
config.addLogger(name, loggerConfig)
ctx.updateLoggers
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 warn(message: => String): Unit = log(Level.Warn, message)
final def error(message: => String): Unit = log(Level.Error, message)
// Added by sys.process.ProcessLogger
final def err(message: => String): Unit = log(Level.Error, message)
// sys.process.ProcessLogger
@ -62,12 +63,16 @@ object Logger {
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 xlog2Log(lg: xLogger): Logger = lg match {
case l: Logger => l
case _ => wrapXLogger(lg)
}
private[this] def wrapXLogger(lg: xLogger): Logger = new Logger {
import InterfaceUtil.toSupplier
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)
def trace(t: => Throwable): Unit = trace(toSupplier(t))
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)
level match {
case Level.Debug => lg.debug(fmsg)
@ -92,9 +96,26 @@ object Logger {
def jo2o[A](o: Optional[A]): Option[A] = InterfaceUtil.jo2o(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 =
InterfaceUtil.position(line0, content, offset0, pointer0, pointerSpace0, sourcePath0, sourceFile0)
def position(
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 =
InterfaceUtil.problem(cat, pos, msg, sev)
}

View File

@ -8,22 +8,21 @@ import EscHelpers.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequ
object Escapes extends Properties("Escapes") {
property("genTerminator only generates terminators") =
forAllNoShrink(genTerminator) { (c: Char) => isEscapeTerminator(c) }
forAllNoShrink(genTerminator)((c: Char) => isEscapeTerminator(c))
property("genWithoutTerminator only generates terminators") =
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) =>
!hasEscapeSequence(s)
}
property("hasEscapeSequence is false when no escape character is present") =
forAllNoShrink(genWithoutEscape)((s: String) => !hasEscapeSequence(s))
property("hasEscapeSequence is true when escape character is present") = forAllNoShrink(genWithRandomEscapes) { (s: String) =>
hasEscapeSequence(s)
}
property("hasEscapeSequence is true when escape character is present") =
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)
("Escape sequence removed: '" + removed + "'") |:
(removed == s)
@ -36,8 +35,10 @@ object Escapes extends Properties("Escapes") {
}
property("removeEscapeSequences returns string without escape sequences") =
forAllNoShrink(genWithoutEscape, genEscapePairs) { (start: String, escapes: List[EscapeAndNot]) =>
val withEscapes: String = start + (escapes.map { ean => ean.escape.makeString + ean.notEscape }).mkString("")
forAllNoShrink(genWithoutEscape, genEscapePairs) {
(start: String, escapes: List[EscapeAndNot]) =>
val withEscapes: String =
start + escapes.map(ean => ean.escape.makeString + ean.notEscape).mkString("")
val removed: String = removeEscapeSequences(withEscapes)
val original = start + escapes.map(_.notEscape).mkString("")
val diffCharString = diffIndex(original, removed)
@ -51,7 +52,9 @@ object Escapes extends Properties("Escapes") {
def diffIndex(expect: String, original: String): String = {
var i = 0;
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
}
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) {
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
final case class EscapeSequence(content: String, terminator: Char) {
if (!content.isEmpty) {
assert(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(
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))
def makeString: String = ESC + content + terminator
@ -74,14 +85,20 @@ object Escapes extends Properties("Escapes") {
if (content.isEmpty) s"ESC (${terminator.toInt})"
else s"ESC ($content) (${terminator.toInt})"
}
private[this] def noEscape(s: String): String = s.replace(ESC, ' ')
lazy val genEscapeSequence: Gen[EscapeSequence] = oneOf(genKnownSequence, genTwoCharacterSequence, genArbitraryEscapeSequence)
lazy val genEscapePair: Gen[EscapeAndNot] = for (esc <- genEscapeSequence; not <- genWithoutEscape) yield EscapeAndNot(esc, not)
lazy val genEscapeSequence: Gen[EscapeSequence] =
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 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] =
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 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 setMode = setModeLike('h')
def setModeLike(term: Char): Seq[String] = (0 to 19).map(i => "=" + i.toString + term)
lazy val genWithoutTerminator =
genRawString.map(_.filter { c => !isEscapeTerminator(c) && (c != '[') })
genRawString.map(_.filter(c => !isEscapeTerminator(c) && (c != '[')))
lazy val genTwoCharacterSequence =
// 91 == [ which is the CSI escape sequence.
@ -108,7 +126,8 @@ object Escapes extends Properties("Escapes") {
lazy val genWithoutEscape: Gen[String] = genRawString.map(noEscape)
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
}

View File

@ -36,6 +36,7 @@ object LogWriterTest extends Properties("Log Writer") {
case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")"
case _ => "Not Log"
}
/**
* 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`
@ -46,7 +47,7 @@ object LogWriterTest extends Properties("Log Writer") {
val content = section.content
val normalized = Escape.newline(content, newLine)
if (section.byCharacter)
normalized.foreach { c => writer.write(c.toInt) }
normalized.foreach(c => writer.write(c.toInt))
else
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.*/
def toLines(lines: List[List[ToLog]]): List[String] =
lines.map(_.map(_.contentOnly).mkString)
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/
def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean =
(lines zip events) forall {
@ -76,7 +78,8 @@ object LogWriterTest extends Properties("Log Writer") {
implicit lazy val arbLevel: Arbitrary[Level.Value] = Arbitrary(genLevel)
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] =
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
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*/
@ -107,18 +110,23 @@ final class Output(val lines: List[List[ToLog]], val level: Level.Value) {
override def toString =
"Level: " + level + "\n" + lines.map(_.mkString).mkString("\n")
}
final class NewLine(val str: String) {
override def toString = Escape(str)
}
final class ToLog(val content: String, val byCharacter: Boolean) {
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.*/
object Escape {
/** 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)
for (c <- s) {
val char = c.toInt
@ -127,15 +135,18 @@ object Escape {
}
builder.toString
}
def pad(s: String, minLength: Int, extra: Char) =
{
def pad(s: String, minLength: Int, extra: Char) = {
val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
}
/** Replaces a \n character at the end of a string `s` with `nl`.*/
def newline(s: String, nl: String): String =
if (s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s
}
/** Records logging events for later retrieval.*/
final class RecordingLogger extends BasicLogger {
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 success(message: => String): Unit = { events ::= new Success(message) }
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._
val log = LogExchange.logger("foo")
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.infoEvent(1)
}
@ -33,7 +34,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo")
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.infoEvent(Array(1, 2, 3))
}
@ -42,7 +44,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
import sjsonnew.BasicJsonProtocol._
val log = LogExchange.logger("foo")
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.infoEvent(Vector(Vector(1, 2, 3)))
}
@ -51,7 +54,9 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
// this is passed into State normally
val global0 = initialGlobalLogging
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
@ -62,7 +67,7 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
val out = new PrintWriter(writer)
val g = global0.newAppender(global0.full, out, logBacking0)
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)
g
}
@ -71,7 +76,7 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
val out = new PrintWriter(writer)
val g = global1.newAppender(global1.full, out, logBacking1)
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)
// print("Press enter to continue. ")
// System.console.readLine
@ -81,6 +86,8 @@ class ManagedLoggerSpec extends FlatSpec with Matchers {
val console = ConsoleOut.systemOut
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._
object TestLogger {
def apply[T](f: Logger => T): T =
{
def apply[T](f: Logger => T): T = {
val log = new BufferedLogger(ConsoleLogger())
log.setLevel(Level.Debug)
log.bufferQuietly(f(log))

View File

@ -6,6 +6,7 @@ package sbt.internal.util
import Relation._
object Relation {
/** Constructs a new immutable, finite relation that is initially 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.
* 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. */
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 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)
}
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] =
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] =
(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] =
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. */
trait Relation[A, B] {
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
def forward(_1: A): Set[B]
/** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */
def reverse(_2: B): Set[A]
/** Includes `pair` in the relation. */
def +(pair: (A, B)): Relation[A, B]
/** Includes `(a, b)` in the relation. */
def +(a: A, b: B): Relation[A, B]
/** Includes in the relation `(a, b)` for all `b` in `bs`. */
def +(a: A, bs: Traversable[B]): Relation[A, B]
/** Returns the union of the relation `r` with this relation. */
def ++(r: Relation[A, B]): Relation[A, B]
/** Includes the given pairs in this relation. */
def ++(rs: Traversable[(A, B)]): Relation[A, B]
/** Removes all elements `(_1, _2)` for all `_1` in `_1s` from this relation. */
def --(_1s: Traversable[A]): Relation[A, B]
/** Removes all `pairs` from this relation. */
def --(pairs: TraversableOnce[(A, B)]): Relation[A, B]
/** Removes all `relations` from this relation. */
def --(relations: Relation[A, B]): Relation[A, B]
/** Removes all pairs `(_1, _2)` from this relation. */
def -(_1: A): Relation[A, B]
/** Removes `pair` from this relation. */
def -(pair: (A, B)): Relation[A, B]
/** Returns the set of all `_1`s such that `(_1, _2)` is in this relation. */
def _1s: collection.Set[A]
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
def _2s: collection.Set[B]
/** Returns the number of pairs in this relation */
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.
*/
def reverseMap: Map[B, Set[A]]
}
// 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 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 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 +(from: A, to: B) = this + (from, to :: Nil)
def +(from: A, to: Traversable[B]) = if (to.isEmpty) this else
new MRelation(add(fwd, from, to), (rev /: to) { (map, t) => add(map, t, from :: Nil) })
def +(from: A, to: Traversable[B]) =
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 ++(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 --(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 -(pair: (A, B)): Relation[A, B] =
new MRelation(remove(fwd, pair._1, pair._2), remove(rev, pair._2, pair._1))
def -(t: A): Relation[A, B] =
fwd.get(t) match {
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)
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)
}
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)
override def equals(other: Any) = other match {
// 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.
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
}
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
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 _2s = pairs.map(_._2).toSet
@ -75,8 +74,8 @@ object RelationTest extends Properties("Relation") {
object EmptyRelationTest extends Properties("Empty relation") {
lazy val e = Relation.empty[Int, Double]
property("Forward empty") = forAll { (i: Int) => e.forward(i).isEmpty }
property("Reverse empty") = forAll { (i: Double) => e.reverse(i).isEmpty }
property("Forward empty") = forAll((i: Int) => e.forward(i).isEmpty)
property("Reverse empty") = forAll((i: Double) => e.reverse(i).isEmpty)
property("Forward map empty") = e.forwardMap.isEmpty
property("Reverse map empty") = e.reverseMap.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 lines2 = IO.readLines(fromString(file2))
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 pathB = fromString(b)
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)
()
}
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 =
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) {
@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."))
super.loadClass(className, resolve)
else

View File

@ -15,13 +15,15 @@ class ScriptRunner {
def processStatement(handler: StatementHandler, statement: Statement): Unit = {
val state = states(handler).asInstanceOf[handler.State]
val nextState =
try { Right(handler(statement.command, statement.arguments, state)) }
catch { case e: Exception => Left(e) }
try { Right(handler(statement.command, statement.arguments, state)) } catch {
case e: Exception => Left(e)
}
nextState match {
case Left(err) =>
if (statement.successExpected) {
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)
}
} else
@ -36,12 +38,11 @@ class ScriptRunner {
val handlers = Set() ++ statements.map(_._1)
try {
handlers.foreach { handler => states(handler) = handler.initialState }
handlers.foreach(handler => states(handler) = handler.initialState)
statements foreach (Function.tupled(processStatement))
} finally {
for (handler <- handlers; state <- states.get(handler)) {
try { handler.finish(state.asInstanceOf[handler.State]) }
catch { case e: Exception => () }
try { handler.finish(state.asInstanceOf[handler.State]) } catch { case e: Exception => () }
}
}
}

View File

@ -12,7 +12,12 @@ import sbt.internal.io.Resources
import java.util.concurrent.atomic.AtomicInteger
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 logger = newLogger
val allTests = get(tests, resourceBaseDirectory, logger) flatMap {
@ -36,15 +41,18 @@ object ScriptedRunnerImpl {
ScriptedTest(group, name)
}
private[sbt] val generateId: AtomicInteger = new AtomicInteger
private[sbt] def newLogger: ManagedLogger =
{
private[sbt] def newLogger: ManagedLogger = {
val loggerName = "scripted-" + generateId.incrementAndGet
val x = LogExchange.logger(loggerName)
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 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]] =
scriptedTest(group, name, Logger.xlog2Log(log))
def scriptedTest(group: String, name: String, log: ManagedLogger): Seq[() => Option[String]] =
scriptedTest(group, name, { _ => () }, log)
def scriptedTest(group: String, name: String, prescripted: File => Unit, log: ManagedLogger): Seq[() => Option[String]] = {
scriptedTest(group, name, (_ => ()), log)
def scriptedTest(
group: String,
name: String,
prescripted: File => Unit,
log: ManagedLogger
): Seq[() => Option[String]] = {
for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield {
val g = groupDir.getName
val n = nme.getName
val str = s"$g / $n"
() => {
() =>
{
println("Running " + str)
testResources.readWriteResourceDirectory(g, n) { testDirectory =>
val disabled = new File(testDirectory, "disabled").isFile
@ -68,24 +84,28 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
log.info("D " + str + " [DISABLED]")
None
} else {
try { scriptedTest(str, testDirectory, prescripted, log); None }
catch { case _: TestException | _: PendingTestSuccessException => Some(str) }
try { scriptedTest(str, testDirectory, prescripted, log); None } catch {
case _: TestException | _: PendingTestSuccessException => Some(str)
}
}
}
}
}
}
private def scriptedTest(label: String, testDirectory: File, prescripted: File => Unit, log: ManagedLogger): Unit =
{
private def scriptedTest(
label: String,
testDirectory: File,
prescripted: File => Unit,
log: ManagedLogger
): Unit = {
val buffered = BufferedAppender(consoleAppender)
LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
if (bufferLog) {
buffered.record()
}
def createParser() =
{
def createParser() = {
// val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
@ -99,8 +119,7 @@ final class ScriptedTests(resourceBaseDirectory: File, bufferLog: Boolean, handl
}
val pendingString = if (pending) " [PENDING]" else ""
def runTest() =
{
def runTest() = {
val run = new ScriptRunner
val parser = createParser()
run(parser.parse(file))
@ -148,22 +167,21 @@ object ListTests {
import ListTests._
final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) {
def filter = DirectoryFilter -- HiddenFileFilter
def listTests: Seq[ScriptedTest] =
{
def listTests: Seq[ScriptedTest] = {
list(baseDirectory, filter) flatMap { group =>
val groupName = group.getName
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 allTests = list(group, filter).sortBy(_.getName)
if (allTests.isEmpty) {
log.warn("No tests in test group " + groupName)
Seq.empty
} else {
val (included, skipped) = allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
val (included, skipped) =
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty)
log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) {

View File

@ -15,7 +15,10 @@ trait StatementHandler {
trait BasicStatementHandler extends StatementHandler {
final type State = Unit
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 finish(state: Unit) = ()
}

View File

@ -20,7 +20,12 @@ word ::= [^ \[\]]+
comment ::= '#' \S* nl
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 + "} "
}
@ -41,14 +46,13 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
if (handlers.keys.exists(key => key == '+' || key == '-'))
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)
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 {
case Success(result, next) => result
case err: NoSuccess =>
{
case err: NoSuccess => {
val labelString = label.map("'" + _ + "' ").getOrElse("")
sys.error("Could not parse test script, " + labelString + err.toString)
}
@ -56,28 +60,36 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
}
lazy val statements = rep1(space ~> statement <~ newline)
def statement: Parser[(StatementHandler, Statement)] =
{
def statement: Parser[(StatementHandler, Statement)] = {
trait PositionalStatement extends Positional {
def tuple: (StatementHandler, Statement)
}
positioned {
val command = (word | err("expected command"))
val arguments = rep(space ~> (word | failure("expected argument")))
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^
{
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^ {
case successExpected ~ start ~ command ~ arguments =>
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)
}
def successParser: Parser[Boolean] = ('+' ^^^ true) | ('-' ^^^ false) | success(true)
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 _) |
((newline | err("expected start character " + handlers.keys.mkString("(", "", ")"))) ~> failure("end of input"))
lazy val word: Parser[String] =
("\'" ~> "[^'\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
}

View File

@ -44,9 +44,15 @@ object Dependencies {
val scalatest = "org.scalatest" %% "scalatest" % "3.0.1"
val parserCombinator211 = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
val sjsonnew = Def.setting { "com.eed3si9n" %% "sjson-new-core" % contrabandSjsonNewVersion.value }
val sjsonnewScalaJson = Def.setting { "com.eed3si9n" %% "sjson-new-scalajson" % contrabandSjsonNewVersion.value }
val sjsonnewMurmurhash = Def.setting { "com.eed3si9n" %% "sjson-new-murmurhash" % contrabandSjsonNewVersion.value }
val sjsonnew = Def.setting {
"com.eed3si9n" %% "sjson-new-core" % 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"
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-contraband" % "0.3.0")
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`
*/
trait Cache[I, O] {
/**
* Queries the cache backed with store `store` for key `key`.
*/

View File

@ -3,5 +3,4 @@ package sbt.util
import sjsonnew.BasicJsonProtocol
object CacheImplicits extends CacheImplicits
trait CacheImplicits extends BasicCacheImplicits
with BasicJsonProtocol
trait CacheImplicits extends BasicCacheImplicits 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. */
abstract class CacheStore extends Input with Output {
/** Delete the persisted information. */
def delete(): Unit
}
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. */
def apply(cacheFile: File): CacheStore = file(cacheFile)
@ -25,6 +28,7 @@ object CacheStore {
/** Factory that can make new stores. */
abstract class CacheStoreFactory {
/** Create a new store. */
def make(identifier: String): CacheStore
@ -36,7 +40,8 @@ abstract class 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. */
def apply(base: File): CacheStoreFactory = directory(base)
@ -46,29 +51,38 @@ object CacheStoreFactory {
}
/** 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)
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`. */
class FileBasedStore[J: IsoString](file: File, converter: SupportConverter[J]) extends CacheStore {
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) =
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 close() = ()
}
/** 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 write[T: JsonWriter](value: T) = new PlainOutput(outputStream, converter).write(value)
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 FileModified(file: File, lastModified: Long) extends ModifiedFileInfo
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])
object FilesInfo {
@ -52,7 +53,8 @@ object FileInfo {
type F <: FileInfo
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(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))
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 {
@ -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 {

View File

@ -7,7 +7,9 @@ import sbt.io.{ IO, Using }
trait Input extends Closeable {
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 {

View File

@ -8,7 +8,8 @@ trait Output extends Closeable {
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
def write[T: JsonWriter](value: T) = {

View File

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

View File

@ -10,13 +10,17 @@ object StampedFormat extends BasicJsonProtocol {
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] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): T =
jsOpt match {
case Some(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 readValue = unbuilder.nextElement
val actualStamp = formatStamp.read(Some(readStamp), unbuilder)
@ -34,7 +38,10 @@ object StampedFormat extends BasicJsonProtocol {
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
}

View File

@ -13,7 +13,8 @@ import sbt.internal.util.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 {
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 =>
val store = new FileBasedStore(tmp / "cache-store", Converter)
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 {
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 =>
val store = new FileBasedStore(tmp / "cache-store", Converter)
f(cache, store)

View File

@ -10,28 +10,36 @@ object ChangeReport {
override def modified = files
override def markAllModified = this
}
def unmodified[T](files: Set[T]): ChangeReport[T] =
new EmptyChangeReport[T] {
override def checked = files
override def unmodified = files
}
}
/** The result of comparing some current set of objects against a previous set of objects.*/
trait ChangeReport[T] {
/** The set of all of the objects in the current set.*/
def checked: Set[T]
/** All of the objects that are in the same state in the current and reference sets.*/
def unmodified: Set[T]
/**
* 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.
*/
def modified: Set[T] // all changes, including added
/** All objects that are only in the current set.*/
def added: Set[T]
/** All objects only in the previous set*/
def removed: Set[T]
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
* 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
override def markAllModified = this
}
override def toString =
{
override def toString = {
val labels = 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(", ") }
keyValues.mkString("Change report:\n\t", "\n\t", "")
}
}
class EmptyChangeReport[T] extends ChangeReport[T] {
def checked = Set.empty[T]
def unmodified = Set.empty[T]
@ -61,7 +71,9 @@ class EmptyChangeReport[T] extends ChangeReport[T] {
def removed = Set.empty[T]
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 unmodified = a.unmodified ++ b.unmodified
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 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)
/**
@ -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 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] =
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)((in, out) => action(in.checked))
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
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 /
@ -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 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)
/**
@ -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 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 outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle)
inputs =>

View File

@ -13,6 +13,7 @@ import sjsonnew.JsonFormat
import sjsonnew.support.murmurhash.Hasher
object Tracked {
/**
* Creates a tracker that provides the last time it was evaluated.
* 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.
* 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.*/
def diffInputs(store: CacheStore, style: FileInfo.Style): Difference =
Difference.inputs(store, style)
/** 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.*/
def diffOutputs(store: CacheStore, style: FileInfo.Style): Difference =
Difference.outputs(store, style)
/** 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 */
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))
* }}}
*/
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] = {
import CacheImplicits.LongJsonFormat
implicitly
@ -131,7 +137,9 @@ object Tracked {
* 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] = {
import CacheImplicits.LongJsonFormat
implicitly
@ -159,7 +167,9 @@ object Tracked {
* 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)
private final class CacheHelp[I: JsonFormat](val sc: SingletonCache[Long]) {
@ -186,23 +196,29 @@ object Tracked {
}
trait Tracked {
/** Cleans outputs and clears the cache.*/
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()
/**
* Reads the previous timestamp, evaluates the provided function,
* 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 result = f(readTimestamp)
store.write(if (useStartTime) start else now())
result
}
private def now() = System.currentTimeMillis
def readTimestamp: Long =
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")
class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
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))
ifUnchanged(value)
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 = {
val equiv: Equiv[O] = implicitly
equiv.equiv(value, store.read[O])
}
}
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)
/**
@ -236,38 +258,46 @@ object Difference {
* before and after running the function.
*/
val outputs = constructor(true, true)
/**
* Provides a constructor for a Difference that does nothing on a call to 'clean' and saves the
* hash/last modified time of the files as they were prior to running the function.
*/
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 ()
clearCache()
}
private def clearCache() = store.delete()
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)
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
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
apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles)
}
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 currentFiles = abs(files)
val currentFilesInfo = style(currentFiles)

View File

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