From 3d5d6c95f4aa49047a6c68691786f5647e17eec1 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 28 Jun 2024 18:30:08 +0200 Subject: [PATCH 1/3] bugfix: Forward standard output to logger Causes https://github.com/VirtusLab/scala-cli/issues/1023 --- .../scala/bloop/logging/BloopLogger.scala | 3 + .../scala/bloop/logging/RecordingLogger.scala | 1 + .../test/scala/bloop/BaseCompileSpec.scala | 32 +++++++++ .../logging/DuplicatingOutputStream.scala | 70 +++++++++++++++++++ .../src/main/scala/bloop/logging/Logger.scala | 9 +++ 5 files changed, 115 insertions(+) create mode 100644 shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala 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 352330e559..7fbd00b407 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 From b4fe0014dd8fe56ad85a47b783abe2387d1c37e1 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 28 Jun 2024 18:30:08 +0200 Subject: [PATCH 2/3] bugfix: Forward standard output to logger Causes https://github.com/VirtusLab/scala-cli/issues/1023 --- .../scala/bloop/logging/BloopLogger.scala | 2 +- .../internal/BloopHighLevelCompiler.scala | 22 ++++++ .../scala/bloop/logging/RecordingLogger.scala | 1 + .../logging/DuplicatingOutputStream.scala | 70 ------------------- .../src/main/scala/bloop/logging/Logger.scala | 5 +- .../scala/bloop/logging/TeeOutputStream.scala | 36 ++++++++++ 6 files changed, 61 insertions(+), 75 deletions(-) delete mode 100644 shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala create mode 100644 shared/src/main/scala/bloop/logging/TeeOutputStream.scala diff --git a/backend/src/main/scala/bloop/logging/BloopLogger.scala b/backend/src/main/scala/bloop/logging/BloopLogger.scala index 2526a6bb99..95700f631d 100644 --- a/backend/src/main/scala/bloop/logging/BloopLogger.scala +++ b/backend/src/main/scala/bloop/logging/BloopLogger.scala @@ -30,7 +30,7 @@ final class BloopLogger( originId: Option[String] ) extends Logger { - redirectOutputToLogs(out) + redirectOutputToLogs(System.out) override def ansiCodesSupported() = true override def debug(msg: String)(implicit ctx: DebugFilter): Unit = diff --git a/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala b/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala index 43deccb562..95e2617975 100644 --- a/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala +++ b/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala @@ -1,6 +1,7 @@ // scalafmt: { maxColumn = 250 } package sbt.internal.inc.bloop.internal +import java.io.ByteArrayOutputStream import java.nio.file.Files import java.util.Optional @@ -8,6 +9,7 @@ import scala.concurrent.Promise import scala.util.control.NonFatal import bloop.logging.ObservedLogger +import bloop.logging.TeeOutputStream import bloop.reporter.ZincReporter import bloop.task.Task import bloop.tracing.BraveTracer @@ -107,6 +109,14 @@ final class BloopHighLevelCompiler( } } + def withTee(block: TeeOutputStream => Unit) = { + System.out match { + case tee: TeeOutputStream => + block(tee) + case _ => + } + + } def compileSources( sources: Seq[VirtualFile], scalacOptions: Array[String], @@ -120,6 +130,11 @@ final class BloopHighLevelCompiler( throw new CompileFailed(new Array(0), s"Expected Scala library jar in Scala instance containing ${scalac.scalaInstance.allJars().mkString(", ")}", new Array(0)) } try { + val baos = new ByteArrayOutputStream() + withTee { + _.addListener(baos) + } + scalac.compile( sources.toArray, classpath.toArray, @@ -132,6 +147,13 @@ final class BloopHighLevelCompiler( config.progress.toOptional, logger ) + + withTee { tee => + val result = baos.toString() + if (result.nonEmpty) + logger.info(baos.toString) + tee.removeListener(baos) + } } catch { case t: StackOverflowError => val msg = "Encountered a StackOverflowError coming from the compiler. You might need to restart your Bloop build server" diff --git a/backend/src/test/scala/bloop/logging/RecordingLogger.scala b/backend/src/test/scala/bloop/logging/RecordingLogger.scala index fb236bc438..cd3fcbd57d 100644 --- a/backend/src/test/scala/bloop/logging/RecordingLogger.scala +++ b/backend/src/test/scala/bloop/logging/RecordingLogger.scala @@ -16,6 +16,7 @@ class RecordingLogger( 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/shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala b/shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala deleted file mode 100644 index 99e2765fb5..0000000000 --- a/shared/src/main/scala/bloop/logging/DuplicatingOutputStream.scala +++ /dev/null @@ -1,70 +0,0 @@ -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 1c81f57789..dcaa554049 100644 --- a/shared/src/main/scala/bloop/logging/Logger.scala +++ b/shared/src/main/scala/bloop/logging/Logger.scala @@ -1,6 +1,5 @@ package bloop.logging -import java.io.ByteArrayOutputStream import java.io.PrintStream import java.util.function.Supplier @@ -10,9 +9,7 @@ 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)); + System.setOut(new TeeOutputStream(out)) } /** The name of the logger */ diff --git a/shared/src/main/scala/bloop/logging/TeeOutputStream.scala b/shared/src/main/scala/bloop/logging/TeeOutputStream.scala new file mode 100644 index 0000000000..089d4b42c6 --- /dev/null +++ b/shared/src/main/scala/bloop/logging/TeeOutputStream.scala @@ -0,0 +1,36 @@ +package bloop.logging + +import java.io.PrintStream +import java.io.ByteArrayOutputStream +import scala.collection.concurrent.TrieMap + +class TeeOutputStream(targetPS: PrintStream) extends PrintStream(targetPS) { + + private val allStreams = TrieMap.empty[Int, ByteArrayOutputStream]; + + override def write(b: Int): Unit = { + allStreams.values.foreach( + _.write(b) + ) + targetPS.write(b) + } + + override def write(buf: Array[Byte], off: Int, len: Int): Unit = { + allStreams.values.foreach( + _.write(buf, off, len) + ) + targetPS.write(buf, off, len) + } + + override def flush(): Unit = { + targetPS.flush() + } + + def addListener(baos: ByteArrayOutputStream): Unit = { + allStreams += baos.hashCode -> baos + } + + def removeListener(baos: ByteArrayOutputStream): Unit = { + allStreams -= baos.hashCode + } +} From c7f34844de70c7f46774e9403d41380df5d40d37 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 6 Aug 2024 16:46:07 +0200 Subject: [PATCH 3/3] improvement: Move logging stdout to a finally block --- .../internal/BloopHighLevelCompiler.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala b/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala index 95e2617975..33b6549f57 100644 --- a/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala +++ b/backend/src/main/scala/sbt/internal/inc/bloop/internal/BloopHighLevelCompiler.scala @@ -129,12 +129,12 @@ final class BloopHighLevelCompiler( if (scalac.scalaInstance.libraryJars().isEmpty) { throw new CompileFailed(new Array(0), s"Expected Scala library jar in Scala instance containing ${scalac.scalaInstance.allJars().mkString(", ")}", new Array(0)) } - try { - val baos = new ByteArrayOutputStream() - withTee { - _.addListener(baos) - } + val baos = new ByteArrayOutputStream() + withTee { + _.addListener(baos) + } + try { scalac.compile( sources.toArray, classpath.toArray, @@ -147,13 +147,6 @@ final class BloopHighLevelCompiler( config.progress.toOptional, logger ) - - withTee { tee => - val result = baos.toString() - if (result.nonEmpty) - logger.info(baos.toString) - tee.removeListener(baos) - } } catch { case t: StackOverflowError => val msg = "Encountered a StackOverflowError coming from the compiler. You might need to restart your Bloop build server" @@ -172,6 +165,13 @@ final class BloopHighLevelCompiler( throw new InterfaceCompileCancelled(Array(), "Caught NPE when compilation was cancelled!") case t => throw t } + } finally { + withTee { tee => + val result = baos.toString() + if (result.nonEmpty) + logger.info(baos.toString) + tee.removeListener(baos) + } } }