mirror of https://github.com/sbt/sbt.git
[2.x] Fix console output not appearing with bgRun and run / fork := true (#9166)
Bug 1: bgRun forks with inheritIO() instead of LoggedOutput bgRunTask / bgRunMainTask resolve their fork options via (run / forkOptions), which inherits run / connectInput := true. That has two downstream consequences: 1. ForkRun.configLogged (in run/src/main/scala/sbt/Run.scala) skips installing OutputStrategy.LoggedOutput(log) whenever config.connectInput == true. 2. Fork.apply (in run/src/main/scala/sbt/Fork.scala) sees connectInput && outputStrategy == StdoutOutput and takes the interactiveFork path, which calls jpb.inheritIO(). Bug 2: Background-job log relay drops messages after the spawning task ends Even after Bug 1 is fixed, LoggedOutput routes the forked process's stdout into the background ManagedLogger, whose appenders include a shared RelayAppender. The relay calls CommandExchange.logMessage(event), which uses isChannelOwner(c) to pick the target channel.
This commit is contained in:
parent
0af7c2f4a6
commit
5757955fc7
|
|
@ -424,6 +424,15 @@ private[sbt] final class CommandExchange {
|
|||
tryTo(_.notifyEvent(event))(c)
|
||||
case _ =>
|
||||
|
||||
// Route a log event to a specific channel, independent of currentExec.
|
||||
// Used for background job output so messages reach the originating client
|
||||
// even after the spawning task has completed and currentExec has been cleared.
|
||||
private[sbt] def logMessage(channelName: String, event: LogEvent): Unit =
|
||||
channels.foreach:
|
||||
case c: NetworkChannel if c.subscribeToAll || c.name == channelName =>
|
||||
tryTo(_.notifyEvent(event))(c)
|
||||
case _ =>
|
||||
|
||||
private def isChannelOwner(c: NetworkChannel): Boolean =
|
||||
currentExec.exists(_.source.exists(_.channelName == c.name))
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,14 @@ object LogManager {
|
|||
context: LoggerContext
|
||||
): ManagedLogger = {
|
||||
val console = ConsoleAppender.safe("bg-" + ConsoleAppender.generateName(), ITerminal.current)
|
||||
LogManager.backgroundLog(data, state, task, console, relay(()), context)
|
||||
// Use a channel-aware relay appender so background job log output reaches
|
||||
// the originating client even after the spawning task completes and
|
||||
// currentExec is cleared.
|
||||
val channelName = state.currentCommand.flatMap(_.source.map(_.channelName))
|
||||
val bgRelay = channelName match
|
||||
case Some(_) => new RelayAppender("bg-Relay" + generateId.incrementAndGet, channelName)
|
||||
case None => relay(())
|
||||
LogManager.backgroundLog(data, state, task, console, bgRelay, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,19 @@ import sbt.internal.util.*
|
|||
import sbt.protocol.LogEvent
|
||||
import sbt.util.Level
|
||||
|
||||
class RelayAppender(override val name: String)
|
||||
class RelayAppender(override val name: String, targetChannel: Option[String] = None)
|
||||
extends ConsoleAppender(
|
||||
name,
|
||||
ConsoleAppender.Properties.from(ConsoleOut.NullConsoleOut, true, true),
|
||||
_ => None
|
||||
) {
|
||||
def this(name: String) = this(name, None)
|
||||
|
||||
lazy val exchange = StandardMain.exchange
|
||||
override def appendLog(level: Level.Value, message: => String): Unit = {
|
||||
exchange.logMessage(LogEvent(level = level.toString, message = message))
|
||||
val event = LogEvent(level = level.toString, message = message)
|
||||
targetChannel match
|
||||
case Some(ch) => exchange.logMessage(ch, event)
|
||||
case None => exchange.logMessage(event)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,9 +293,16 @@ object RunUtil:
|
|||
val (mainClass, allArgs) = parser.parsed
|
||||
val (jvmArgs, appArgs) = splitArgs(allArgs)
|
||||
val hashClasspath = (bgRunMain / bgHashClasspath).value
|
||||
val fo = (run / forkOptions).value
|
||||
// Background runs must not inherit the terminal's stdin/stdout via connectInput.
|
||||
// Without this, ForkRun.configLogged skips setting LoggedOutput, causing
|
||||
// the fork to use inheritIO() which bypasses the background logger entirely.
|
||||
val fo = (run / forkOptions).value.withConnectInput(false)
|
||||
val log = streams.value.log
|
||||
val (modifiedRun, _) = applyJvmArgs(scalaRun.value, jvmArgs, fo, log)
|
||||
// applyJvmArgs only builds a new ForkRun when jvmArgs is non-empty, so force
|
||||
// construction of a ForkRun that uses the updated ForkOptions above.
|
||||
val (modifiedRun, _) = applyJvmArgs(scalaRun.value, jvmArgs, fo, log) match
|
||||
case (_: ForkRun, _) => (new ForkRun(fo.withRunJVMOptions(fo.runJVMOptions ++ jvmArgs)), fo)
|
||||
case other => other
|
||||
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
||||
val converter = fileConverter.value
|
||||
setWindowTitle(mkWindowTitle("bgRunMain", organization.value, name.value, version.value))
|
||||
|
|
@ -337,9 +344,16 @@ object RunUtil:
|
|||
val service = bgJobService.value
|
||||
val mainClass = getMainClass(mainClassTask.value)
|
||||
val hashClasspath = (bgRun / bgHashClasspath).value
|
||||
val fo = (run / forkOptions).value
|
||||
// Background runs must not inherit the terminal's stdin/stdout via connectInput.
|
||||
// Without this, ForkRun.configLogged skips setting LoggedOutput, causing
|
||||
// the fork to use inheritIO() which bypasses the background logger entirely.
|
||||
val fo = (run / forkOptions).value.withConnectInput(false)
|
||||
val log = streams.value.log
|
||||
val (modifiedRun, _) = applyJvmArgs(scalaRun.value, jvmArgs, fo, log)
|
||||
// applyJvmArgs only builds a new ForkRun when jvmArgs is non-empty, so force
|
||||
// construction of a ForkRun that uses the updated ForkOptions above.
|
||||
val (modifiedRun, _) = applyJvmArgs(scalaRun.value, jvmArgs, fo, log) match
|
||||
case (_: ForkRun, _) => (new ForkRun(fo.withRunJVMOptions(fo.runJVMOptions ++ jvmArgs)), fo)
|
||||
case other => other
|
||||
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
|
||||
val converter = fileConverter.value
|
||||
setWindowTitle(mkWindowTitle("bgRun", organization.value, name.value, version.value))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import sbt.internal.LogManager
|
||||
import sbt.internal.util.{ Appender, ConsoleAppender, ConsoleOut }
|
||||
import java.io.{ FileWriter, PrintWriter }
|
||||
|
||||
lazy val checkBgOutput = taskKey[Unit]("Verify the bgRun forked process output was logged")
|
||||
lazy val waitForAllBgJobs = taskKey[Unit]("Wait for every bgJobService job to terminate")
|
||||
lazy val outFile = settingKey[File]("File where bgRun output is captured for the test")
|
||||
|
||||
lazy val root = project
|
||||
.in(file("."))
|
||||
.settings(
|
||||
run / fork := true,
|
||||
outFile := baseDirectory.value / "target" / "bg-output.log",
|
||||
// Override logManager so the background logger's relay appender writes
|
||||
// to a file. This lets the test assert that forked-process output
|
||||
// reached the managed logger (rather than going to the JVM's stdout via
|
||||
// inheritIO, which is what happens when the bgRun fork-output bug is
|
||||
// present). In a scripted test there are no network channels, so the
|
||||
// default relay appender has no observable effect anyway.
|
||||
logManager := {
|
||||
val ea = extraAppenders.value
|
||||
val f = outFile.value
|
||||
IO.touch(f)
|
||||
val fileRelay: Unit => Appender = _ => {
|
||||
val pw = new PrintWriter(new FileWriter(f, true), true)
|
||||
ConsoleAppender("bg-file-test", ConsoleOut.printWriterOut(pw))
|
||||
}
|
||||
LogManager.withLoggers(
|
||||
screen = (task, state) => ConsoleAppender(s"screen-${task.key.label}"),
|
||||
relay = fileRelay,
|
||||
extra = ea
|
||||
)
|
||||
},
|
||||
waitForAllBgJobs := Def.uncached {
|
||||
val service = bgJobService.value
|
||||
service.jobs.foreach(service.waitFor)
|
||||
},
|
||||
checkBgOutput := Def.uncached {
|
||||
val f = outFile.value
|
||||
val content = IO.read(f)
|
||||
assert(content.contains("foobar"), s"Expected 'foobar' in $f, got:\n$content")
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
@main def test: Unit = println("foobar")
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
> bgRun
|
||||
> waitForAllBgJobs
|
||||
> checkBgOutput
|
||||
Loading…
Reference in New Issue