From dd16d55e95e5cc21c102fe49b4cf0e727261fedf Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Fri, 5 Jan 2018 21:59:02 -0800 Subject: Forward bulk output to `stderr` when `--show` is called, reserving `stdout` for the shown JSON blob --- core/src/main/scala/mill/Main.scala | 2 +- core/src/main/scala/mill/eval/Evaluator.scala | 4 +- core/src/main/scala/mill/main/MainRunner.scala | 29 ++++++--- .../main/scala/mill/main/ReplApplyHandler.scala | 1 + core/src/main/scala/mill/main/RunScript.scala | 7 +-- core/src/main/scala/mill/modules/Jvm.scala | 15 +++-- core/src/main/scala/mill/util/Logger.scala | 69 ++++++++++++++++++---- 7 files changed, 92 insertions(+), 35 deletions(-) (limited to 'core/src/main') diff --git a/core/src/main/scala/mill/Main.scala b/core/src/main/scala/mill/Main.scala index 479366b2..684040a7 100644 --- a/core/src/main/scala/mill/Main.scala +++ b/core/src/main/scala/mill/Main.scala @@ -60,7 +60,7 @@ object Main { val runner = new mill.main.MainRunner( config, show, - System.out, System.err, System.in, System.out, System.err + System.out, System.err, System.in ) if (repl){ runner.printInfo("Loading...") diff --git a/core/src/main/scala/mill/eval/Evaluator.scala b/core/src/main/scala/mill/eval/Evaluator.scala index 9149e532..17108307 100644 --- a/core/src/main/scala/mill/eval/Evaluator.scala +++ b/core/src/main/scala/mill/eval/Evaluator.scala @@ -219,10 +219,10 @@ class Evaluator[T](val workspacePath: Path, val out = System.out val err = System.err try{ - System.setErr(multiLogger.outputStream) + System.setErr(multiLogger.errorStream) System.setOut(multiLogger.outputStream) Console.withOut(multiLogger.outputStream){ - Console.withErr(multiLogger.outputStream){ + Console.withErr(multiLogger.errorStream){ target.evaluate(args) } } diff --git a/core/src/main/scala/mill/main/MainRunner.scala b/core/src/main/scala/mill/main/MainRunner.scala index 4a6be436..76e2d069 100644 --- a/core/src/main/scala/mill/main/MainRunner.scala +++ b/core/src/main/scala/mill/main/MainRunner.scala @@ -7,6 +7,7 @@ import ammonite.ops.Path import ammonite.util._ import mill.discover.Discovered import mill.eval.{Evaluator, PathRef} +import mill.util.PrintLogger import upickle.Js /** @@ -14,13 +15,15 @@ import upickle.Js * `build.sc` scripts with mill-specific tweaks such as a custom * `scriptCodeWrapper` or with a persistent evaluator between runs. */ -class MainRunner(config: ammonite.main.Cli.Config, show: Boolean, +class MainRunner(config: ammonite.main.Cli.Config, + show: Boolean, outprintStream: PrintStream, errPrintStream: PrintStream, - stdIn: InputStream, - stdOut: OutputStream, - stdErr: OutputStream) - extends ammonite.MainRunner(config, outprintStream, errPrintStream, stdIn, stdOut, stdErr){ + stdIn: InputStream) + extends ammonite.MainRunner( + config, outprintStream, errPrintStream, + stdIn, outprintStream, errPrintStream + ){ var lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_])] = None override def runScript(scriptPath: Path, scriptArgs: List[String]) = @@ -29,8 +32,18 @@ class MainRunner(config: ammonite.main.Cli.Config, show: Boolean, printing = true, mainCfg => { val (result, interpWatched) = RunScript.runScript( - mainCfg.wd, scriptPath, mainCfg.instantiateInterpreter(), scriptArgs, lastEvaluator, - errPrintStream, errPrintStream, colors + mainCfg.wd, + scriptPath, + mainCfg.instantiateInterpreter(), + scriptArgs, + lastEvaluator, + new PrintLogger( + colors != ammonite.util.Colors.BlackWhite, + colors, + if (show) errPrintStream else outprintStream, + errPrintStream, + errPrintStream + ) ) result match{ @@ -50,7 +63,7 @@ class MainRunner(config: ammonite.main.Cli.Config, show: Boolean, case Res.Success(value) => if (show){ for(json <- value.asInstanceOf[Seq[Js.Value]]){ - System.out.println(json) + outprintStream.println(json) } } diff --git a/core/src/main/scala/mill/main/ReplApplyHandler.scala b/core/src/main/scala/mill/main/ReplApplyHandler.scala index e68dfd93..1e88ee53 100644 --- a/core/src/main/scala/mill/main/ReplApplyHandler.scala +++ b/core/src/main/scala/mill/main/ReplApplyHandler.scala @@ -22,6 +22,7 @@ object ReplApplyHandler{ new mill.util.PrintLogger( colors != ammonite.util.Colors.BlackWhite, colors, + System.out, System.err, System.err ) diff --git a/core/src/main/scala/mill/main/RunScript.scala b/core/src/main/scala/mill/main/RunScript.scala index bd27039c..7e578fb0 100644 --- a/core/src/main/scala/mill/main/RunScript.scala +++ b/core/src/main/scala/mill/main/RunScript.scala @@ -12,7 +12,7 @@ import mill.define.Task import mill.discover.Mirror.Segment import mill.discover.{Discovered, Mirror} import mill.eval.{Evaluator, PathRef, Result} -import mill.util.{OSet, PrintLogger} +import mill.util.{Logger, OSet, PrintLogger} import upickle.Js /** @@ -26,12 +26,9 @@ object RunScript{ instantiateInterpreter: => Either[(Res.Failing, Seq[(Path, Long)]), ammonite.interp.Interpreter], scriptArgs: Seq[String], lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_])], - infoStream: PrintStream, - errStream: PrintStream, - colors: ammonite.util.Colors) + log: Logger) : (Res[(Evaluator[_], Seq[(Path, Long)], Either[String, Seq[Js.Value]])], Seq[(Path, Long)]) = { - val log = new PrintLogger(colors != ammonite.util.Colors.BlackWhite, colors, infoStream, errStream) val (evalRes, interpWatched) = lastEvaluator match{ case Some((prevInterpWatchedSig, prevEvaluator)) if watchedSigUnchanged(prevInterpWatchedSig) => diff --git a/core/src/main/scala/mill/modules/Jvm.scala b/core/src/main/scala/mill/modules/Jvm.scala index 1bff3c9b..e6806923 100644 --- a/core/src/main/scala/mill/modules/Jvm.scala +++ b/core/src/main/scala/mill/modules/Jvm.scala @@ -60,7 +60,10 @@ object Jvm { val stdout = proc.getInputStream val stderr = proc.getErrorStream - val sources = Seq(stdout -> (Left(_: Bytes)), stderr -> (Right(_: Bytes))) + val sources = Seq( + (stdout, Left(_: Bytes), ctx.log.outputStream), + (stderr, Right(_: Bytes),ctx.log.errorStream ) + ) val chunks = mutable.Buffer.empty[Either[Bytes, Bytes]] while( // Process.isAlive doesn't exist on JDK 7 =/ @@ -69,13 +72,13 @@ object Jvm { stderr.available() > 0 ){ var readSomething = false - for ((std, wrapper) <- sources){ - while (std.available() > 0){ + for ((subStream, wrapper, parentStream) <- sources){ + while (subStream.available() > 0){ readSomething = true - val array = new Array[Byte](std.available()) - val actuallyRead = std.read(array) + val array = new Array[Byte](subStream.available()) + val actuallyRead = subStream.read(array) chunks.append(wrapper(new ammonite.ops.Bytes(array))) - ctx.log.outputStream.write(array, 0, actuallyRead) + parentStream.write(array, 0, actuallyRead) } } // if we did not read anything sleep briefly to avoid spinning diff --git a/core/src/main/scala/mill/util/Logger.scala b/core/src/main/scala/mill/util/Logger.scala index 5a843002..316e17ff 100644 --- a/core/src/main/scala/mill/util/Logger.scala +++ b/core/src/main/scala/mill/util/Logger.scala @@ -6,25 +6,38 @@ import ammonite.ops.Path import ammonite.util.Colors +/** + * The standard logging interface of the Mill build tool. + * + * Contains four primary logging methods, in order of increasing importance: + * + * - `ticker`: short-lived logging output where consecutive lines over-write + * each other; useful for information which is transient and disposable + * + * - `info`: miscellaneous logging output which isn't part of the main output + * a user is looking for, but useful to provide context on what Mill is doing + * + * - `error`: logging output which represents problems the user should care + * about + * + * Also contains the two forwarded stdout and stderr streams, for code executed + * by Mill to use directly. Typically these correspond to the stdout and stderr, + * but when `--show` is used both are forwarded to stderr and stdout is only + * used to display the final `--show` output for easy piping. + */ trait Logger { def colored: Boolean + val errorStream: PrintStream val outputStream: PrintStream def info(s: String): Unit def error(s: String): Unit - - /** - * Like [[info]], but if two calls to [[ticker]] are made consecutively - * without any calls to [[info]]/[[error]][[outputStream]] in between, - * the second call to [[ticker]] over-writes the first one in the console. - * This is useful for displaying loading bars, progress updates or all other - * sorts of fast-changing information to the user - */ def ticker(s: String): Unit def close(): Unit = () } object DummyLogger extends Logger { def colored = false + object errorStream extends PrintStream(_ => ()) object outputStream extends PrintStream(_ => ()) def info(s: String) = () def error(s: String) = () @@ -33,36 +46,56 @@ object DummyLogger extends Logger { case class PrintLogger(colored: Boolean, colors: ammonite.util.Colors, + outStream: PrintStream, infoStream: PrintStream, - errorStream: PrintStream) extends Logger { + errStream: PrintStream) extends Logger { var lastLineTicker = false + override val errorStream = new PrintStream( + new OutputStream { + override def write(b: Array[Byte]): Unit = { + lastLineTicker = false + errStream.write(b) + } + + override def write(b: Array[Byte], off: Int, len: Int): Unit = { + lastLineTicker = false + errStream.write(b, off, len) + } + + def write(b: Int) = { + lastLineTicker = false + errStream.write(b) + } + } + ) override val outputStream = new PrintStream( new OutputStream { override def write(b: Array[Byte]): Unit = { lastLineTicker = false - infoStream.write(b) + outStream.write(b) } override def write(b: Array[Byte], off: Int, len: Int): Unit = { lastLineTicker = false - infoStream.write(b, off, len) + outStream.write(b, off, len) } def write(b: Int) = { lastLineTicker = false - infoStream.write(b) + outStream.write(b) } } ) + def info(s: String) = { lastLineTicker = false infoStream.println(colors.info()(s)) } def error(s: String) = { lastLineTicker = false - errorStream.println(colors.error()(s)) + errStream.println(colors.error()(s)) } def ticker(s: String) = { if (lastLineTicker){ @@ -86,6 +119,11 @@ case class FileLogger(colored: Boolean, file: Path) extends Logger { new PrintStream(new FileOutputStream(file.toIO.getAbsolutePath)) } + lazy val errorStream = { + outputStreamUsed = true + new PrintStream(new FileOutputStream(file.toIO.getAbsolutePath)) + } + def info(s: String) = outputStream.println(s) def error(s: String) = outputStream.println(s) def ticker(s: String) = outputStream.println(s) @@ -101,6 +139,11 @@ case class MultiLogger(colored: Boolean, streams: Logger*) extends Logger { override def flush() = streams.foreach(_.outputStream.flush()) override def close() = streams.foreach(_.outputStream.close()) } + lazy val errorStream: PrintStream = + new PrintStream(b => streams.foreach(_.outputStream.write(b))) { + override def flush() = streams.foreach(_.outputStream.flush()) + override def close() = streams.foreach(_.outputStream.close()) + } def info(s: String) = streams.foreach(_.info(s)) def error(s: String) = streams.foreach(_.error(s)) -- cgit v1.2.3