diff --git a/backend/src/main/scala/bloop/logging/BloopLogger.scala b/backend/src/main/scala/bloop/logging/BloopLogger.scala index e20ccc67d7..2526a6bb99 100644 --- a/backend/src/main/scala/bloop/logging/BloopLogger.scala +++ b/backend/src/main/scala/bloop/logging/BloopLogger.scala @@ -29,6 +29,9 @@ final class BloopLogger( val debugFilter: DebugFilter, originId: Option[String] ) extends Logger { + + redirectOutputToLogs(out) + override def ansiCodesSupported() = true override def debug(msg: String)(implicit ctx: DebugFilter): Unit = if (isVerbose && debugFilter.isEnabledFor(ctx)) print(msg, printDebug) diff --git a/backend/src/test/scala/bloop/logging/RecordingLogger.scala b/backend/src/test/scala/bloop/logging/RecordingLogger.scala index c237836cf0..fb236bc438 100644 --- a/backend/src/test/scala/bloop/logging/RecordingLogger.scala +++ b/backend/src/test/scala/bloop/logging/RecordingLogger.scala @@ -15,6 +15,7 @@ class RecordingLogger( ) extends Logger { private[this] val messages = new ConcurrentLinkedQueue[(String, String)] + redirectOutputToLogs(System.out) def clear(): Unit = messages.clear() def debugs: List[String] = getMessagesAt(Some("debug")) diff --git a/frontend/src/test/scala/bloop/BaseCompileSpec.scala b/frontend/src/test/scala/bloop/BaseCompileSpec.scala index 7dba45821a..936acf8096 100644 --- a/frontend/src/test/scala/bloop/BaseCompileSpec.scala +++ b/frontend/src/test/scala/bloop/BaseCompileSpec.scala @@ -63,6 +63,38 @@ abstract class BaseCompileSpec extends bloop.testing.BaseSuite { } } } + test("compile-with-Vprint:typer") { + TestUtil.withinWorkspace { workspace => + val sources = List( + """/main/scala/Foo.scala + |class Foo + """.stripMargin + ) + + val logger = new RecordingLogger(ansiCodesSupported = false) + val `A` = TestProject(workspace, "a", sources, scalacOptions = List("-Vprint:typer")) + val projects = List(`A`) + val state = loadState(workspace, projects, logger) + val compiledState = state.compile(`A`) + assertExitStatus(compiledState, ExitStatus.Ok) + assertValidCompilationState(compiledState, projects) + + assertNoDiff( + logger.infos.filterNot(_.contains("Compiled")).mkString("\n").trim(), + """|Compiling a (1 Scala source) + |[[syntax trees at end of typer]] // Foo.scala + |package { + | class Foo extends scala.AnyRef { + | def (): Foo = { + | Foo.super.(); + | () + | } + | } + |} + |""".stripMargin + ) + } + } test("compile a project, delete an analysis and then write it back during a no-op compilation") { TestUtil.withinWorkspace { workspace => diff --git a/shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala b/shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala new file mode 100644 index 0000000000..99e2765fb5 --- /dev/null +++ b/shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala @@ -0,0 +1,70 @@ +package bloop.logging + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream + +import scala.util.control.NonFatal + +final class DuplicatingOutputStream( + val stdout: OutputStream, + val logged: ByteArrayOutputStream, + logger: Logger +) extends OutputStream { + + private val cache = new StringBuilder + if (stdout == null || logged == null) { + throw new NullPointerException() + } + + @throws[IOException] + override def write(b: Int): Unit = { + stdout.write(b) + logged.write(b) + logAndReset() + } + + @throws[IOException] + override def write(b: Array[Byte]): Unit = { + stdout.write(b) + logged.write(b) + logAndReset() + } + + @throws[IOException] + override def write(b: Array[Byte], off: Int, len: Int): Unit = { + stdout.write(b, off, len) + logged.write(b, off, len) + logAndReset() + } + + private def logAndReset() = synchronized { + try { + cache.append(logged.toString()) + logged.reset() + val logMessage = cache.toString.reverse.dropWhile(_ != '\n').reverse.trim() + + if (logMessage != "") { + cache.delete(0, logMessage.size + 1) + logger.info(logMessage) + } + } catch { + case NonFatal(_) => + } + } + + @throws[IOException] + override def flush(): Unit = { + stdout.flush() + logged.flush() + } + + @throws[IOException] + override def close(): Unit = { + try { + stdout.close() + } finally { + logged.close() + } + } +} diff --git a/shared/src/main/scala/bloop/logging/Logger.scala b/shared/src/main/scala/bloop/logging/Logger.scala index 19497d7603..1c81f57789 100644 --- a/shared/src/main/scala/bloop/logging/Logger.scala +++ b/shared/src/main/scala/bloop/logging/Logger.scala @@ -1,11 +1,20 @@ package bloop.logging +import java.io.ByteArrayOutputStream +import java.io.PrintStream import java.util.function.Supplier import bloop.io.Environment abstract class Logger extends xsbti.Logger with BaseSbtLogger { + // Duplicate the standard output so that we get printlns from the compiler + protected def redirectOutputToLogs(out: PrintStream) = { + val baos = new ByteArrayOutputStream() + val duplicating = new DuplicatingOutputStream(out, baos, this) + System.setOut(new PrintStream(duplicating)); + } + /** The name of the logger */ def name: String