Skip to content

Commit

Permalink
Fix sbt#318: Add a ReporterUtil API
Browse files Browse the repository at this point in the history
This API is inspired by the sbt backend that implements the whole
machinery for `ManagedLogger` and instantiates `LoggerReporter`.

The provided functionality is to get a default reporter configuration
that customises the default instantiation of the reporter. The
instantiation is provided in the same API.

We use contraband for the default reporter configuration to get the
builder pattern so that users can modify it more easily.
  • Loading branch information
jvican committed Jul 13, 2017
1 parent d5c3cb8 commit d96eb1a
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 1 deletion.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ lazy val zincIvyIntegration = (project in internalPath / "zinc-ivy-integration")

// sbt-side interface to compiler. Calls compiler-side interface reflectively
lazy val zincCompileCore = (project in internalPath / "zinc-compile-core")
.enablePlugins(ContrabandPlugin)
.dependsOn(
compilerInterface % "compile;test->test",
zincClasspath,
Expand Down
17 changes: 17 additions & 0 deletions internal/zinc-compile-core/src/main/contraband/reporter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"types": [
{
"name": "ReporterConfig",
"namespace": "xsbti",
"target": "Java",
"type": "record",
"fields": [
{ "name": "loggerName", "type": "String" },
{ "name": "maximumErrors", "type": "int" },
{ "name": "useColor", "type": "boolean" },
{ "name": "logLevel", "type": "java.util.logging.Level" },
{ "name": "positionMapper", "type": "java.util.function.Function<Position, Position>" }
]
}
]
}
18 changes: 18 additions & 0 deletions internal/zinc-compile-core/src/main/java/xsbti/ReporterUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package xsbti;

import sbt.internal.inc.LoggerReporter;
import sbt.internal.inc.ReporterManager;
import sbt.internal.util.ManagedLogger;
import sbt.util.Level$;

import java.util.function.Function;

public interface ReporterUtil {
public static ReporterConfig getDefaultReporterConfig() {
return ReporterManager.getDefaultReporterConfig();
}

public static Reporter getDefault(ReporterConfig config) {
return ReporterManager.getReporter(System.out, config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object LoggerReporter {
jo2o(pos.offset).hashCode * 31 + jo2o(pos.sourceFile).hashCode
}

def countElementsAsString(n: Int, elements: String): String =
def countElementsAsString(n: Int, elements: String): String = {
n match {
case 0 => "no " + elements + "s"
case 1 => "one " + elements
Expand All @@ -48,6 +48,7 @@ object LoggerReporter {
case 4 => "four " + elements + "s"
case _ => "" + n + " " + elements + "s"
}
}

lazy val problemFormats: ProblemFormats = new ProblemFormats with SeverityFormats
with PositionFormats with sjsonnew.BasicJsonProtocol {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package sbt.internal.inc

import java.io.{ OutputStreamWriter, PrintStream, PrintWriter }
import java.nio.charset.StandardCharsets
import java.util.logging.Level

import sbt.internal.util.{ ConsoleAppender, MainAppender }
import sbt.util.{ LogExchange }
import sbt.util.{ Level => SbtLevel }
import xsbti.{ Position, Reporter, ReporterConfig }

object ReporterManager {
import java.util.concurrent.atomic.AtomicInteger
private val idGenerator: AtomicInteger = new AtomicInteger
private val DefaultReporterName = "zinc-out"
private def generateZincReporterId(name: String): String =
s"$name-${idGenerator.incrementAndGet}"

/**
* Returns the id that is used to know what's the minimum level from which the
* reporter should log messages from the compilers.
*
* It uses `java.util.logging.Level` because `sbt.util.Level` is a Scala enumeration
* and there are lots of deficiencies in its design (as well as in some bugs in
* scalac) that prevent its leak in the public Java API, even though it's supposed
* to be public. For instance, I have tried to refer to it in the following ways:
*
* - `sbt.util.Level.Info` -> Scalac passes, Javac fails.
* - `sbt.util.Level$.Info` -> Scalac error: type mismatch.
* - `sbt.util.Level$.MODULE$.Info` -> Scalac fails with stable identifier required.
*
* Using `CompileOrder.Mixed` does not solve the issue. Therefore, we fallback
* on `java.util.logging.Level` and we transform to `sbt.util.Level` at runtime. This
* provides more type safety than asking users to provide the id of `sbt.util.Level`.
*
* TODO(someone): Don't define `sbt.util.Level` as a Scala enumeration.
*/
private def fromJavaLogLevel(level: Level): SbtLevel.Value = {
level match {
case Level.INFO => SbtLevel.Info
case Level.WARNING => SbtLevel.Warn
case Level.SEVERE => SbtLevel.Error
case Level.OFF => sys.error("Level.OFF is not supported. Change the logging level.")
case _ => SbtLevel.Debug
}
}

private val FallbackUseColor = ConsoleAppender.formatEnabled
private val NoPositionMapper = java.util.function.Function.identity[Position]()

import java.util.function.{ Function => JavaFunction }
private implicit class EnrichedJava[T, R](f: JavaFunction[T, R]) {
def toScala: Function[T, R] =
new Function[T, R] { override def apply(v1: T): R = f.apply(v1) }
}

/** Returns sane defaults with a long tradition in sbt. */
def getDefaultReporterConfig: ReporterConfig =
new ReporterConfig(DefaultReporterName, 100, FallbackUseColor, Level.INFO, NoPositionMapper)

def getReporter(toOutput: PrintWriter, config: ReporterConfig): Reporter = {
val printWriterToAppender = MainAppender.defaultBacked(config.useColor())
val appender = printWriterToAppender(toOutput)
val freshName = generateZincReporterId(config.loggerName())
val logger = LogExchange.logger(freshName)
val loggerName = logger.name

LogExchange.unbindLoggerAppenders(loggerName)
val sbtLogLevel = fromJavaLogLevel(config.logLevel())
val toAppend = List(appender -> sbtLogLevel)
LogExchange.bindLoggerAppenders(loggerName, toAppend)
new LoggerReporter(config.maximumErrors(), logger, config.positionMapper().toScala)
}

def getReporter(toOutput: PrintStream, config: ReporterConfig): Reporter = {
val utf8Writer = new OutputStreamWriter(toOutput, StandardCharsets.UTF_8)
val printWriter = new PrintWriter(utf8Writer)
getReporter(printWriter, config)
}
}

0 comments on commit d96eb1a

Please sign in to comment.