From cbad6401453ca20ed4fc4b138d95b9d676f26049 Mon Sep 17 00:00:00 2001 From: jvican Date: Tue, 20 Jun 2017 18:34:26 +0200 Subject: [PATCH] Fix #318: Add a `ReporterUtil` API 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. --- build.sbt | 1 + .../src/main/contraband/reporter.json | 17 ++++ .../src/main/java/xsbti/ReporterUtil.java | 18 +++++ .../sbt/internal/inc/LoggerReporter.scala | 3 +- .../sbt/internal/inc/ReporterManager.scala | 80 +++++++++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 internal/zinc-compile-core/src/main/contraband/reporter.json create mode 100644 internal/zinc-compile-core/src/main/java/xsbti/ReporterUtil.java create mode 100644 internal/zinc-compile-core/src/main/scala/sbt/internal/inc/ReporterManager.scala diff --git a/build.sbt b/build.sbt index 7228ce2428..59acf642c6 100644 --- a/build.sbt +++ b/build.sbt @@ -253,6 +253,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") .disablePlugins(com.typesafe.sbt.SbtScalariform) + .enablePlugins(ContrabandPlugin) .dependsOn(compilerInterface % "compile;test->test", zincClasspath, zincApiInfo, diff --git a/internal/zinc-compile-core/src/main/contraband/reporter.json b/internal/zinc-compile-core/src/main/contraband/reporter.json new file mode 100644 index 0000000000..7519f31a3c --- /dev/null +++ b/internal/zinc-compile-core/src/main/contraband/reporter.json @@ -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" } + ] + } + ] +} diff --git a/internal/zinc-compile-core/src/main/java/xsbti/ReporterUtil.java b/internal/zinc-compile-core/src/main/java/xsbti/ReporterUtil.java new file mode 100644 index 0000000000..c17dea3447 --- /dev/null +++ b/internal/zinc-compile-core/src/main/java/xsbti/ReporterUtil.java @@ -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); + } +} diff --git a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/LoggerReporter.scala b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/LoggerReporter.scala index 0072dcbb8e..46c05d0cf7 100644 --- a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/LoggerReporter.scala +++ b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/LoggerReporter.scala @@ -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 @@ -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 {} diff --git a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/ReporterManager.scala b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/ReporterManager.scala new file mode 100644 index 0000000000..f3a3bc3194 --- /dev/null +++ b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/ReporterManager.scala @@ -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) + } +}