diff --git a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/ForkedJava.scala b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/ForkedJava.scala index 3137e9a6a6..cba19671a1 100644 --- a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/ForkedJava.scala +++ b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/ForkedJava.scala @@ -43,7 +43,7 @@ object ForkedJava { try { exitCode = Process(exe +: forkArgs, cwd) ! javacLogger } finally { - javacLogger.flush(exitCode) + javacLogger.flush(program, exitCode) } // We return true or false, depending on success. exitCode == 0 diff --git a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavaErrorParser.scala b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavaErrorParser.scala index e9f86a4b10..91db8658fa 100644 --- a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavaErrorParser.scala +++ b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavaErrorParser.scala @@ -220,12 +220,16 @@ class JavaErrorParser(relativeDir: File = new File(new File(".").getAbsolutePath */ final def parseProblems(in: String, logger: sbt.util.Logger): Seq[Problem] = parse(javacOutput, in) match { - case Success(result, _) => result - case Failure(_, n) => - logger.warn(s"Unexpected javac output at:${n.pos.longString}.") + case Success(result, next) => + if (!next.atEnd) { + logger.warn(s"Unexpected javac output: ${next.source}.") + } + result + case Failure(msg, n) => + logger.warn(s"Unexpected javac output at ${n.pos.longString}: $msg.") Seq.empty - case Error(_, n) => - logger.warn(s"Unexpected javac output at:${n.pos.longString}.") + case Error(msg, n) => + logger.warn(s"Unexpected javac output at ${n.pos.longString}: $msg.") Seq.empty } diff --git a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavacProcessLogger.scala b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavacProcessLogger.scala index f3ba6c7b2a..2555b32a75 100644 --- a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavacProcessLogger.scala +++ b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/JavacProcessLogger.scala @@ -13,6 +13,8 @@ package javac import xsbti._ import java.io.File + +import scala.collection.mutable.ListBuffer import scala.sys.process.ProcessLogger /** @@ -20,43 +22,37 @@ import scala.sys.process.ProcessLogger * dump logs. * * - * @param log The logger where all input will go. + * @param log The logger where all non-semantic messages will go. * @param reporter A reporter for semantic Javac error messages. * @param cwd The current working directory of the Javac process, used when parsing Filenames. */ final class JavacLogger(log: sbt.util.Logger, reporter: Reporter, cwd: File) extends ProcessLogger { - import scala.collection.mutable.ListBuffer - import sbt.util.Level.{ Info, Error, Value => LogLevel } - - private val msgs: ListBuffer[(LogLevel, String)] = new ListBuffer() + private var out: ListBuffer[String] = new ListBuffer() + private var err: ListBuffer[String] = new ListBuffer() def out(s: => String): Unit = - synchronized { msgs += ((Info, s)); () } + synchronized { out += s } def err(s: => String): Unit = - synchronized { msgs += ((Error, s)); () } + synchronized { err += s } def buffer[T](f: => T): T = f - // Helper method to dump all semantic errors. - private def parseAndDumpSemanticErrors(): Unit = { - val input = - msgs collect { - case (Error, msg) => msg - } mkString "\n" - val parser = new JavaErrorParser(cwd) - parser.parseProblems(input, log).foreach(reporter.log) - } + def flush(exitCode: Int): Unit = flush("tool", exitCode) - def flush(exitCode: Int): Unit = { - parseAndDumpSemanticErrors() - // Here we only display things that wouldn't otherwise be output by the error reporter. + def flush(toolname: String, exitCode: Int): Unit = { // TODO - NOTES may not be displayed correctly! - msgs collect { - case (Info, msg) => msg - } foreach { msg => - log.info(msg) + synchronized { + val parser = new JavaErrorParser(cwd) + + parser.parseProblems(err.mkString("\n"), log).foreach(reporter.log(_)) + out.foreach(log.info(_)) + + if (exitCode != 0) + log.warn(s"$toolname exited with exit code $exitCode") + + out.clear() + err.clear() } - msgs.clear() } } diff --git a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/LocalJava.scala b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/LocalJava.scala index 2e10a119f7..bec93ade2a 100644 --- a/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/LocalJava.scala +++ b/internal/zinc-compile-core/src/main/scala/sbt/internal/inc/javac/LocalJava.scala @@ -101,7 +101,7 @@ final class LocalJavadoc() extends XJavadoc { } finally { warnOrError.close() infoWriter.close() - javacLogger.flush(exitCode) + javacLogger.flush("javadoc", exitCode) } // We return true or false, depending on success. exitCode == 0 diff --git a/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/CollectingLogger.scala b/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/CollectingLogger.scala new file mode 100644 index 0000000000..807b7678b7 --- /dev/null +++ b/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/CollectingLogger.scala @@ -0,0 +1,14 @@ +package sbt.internal.inc.javac + +import sbt.util.{ Level, Logger } + +class CollectingLogger extends Logger { + var messages: Map[Level.Value, Seq[String]] = Map.empty.withDefaultValue(Seq.empty) + + override def trace(t: => Throwable): Unit = ??? + override def success(message: => String): Unit = ??? + override def log(level: Level.Value, message: => String): Unit = + synchronized { + messages = messages.updated(level, messages(level) :+ message) + } +} diff --git a/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/CollectingReporter.scala b/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/CollectingReporter.scala new file mode 100644 index 0000000000..416ec2ac41 --- /dev/null +++ b/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/CollectingReporter.scala @@ -0,0 +1,19 @@ +package sbt +package internal +package inc +package javac + +import xsbti.Reporter +import xsbti.Problem + +class CollectingReporter extends Reporter { + var problems: Array[Problem] = Array[Problem]() + + def reset() = problems = Array[Problem]() + def hasErrors() = ??? + def hasWarnings(): Boolean = ??? + def printSummary(): Unit = ??? + def log(problem: xsbti.Problem): Unit = problems :+= problem + def comment(pos: xsbti.Position, msg: String): Unit = ??? + +} diff --git a/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/JavacProcessLoggerSpec.scala b/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/JavacProcessLoggerSpec.scala new file mode 100644 index 0000000000..12ad295bff --- /dev/null +++ b/internal/zinc-compile-core/src/test/scala/sbt/internal/inc/javac/JavacProcessLoggerSpec.scala @@ -0,0 +1,75 @@ +package sbt +package internal +package inc +package javac + +import java.io.File + +import sbt.util.Level + +class JavaProcessLoggerSpec extends UnitSpec { + "The javac process logger" should "parse regular semantic errors" in logSemanticErrors() + it should "parse semantic errors passed in one by one" in logSeparateSemanticErrors() + it should "log errors that could not be parsed" in logUnparsableErrors() + + def logSemanticErrors(): Unit = { + val reporter = new CollectingReporter() + val errorLogger = new CollectingLogger() + val javacLogger = new JavacLogger(errorLogger, reporter, cwd = new File(".")) + javacLogger.err( + Seq( + """Test.java:4: cannot find symbol + |symbol : method baz() + |location: class Foo + |return baz(); + | ^ + |""", + """Test.java:8: warning: [deprecation] RMISecurityException(java.lang.String) in java.rmi.RMISecurityException has been deprecated + |throw new java.rmi.RMISecurityException("O NOES"); + |^ + |""" + ).mkString("\n")) + + javacLogger.flush("javac", 0) + + errorLogger.messages shouldBe Map.empty + reporter.problems.length shouldBe 2 + } + + def logSeparateSemanticErrors(): Unit = { + val reporter = new CollectingReporter() + val errorLogger = new CollectingLogger() + val javacLogger = new JavacLogger(errorLogger, reporter, cwd = new File(".")) + javacLogger.err("""Test.java:4: cannot find symbol + |symbol : method baz() + |location: class Foo + |return baz(); + | ^ + |""") + javacLogger.err( + """Test.java:8: warning: [deprecation] RMISecurityException(java.lang.String) in java.rmi.RMISecurityException has been deprecated + |throw new java.rmi.RMISecurityException("O NOES"); + |^ + |""") + + javacLogger.flush("javac", 0) + + errorLogger.messages shouldBe Map.empty + reporter.problems.length shouldBe 2 + } + + def logUnparsableErrors(): Unit = { + val reporter = new CollectingReporter() + val errorLogger = new CollectingLogger() + val javacLogger = new JavacLogger(errorLogger, reporter, cwd = new File(".")) + javacLogger.err("javadoc: error - invalid flag: -target") + + javacLogger.flush("javadoc", -1) + + errorLogger.messages(Level.Warn).length shouldBe 2 + errorLogger + .messages(Level.Warn)(0) + .contains("javadoc: error - invalid flag: -target") shouldBe true + errorLogger.messages(Level.Warn)(1).contains("javadoc exited with exit code -1") shouldBe true + } +}