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:

149
build.sbt
View File

@ -3,7 +3,7 @@ import Util._
//import com.typesafe.tools.mima.core._, ProblemFilters._
def baseVersion = "1.0.0-SNAPSHOT"
def internalPath = file("internal")
def internalPath = file("internal")
def commonSettings: Seq[Setting[_]] = Seq(
scalaVersion := scala212,
@ -18,42 +18,53 @@ 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")
}
},
scalacOptions in console in Compile -= "-Ywarn-unused-import",
scalacOptions in console in Test -= "-Ywarn-unused-import",
scalacOptions in console in Test -= "-Ywarn-unused-import",
publishArtifact in Compile := true,
publishArtifact in Test := false
)
val mimaSettings = Def settings (
mimaPreviousArtifacts := Set(organization.value % moduleName.value % "1.0.0-RC3"
cross (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled)
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(
git.baseVersion := baseVersion,
version := {
val v = version.value
if (v contains "SNAPSHOT") git.baseVersion.value
else v
},
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"))
)),
)
.settings(
inThisBuild(
Seq(
git.baseVersion := baseVersion,
version := {
val v = version.value
if (v contains "SNAPSHOT") git.baseVersion.value
else v
},
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")),
scalafmtOnCompile := true,
scalafmtVersion := "1.1.0",
)),
commonSettings,
name := "Util Root",
publish := {},
@ -67,21 +78,19 @@ lazy val utilRoot: Project = (project in file(".")).
// defines Java structures used across Scala versions, such as the API structures and relationships extracted by
// 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(
commonSettings,
javaOnlySettings,
name := "Util Interface",
exportJars := true,
mimaSettings,
)
lazy val utilInterface = (project in internalPath / "util-interface").settings(
commonSettings,
javaOnlySettings,
name := "Util Interface",
exportJars := true,
mimaSettings,
)
lazy val utilControl = (project in internalPath / "util-control").
settings(
commonSettings,
name := "Util Control",
mimaSettings,
)
lazy val utilControl = (project in internalPath / "util-control").settings(
commonSettings,
name := "Util Control",
mimaSettings,
)
val utilPosition = (project in file("internal") / "util-position").settings(
commonSettings,
@ -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,68 +119,69 @@ 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 ++= {
scalaVersion.value match {
case sv if sv startsWith "2.11" => Seq(parserCombinator211)
case sv if sv startsWith "2.12" => Seq(parserCombinator211)
case _ => Seq()
case _ => Seq()
}
},
mimaSettings,
).
configure(addSbtIO)
)
.configure(addSbtIO)
def customCommands: Seq[Setting[_]] = Seq(
commands += Command.command("release") { state =>
// "clean" ::
"+compile" ::
"+publishSigned" ::
"reload" ::
state
"+publishSigned" ::
"reload" ::
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,41 +13,43 @@ 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 =
{
val appender = new BufferedAppender(name, delegate)
appender.start
appender
}
def apply(name: String, delegate: Appender): BufferedAppender = {
val appender = new BufferedAppender(name, delegate)
appender.start
appender
}
}
/**
* Am appender that can buffer the logging done on it and then can flush the buffer
* An appender that can buffer the logging done on it and then can flush the buffer
* to the delegate appender provided in the constructor. Use 'record()' to
* 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 =
{
if (recording) {
buffer += event.toImmutable
} else delegate.append(event)
}
def append(event: XLogEvent): Unit = {
if (recording) {
buffer += event.toImmutable
} else delegate.append(event)
}
/** Enables buffering. */
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,
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
suppressedMessage: SuppressedTraceContext => Option[String] = ConsoleAppender.noSuppressedMessage): ConsoleLogger =
def apply(
out: ConsoleOut = ConsoleOut.systemOut,
ansiCodesSupported: Boolean = ConsoleAppender.formatEnabledInEnv,
useFormat: Boolean = ConsoleAppender.formatEnabledInEnv,
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,
override val ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]) extends BasicLogger {
class ConsoleLogger private[ConsoleLogger] (
out: ConsoleOut,
override val ansiCodesSupported: Boolean,
useFormat: Boolean,
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,14 +193,16 @@ object ConsoleAppender {
* formatting.
* @return A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]): ConsoleAppender = {
val appender = new ConsoleAppender(name, out, ansiCodesSupported, useFormat, suppressedMessage)
appender.start
appender
def apply(
name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
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")
@ -262,11 +275,11 @@ object ConsoleAppender {
* This logger is not thread-safe.
*/
class ConsoleAppender private[ConsoleAppender] (
name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]
name: String,
out: ConsoleOut,
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]
) extends AbstractAppender(name, null, LogExchange.dummyLayout, true) {
import scala.Console.{ BLUE, GREEN, RED, YELLOW }
@ -275,12 +288,12 @@ class ConsoleAppender private[ConsoleAppender] (
else ""
}
private val SUCCESS_LABEL_COLOR = GREEN
private val SUCCESS_LABEL_COLOR = GREEN
private val SUCCESS_MESSAGE_COLOR = reset
private val NO_COLOR = reset
private val NO_COLOR = reset
private var traceEnabledVar: Int = Int.MaxValue
def setTrace(level: Int): Unit = synchronized { traceEnabledVar = level }
/**
@ -307,9 +320,11 @@ 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)
}
}
/**
@ -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)
}
}
@ -395,31 +416,30 @@ class ConsoleAppender private[ConsoleAppender] (
private def appendTraceEvent(te: TraceEvent): Unit = {
val traceLevel = getTrace
val throwableShowLines: ShowLines[Throwable] =
ShowLines[Throwable]( (t: Throwable) => {
ShowLines[Throwable]((t: Throwable) => {
List(StackTrace.trimmed(t, traceLevel))
})
val codec: ShowLines[TraceEvent] =
ShowLines[TraceEvent]( (t: TraceEvent) => {
ShowLines[TraceEvent]((t: TraceEvent) => {
throwableShowLines.showLines(t.message)
})
codec.showLines(te).toVector foreach { appendLog(Level.Error, _) }
}
private def appendMessageContent(level: Level.Value, o: AnyRef): 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 {
private def appendMessageContent(level: Level.Value, o: AnyRef): 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 {
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, _) }
case _ => appendLog(level, oe.message.toString)
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach (appendLog(level, _))
case _ => appendLog(level, oe.message.toString)
}
}
}
o match {
case x: StringEvent => Vector(x.message) foreach { appendLog(level, _) }

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 =
{
val log = ConsoleLogger(console)
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
}
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 =
{
val loggerName = generateName
val log = LogExchange.logger(loggerName)
val appender = ConsoleAppender(ConsoleAppender.generateName, console)
LogExchange.bindLoggerAppenders(
loggerName, List(appender -> Level.Info)
)
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
}
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))
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,47 +10,78 @@ object MainAppender {
"GlobalBacking" + generateId.incrementAndGet
private val generateId: AtomicInteger = new AtomicInteger
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger =
{
import config._
// TODO
// console setTrace screenTrace
// backed setTrace backingTrace
// multi: Logger
def multiLogger(log: ManagedLogger, config: MainAppenderConfig): ManagedLogger = {
import config._
// TODO
// console setTrace screenTrace
// backed setTrace backingTrace
// multi: Logger
// val log = LogExchange.logger(loggerName)
LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders(
log.name,
(consoleOpt.toList map { _ -> screenLevel }) :::
List(backed -> backingLevel) :::
(extra map { x => (x -> Level.Info) })
)
log
}
// val log = LogExchange.logger(loggerName)
LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders(
log.name,
(consoleOpt.toList map { _ -> screenLevel }) :::
List(backed -> backingLevel) :::
(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)
}
newAppender
}
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

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

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

@ -8,12 +8,12 @@ import sjsonnew.support.scalajson.unsafe.Converter
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
final class ObjectEvent[A](
val level: Level.Value,
val message: A,
val channelName: Option[String],
val execId: Option[String],
val contentType: String,
val json: JValue
val level: Level.Value,
val message: A,
val channelName: Option[String],
val execId: Option[String],
val contentType: String,
val json: JValue
) extends Serializable {
override def toString: String =
s"ObjectEvent($level, $message, $channelName, $execId, $contentType, $json)"
@ -21,12 +21,18 @@ final class ObjectEvent[A](
object ObjectEvent {
def apply[A: JsonFormat](
level: Level.Value,
message: A,
channelName: Option[String],
execId: Option[String],
contentType: String
level: Level.Value,
message: A,
channelName: Option[String],
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,14 +8,13 @@ final case class StringTypeTag[A](key: String) {
}
object StringTypeTag {
def apply[A: TypeTag]: StringTypeTag[A] =
{
val tag = implicitly[TypeTag[A]]
val tpe = tag.tpe
val k = typeToString(tpe)
// println(tpe.getClass.toString + " " + k)
StringTypeTag[A](k)
}
def apply[A: TypeTag]: StringTypeTag[A] = {
val tag = implicitly[TypeTag[A]]
val tpe = tag.tpe
val k = typeToString(tpe)
// println(tpe.getClass.toString + " " + k)
StringTypeTag[A](k)
}
def typeToString(tpe: Type): String =
tpe match {
case TypeRef(_, sym, args) =>

View File

@ -17,7 +17,10 @@ trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
implicit val JBooleanFormat: JF[JBoolean] = projectFormat(_.get, (x: Boolean) => JBoolean(x))
implicit val 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

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

View File

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

View File

@ -13,6 +13,7 @@ abstract class AbstractLogger extends Logger {
def control(event: ControlEvent.Value, message: => String): Unit
def 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,23 +60,22 @@ object InterfaceUtil {
this.get2 == o.get2
case _ => false
}
override def hashCode: Int =
{
var hash = 1
hash = hash * 31 + this.get1.##
hash = hash * 31 + this.get2.##
hash
}
override def hashCode: Int = {
var hash = 1
hash = hash * 31 + this.get1.##
hash = hash * 31 + this.get2.##
hash
}
}
private final class ConcretePosition(
line0: Option[Integer],
content: String,
offset0: Option[Integer],
pointer0: Option[Integer],
pointerSpace0: Option[String],
sourcePath0: Option[String],
sourceFile0: Option[File]
line0: Option[Integer],
content: String,
offset0: Option[Integer],
pointer0: Option[Integer],
pointerSpace0: Option[String],
sourcePath0: Option[String],
sourceFile0: Option[File]
) extends Position {
val line = o2jo(line0)
val lineContent = content
@ -81,10 +87,10 @@ object InterfaceUtil {
}
private final class ConcreteProblem(
cat: String,
pos: Position,
msg: String,
sev: Severity
cat: String,
pos: Position,
msg: String,
sev: Severity
) extends Problem {
val category = cat
val position = pos

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

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

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,23 +83,39 @@ 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 =
{
val fmsg = toSupplier(msg)
level match {
case Level.Debug => lg.debug(fmsg)
case Level.Info => lg.info(fmsg)
case Level.Warn => lg.warn(fmsg)
case Level.Error => lg.error(fmsg)
}
def log(level: Level.Value, msg: => String): Unit = {
val fmsg = toSupplier(msg)
level match {
case Level.Debug => lg.debug(fmsg)
case Level.Info => lg.info(fmsg)
case Level.Warn => lg.warn(fmsg)
case Level.Error => lg.error(fmsg)
}
}
}
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,26 +8,25 @@ 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) =>
val removed: String = removeEscapeSequences(s)
("Escape sequence removed: '" + removed + "'") |:
(removed == s)
}
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)
}
property("No escape characters remain after removeEscapeSequences") = forAll { (s: String) =>
val removed: String = removeEscapeSequences(s)
@ -36,22 +35,26 @@ 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("")
val removed: String = removeEscapeSequences(withEscapes)
val original = start + escapes.map(_.notEscape).mkString("")
val diffCharString = diffIndex(original, removed)
("Input string : '" + withEscapes + "'") |:
("Expected : '" + original + "'") |:
("Escapes removed : '" + removed + "'") |:
(diffCharString) |:
(original == removed)
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)
("Input string : '" + withEscapes + "'") |:
("Expected : '" + original + "'") |:
("Escapes removed : '" + removed + "'") |:
(diffCharString) |:
(original == removed)
}
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 {
@ -64,10 +66,10 @@ object LogWriterTest extends Properties("Log Writer") {
}
/* The following are implicit generators to build up a write sequence.
* ToLog represents a written segment. NewLine represents one of the possible
* newline separators. A List[ToLog] represents a full line and always includes a
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in
* the `content` of a ToLog instance.*/
* ToLog represents a written segment. NewLine represents one of the possible
* newline separators. A List[ToLog] represents a full line and always includes a
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in
* the `content` of a ToLog instance.*/
implicit lazy val arbOut: Arbitrary[Output] = Arbitrary(genOutput)
implicit lazy val arbLog: Arbitrary[ToLog] = Arbitrary(genLog)
@ -76,7 +78,8 @@ object LogWriterTest extends Properties("Log Writer") {
implicit lazy val arbLevel: Arbitrary[Level.Value] = Arbitrary(genLevel)
implicit 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,35 +110,43 @@ 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 =
{
val builder = new StringBuilder(s.length)
for (c <- s) {
val char = c.toInt
def escaped = pad(char.toHexString.toUpperCase, 4, '0')
if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
}
builder.toString
}
def pad(s: String, minLength: Int, extra: Char) =
{
val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
def apply(s: String): String = {
val builder = new StringBuilder(s.length)
for (c <- s) {
val char = c.toInt
def escaped = pad(char.toHexString.toUpperCase, 4, '0')
if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
}
builder.toString
}
def pad(s: String, minLength: Int, extra: Char) = {
val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
}
/** 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,10 +3,9 @@ package sbt.internal.util
import sbt.util._
object TestLogger {
def apply[T](f: Logger => T): T =
{
val log = new BufferedLogger(ConsoleLogger())
log.setLevel(Level.Debug)
log.bufferQuietly(f(log))
}
}
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] =
{
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 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,46 +36,61 @@ 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)
private[sbt] def get[X, Y](map: M[X, Y], t: X): Set[Y] = map.getOrElse(t, Set.empty[Y])
private[sbt]type M[X, Y] = Map[X, Set[Y]]
private[sbt] type M[X, Y] = Map[X, Set[Y]]
}
/** Binary relation between A and B. It is a set of pairs (_1, _2) for _1 in A, _2 in B. */
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 _ => false
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,21 +11,20 @@ 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)]) =
{
val _1s = pairs.map(_._1).toSet
val _2s = pairs.map(_._2).toSet
def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) = {
val _1s = pairs.map(_._1).toSet
val _2s = pairs.map(_._2).toSet
r._1s == _1s && r.forwardMap.keySet == _1s &&
r._2s == _2s && r.reverseMap.keySet == _2s &&
pairs.forall {
case (a, b) =>
(r.forward(a) contains b) &&
(r.reverse(b) contains a) &&
(r.forwardMap(a) contains b) &&
(r.reverseMap(b) contains a)
}
r._1s == _1s && r.forwardMap.keySet == _1s &&
r._2s == _2s && r.reverseMap.keySet == _2s &&
pairs.forall {
case (a, b) =>
(r.forward(a) contains b) &&
(r.reverse(b) contains a) &&
(r.forwardMap(a) contains b) &&
(r.reverseMap(b) contains a)
}
}
property("Does not contain removed entries") = forAll { (pairs: List[(Int, Double, Boolean)]) =>
val add = pairs.map { case (a, b, c) => (a, b) }
@ -39,17 +38,17 @@ object RelationTest extends Properties("Relation") {
all(removeCoarse) { rem =>
("_1s does not contain removed" |: (!r._1s.contains(rem))) &&
("Forward does not contain removed" |: r.forward(rem).isEmpty) &&
("Forward map does not contain removed" |: !r.forwardMap.contains(rem)) &&
("Removed is not a value in reverse map" |: !r.reverseMap.values.toSet.contains(rem))
("Forward does not contain removed" |: r.forward(rem).isEmpty) &&
("Forward map does not contain removed" |: !r.forwardMap.contains(rem)) &&
("Removed is not a value in reverse map" |: !r.reverseMap.values.toSet.contains(rem))
} &&
all(removeFine) {
case (a, b) =>
("Forward does not contain removed" |: (!r.forward(a).contains(b))) &&
("Reverse does not contain removed" |: (!r.reverse(b).contains(a))) &&
("Forward map does not contain removed" |: (notIn(r.forwardMap, a, b))) &&
("Reverse map does not contain removed" |: (notIn(r.reverseMap, b, a)))
}
all(removeFine) {
case (a, b) =>
("Forward does not contain removed" |: (!r.forward(a).contains(b))) &&
("Reverse does not contain removed" |: (!r.reverse(b).contains(a))) &&
("Forward map does not contain removed" |: (notIn(r.forwardMap, a, b))) &&
("Reverse map does not contain removed" |: (notIn(r.reverseMap, b, a)))
}
}
property("Groups correctly") = forAll { (entries: List[(Int, Double)], randomInt: Int) =>
@ -75,10 +74,10 @@ object RelationTest extends Properties("Relation") {
object EmptyRelationTest extends Properties("Empty relation") {
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
property("_2 empty") = e._2s.isEmpty
}
}

View File

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

View File

@ -58,18 +58,20 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
val lines1 = IO.readLines(fromString(file1))
val 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 =
{
val pathA = fromString(a)
val pathB = fromString(b)
val isNewer = pathA.exists && (!pathB.exists || pathA.lastModified > pathB.lastModified)
if (!isNewer) {
scriptError(s"$pathA is not newer than $pathB")
}
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)
if (!isNewer) {
scriptError(s"$pathA is not newer than $pathB")
}
}
def exists(paths: List[String]): Unit = {
val notPresent = fromStrings(paths).filter(!_.exists)
if (notPresent.nonEmpty)
@ -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,13 +7,12 @@ package scripted
final class FilteredLoader(parent: ClassLoader) extends ClassLoader(parent) {
@throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[_] =
{
if (className.startsWith("java.") || className.startsWith("javax."))
super.loadClass(className, resolve)
else
throw new ClassNotFoundException(className)
}
override final def loadClass(className: String, resolve: Boolean): Class[_] = {
if (className.startsWith("java.") || className.startsWith("javax."))
super.loadClass(className, resolve)
else
throw new ClassNotFoundException(className)
}
override def getResources(name: String) = null
override def getResource(name: String) = null
}
}

View File

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

View File

@ -6,7 +6,7 @@ package internal
package scripted
final class TestException(statement: Statement, msg: String, exception: Throwable)
extends RuntimeException(statement.linePrefix + " " + msg, exception)
extends RuntimeException(statement.linePrefix + " " + msg, exception)
class ScriptRunner {
import scala.collection.mutable.HashMap
@ -15,14 +15,16 @@ 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 _ => throw new TestException(statement, "Command failed", err)
case t: TestFailed =>
throw new TestException(statement, "Command failed: " + t.getMessage, null)
case _ => throw new TestException(statement, "Command failed", err)
}
} else
()
@ -36,13 +38,12 @@ 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 =
{
val loggerName = "scripted-" + generateId.incrementAndGet
val x = LogExchange.logger(loggerName)
x
}
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,85 +61,96 @@ 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
if (disabled) {
log.info("D " + str + " [DISABLED]")
None
} else {
try { scriptedTest(str, testDirectory, prescripted, log); None }
catch { case _: TestException | _: PendingTestSuccessException => Some(str) }
() =>
{
println("Running " + str)
testResources.readWriteResourceDirectory(g, n) { testDirectory =>
val disabled = new File(testDirectory, "disabled").isFile
if (disabled) {
log.info("D " + str + " [DISABLED]")
None
} else {
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 =
{
val buffered = BufferedAppender(consoleAppender)
LogExchange.unbindLoggerAppenders(log.name)
LogExchange.bindLoggerAppenders(log.name, (buffered -> Level.Debug) :: Nil)
if (bufferLog) {
buffered.record()
}
def createParser() =
{
// val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
val scriptConfig = new ScriptConfig(label, testDirectory, log)
new TestScriptParser(handlersProvider getHandlers scriptConfig)
}
val (file, pending) = {
val normal = new File(testDirectory, ScriptFilename)
val pending = new File(testDirectory, PendingScriptFilename)
if (pending.isFile) (pending, true) else (normal, false)
}
val pendingString = if (pending) " [PENDING]" else ""
def runTest() =
{
val run = new ScriptRunner
val parser = createParser()
run(parser.parse(file))
}
def testFailed(): Unit = {
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
log.error("x " + label + pendingString)
}
try {
prescripted(testDirectory)
runTest()
log.info("+ " + label + pendingString)
if (pending) throw new PendingTestSuccessException(label)
} catch {
case e: TestException =>
testFailed()
e.getCause match {
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
case _ => if (!pending) e.printStackTrace
}
if (!pending) throw e
case e: PendingTestSuccessException =>
testFailed()
log.error(" Mark as passing to remove this failure.")
throw e
case e: Exception =>
testFailed()
if (!pending) throw e
} finally { buffered.clearBuffer() }
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() = {
// val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
val scriptConfig = new ScriptConfig(label, testDirectory, log)
new TestScriptParser(handlersProvider getHandlers scriptConfig)
}
val (file, pending) = {
val normal = new File(testDirectory, ScriptFilename)
val pending = new File(testDirectory, PendingScriptFilename)
if (pending.isFile) (pending, true) else (normal, false)
}
val pendingString = if (pending) " [PENDING]" else ""
def runTest() = {
val run = new ScriptRunner
val parser = createParser()
run(parser.parse(file))
}
def testFailed(): Unit = {
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
log.error("x " + label + pendingString)
}
try {
prescripted(testDirectory)
runTest()
log.info("+ " + label + pendingString)
if (pending) throw new PendingTestSuccessException(label)
} catch {
case e: TestException =>
testFailed()
e.getCause match {
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
case _ => if (!pending) e.printStackTrace
}
if (!pending) throw e
case e: PendingTestSuccessException =>
testFailed()
log.error(" Mark as passing to remove this failure.")
throw e
case e: Exception =>
testFailed()
if (!pending) throw e
} finally { buffered.clearBuffer() }
}
}
// object ScriptedTests extends ScriptedRunner {
@ -148,31 +167,30 @@ object ListTests {
import ListTests._
final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) {
def filter = DirectoryFilter -- HiddenFileFilter
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] =
{
def listTests: Seq[ScriptedTest] = {
list(baseDirectory, filter) flatMap { group =>
val groupName = group.getName
val allTests = list(group, filter).sortBy(_.getName)
if (allTests.isEmpty) {
log.warn("No tests in test group " + groupName)
Seq.empty
} else {
val (included, skipped) = allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty)
log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) {
log.warn("Tests skipped in group " + group.getName + ":")
skipped.foreach(testName => log.warn(" " + testName.getName))
}
Seq(included.map(_.getName): _*)
}
listTests(group).map(ScriptedTest(groupName, _))
}
}
private[this] def listTests(group: File): Seq[String] = {
val groupName = group.getName
val allTests = list(group, filter).sortBy(_.getName)
if (allTests.isEmpty) {
log.warn("No tests in test group " + groupName)
Seq.empty
} else {
val (included, skipped) =
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty)
log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) {
log.warn("Tests skipped in group " + group.getName + ":")
skipped.foreach(testName => log.warn(" " + testName.getName))
}
Seq(included.map(_.getName): _*)
}
}
}
class PendingTestSuccessException(label: String) extends Exception {

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) = ()
}
@ -23,4 +26,4 @@ trait BasicStatementHandler extends StatementHandler {
/** Use when a stack trace is not useful */
final class TestFailed(msg: String) extends RuntimeException(msg) {
override def fillInStackTrace = this
}
}

View File

@ -19,8 +19,13 @@ successChar ::= '+' | '-'
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,43 +46,50 @@ 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)] =
{
parseAll(statements, script) match {
case Success(result, next) => result
case err: NoSuccess =>
{
val labelString = label.map("'" + _ + "' ").getOrElse("")
sys.error("Could not parse test script, " + labelString + err.toString)
}
private def parse(script: String, label: Option[String]): List[(StatementHandler, Statement)] = {
parseAll(statements, script) match {
case Success(result, next) => result
case err: NoSuccess => {
val labelString = label.map("'" + _ + "' ").getOrElse("")
sys.error("Could not parse test script, " + labelString + err.toString)
}
}
}
lazy val statements = rep1(space ~> statement <~ newline)
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) ^^
{
case successExpected ~ start ~ command ~ arguments =>
new PositionalStatement {
def tuple = (handlers(start), new Statement(command, arguments, successExpected, pos.line))
}
}
} ^^ (_.tuple)
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) ^^ {
case successExpected ~ start ~ command ~ arguments =>
new PositionalStatement {
def tuple =
(handlers(start), new Statement(command, arguments, successExpected, pos.line))
}
}
} ^^ (_.tuple)
}
def successParser: Parser[Boolean] = ('+' ^^^ true) | ('-' ^^^ false) | success(true)
def 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.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`.
*/
@ -59,7 +60,7 @@ object Cache {
val result = default(key)
update(result)
result
}
}
def debug[I](label: String, cache: SingletonCache[I]): SingletonCache[I] =
new SingletonCache[I] {

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,10 +69,12 @@ class CacheSpec extends UnitSpec {
}
}
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(implicit cache: Cache[K, V]): Unit =
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(
implicit cache: Cache[K, V]
): Unit =
IO.withTemporaryDirectory { tmp =>
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,10 +84,12 @@ class SingletonCacheSpec extends UnitSpec {
}
}
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(implicit cache: SingletonCache[T]): Unit =
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(
implicit cache: SingletonCache[T]
): Unit =
IO.withTemporaryDirectory { tmp =>
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 =
{
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", "")
}
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,20 +132,21 @@ 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] =
{
lazy val inCache = Difference.inputs(storeFactory.make("in-cache"), inStyle)
lazy val outCache = Difference.outputs(storeFactory.make("out-cache"), outStyle)
inputs =>
{
inCache(inputs) { inReport =>
outCache { outReport =>
if (inReport.modified.isEmpty && outReport.modified.isEmpty)
outReport.checked
else
action(inReport, outReport)
}
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 =>
{
inCache(inputs) { inReport =>
outCache { outReport =>
if (inReport.modified.isEmpty && outReport.modified.isEmpty)
outReport.checked
else
action(inReport, outReport)
}
}
}
}
}
}

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 =
{
val start = now()
val result = f(readTimestamp)
store.write(if (useStartTime) start else now())
result
}
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,24 +226,30 @@ 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 =>
{
if (uptodate(value))
ifUnchanged(value)
else {
update(value)
ifChanged(value)
}
}
def update(value: O): Unit = store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value => {
if (uptodate(value))
ifUnchanged(value)
else {
update(value)
ifChanged(value)
}
}
def update(value: O): Unit =
store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
def uptodate(value: O): Boolean = {
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,55 +258,63 @@ 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() =
{
if (defineClean) IO.delete(raw(cachedFilesInfo)) else ()
clearCache()
}
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 =
{
val lastFilesInfo = cachedFilesInfo
apply(files, lastFilesInfo)(f)(_ => files)
}
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 =
{
val lastFilesInfo = cachedFilesInfo
apply(raw(lastFilesInfo), lastFilesInfo)(f)(toFiles)
}
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 =
{
val lastFiles = raw(lastFilesInfo)
val currentFiles = abs(files)
val currentFilesInfo = style(currentFiles)
val report = new ChangeReport[File] {
lazy val checked = currentFiles
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
lazy val unmodified = checked -- modified
}
private[this] def apply[T](files: Set[File], lastFilesInfo: Set[style.F])(
f: ChangeReport[File] => T
)(extractFiles: T => Set[File]): T = {
val lastFiles = raw(lastFilesInfo)
val currentFiles = abs(files)
val currentFilesInfo = style(currentFiles)
val result = f(report)
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
store.write(info)(style.formats)
result
val report = new ChangeReport[File] {
lazy val checked = currentFiles
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
lazy val unmodified = checked -- modified
}
val result = f(report)
val info = if (filesAreOutputs) style(abs(extractFiles(result))) else currentFilesInfo
store.write(info)(style.formats)
result
}
}

View File

@ -11,7 +11,6 @@ import sbt.internal.util.UnitSpec
class TrackedSpec extends UnitSpec {
"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)
}
@ -161,4 +158,4 @@ class TrackedSpec extends UnitSpec {
f(store)
}
}
}