Use terminal printstream in CheckBuildSources

The build source check is evaluated at times when we can't be completely
sure that global logger is pointing at the terminal that initiated the
reload (which may be a passive watch client). To work around this, we
can inspect the exec to determine which terminal initiated the check and
write any output directly to that terminal.
This commit is contained in:
Ethan Atkins 2020-07-09 16:09:48 -07:00
parent 25e83d8fec
commit 9dc3c6b17f
3 changed files with 56 additions and 21 deletions

View File

@ -184,7 +184,8 @@ object MainLoop {
/** This is the main function State transfer function of the sbt command processing. */ /** This is the main function State transfer function of the sbt command processing. */
def processCommand(exec: Exec, state: State): State = { def processCommand(exec: Exec, state: State): State = {
val channelName = exec.source map (_.channelName) val channelName = exec.source map (_.channelName)
StandardMain.exchange notifyStatus val exchange = StandardMain.exchange
exchange notifyStatus
ExecStatusEvent("Processing", channelName, exec.execId, Vector()) ExecStatusEvent("Processing", channelName, exec.execId, Vector())
try { try {
def process(): State = { def process(): State = {
@ -197,9 +198,9 @@ object MainLoop {
state.put(sbt.Keys.currentTaskProgress, new Keys.TaskProgress(progress)) state.put(sbt.Keys.currentTaskProgress, new Keys.TaskProgress(progress))
} else state } else state
} }
StandardMain.exchange.setState(progressState) exchange.setState(progressState)
StandardMain.exchange.setExec(Some(exec)) exchange.setExec(Some(exec))
StandardMain.exchange.unprompt(ConsoleUnpromptEvent(exec.source), force = false) exchange.unprompt(ConsoleUnpromptEvent(exec.source), force = false)
val newState = Command.process(exec.commandLine, progressState) val newState = Command.process(exec.commandLine, progressState)
if (exec.execId.fold(true)(!_.startsWith(networkExecPrefix)) && if (exec.execId.fold(true)(!_.startsWith(networkExecPrefix)) &&
!exec.commandLine.startsWith(networkExecPrefix)) { !exec.commandLine.startsWith(networkExecPrefix)) {
@ -210,26 +211,25 @@ object MainLoop {
newState.remainingCommands.toVector map (_.commandLine), newState.remainingCommands.toVector map (_.commandLine),
exitCode(newState, state), exitCode(newState, state),
) )
StandardMain.exchange.respondStatus(doneEvent) exchange.respondStatus(doneEvent)
} }
StandardMain.exchange.setExec(None) exchange.setExec(None)
newState.get(sbt.Keys.currentTaskProgress).foreach(_.progress.stop()) newState.get(sbt.Keys.currentTaskProgress).foreach(_.progress.stop())
newState.remove(sbt.Keys.currentTaskProgress) newState.remove(sbt.Keys.currentTaskProgress)
} }
state.get(CheckBuildSourcesKey) match { state.get(CheckBuildSourcesKey) match {
case Some(cbs) => case Some(cbs) =>
if (!cbs.needsReload(state, state.globalLogging.full, exec.commandLine)) process() if (!cbs.needsReload(state, exec)) process()
else { else {
if (exec.commandLine.startsWith(SetTerminal)) val isSetTerminal = exec.commandLine.startsWith(SetTerminal)
exec +: Exec("reload", None, None) +: state.remove(CheckBuildSourcesKey) if (isSetTerminal) exec +: Exec("reload", None) +: state.remove(CheckBuildSourcesKey)
else else Exec("reload", None) +: exec +: state.remove(CheckBuildSourcesKey)
Exec("reload", None, None) +: exec +: state.remove(CheckBuildSourcesKey)
} }
case _ => process() case _ => process()
} }
} catch { } catch {
case err: JsonRpcResponseError => case err: JsonRpcResponseError =>
StandardMain.exchange.respondError(err, exec.execId, channelName.map(CommandSource(_))) exchange.respondError(err, exec.execId, channelName.map(CommandSource(_)))
throw err throw err
case err: Throwable => case err: Throwable =>
val errorEvent = ExecStatusEvent( val errorEvent = ExecStatusEvent(

View File

@ -320,7 +320,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
val (nextFileEvent, cleanupFileMonitor): ( val (nextFileEvent, cleanupFileMonitor): (
Int => Option[(Watch.Event, Watch.Action)], Int => Option[(Watch.Event, Watch.Action)],
() => Unit () => Unit
) = getFileEvents(configs, logger, state, commands, fileStampCache) ) = getFileEvents(configs, logger, state, commands, fileStampCache, channel.name)
val executor = new WatchExecutor(channel.name) val executor = new WatchExecutor(channel.name)
val nextEvent: Int => Watch.Action = val nextEvent: Int => Watch.Action =
combineInputAndFileEvents(nextInputEvent, nextFileEvent, message, logger, logger, executor) combineInputAndFileEvents(nextInputEvent, nextFileEvent, message, logger, logger, executor)
@ -420,7 +420,8 @@ private[sbt] object Continuous extends DeprecatedContinuous {
logger: Logger, logger: Logger,
state: State, state: State,
commands: Seq[String], commands: Seq[String],
fileStampCache: FileStamp.Cache fileStampCache: FileStamp.Cache,
channel: String,
)(implicit extracted: Extracted): (Int => Option[(Watch.Event, Watch.Action)], () => Unit) = { )(implicit extracted: Extracted): (Int => Option[(Watch.Event, Watch.Action)], () => Unit) = {
val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild) val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild)
val buildGlobs = val buildGlobs =
@ -554,7 +555,9 @@ private[sbt] object Continuous extends DeprecatedContinuous {
getWatchEvent(forceTrigger = false).flatMap { e => getWatchEvent(forceTrigger = false).flatMap { e =>
state.get(CheckBuildSources.CheckBuildSourcesKey) match { state.get(CheckBuildSources.CheckBuildSourcesKey) match {
case Some(cbs) => case Some(cbs) =>
if (cbs.needsReload(state, logger, "")) Some(e -> Watch.Reload) else None if (cbs.needsReload(state, Exec("", Some(CommandSource(channel)))))
Some(e -> Watch.Reload)
else None
case None => case None =>
Some(e -> Watch.Reload) Some(e -> Watch.Reload)
} }

View File

@ -10,13 +10,13 @@ package internal.nio
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import sbt.BasicCommandStrings.{ RebootCommand, Shutdown, TerminateAction } import sbt.BasicCommandStrings.{ RebootCommand, SetTerminal, Shutdown, TerminateAction }
import sbt.Keys.{ baseDirectory, pollInterval, state } import sbt.Keys.{ baseDirectory, pollInterval, state }
import sbt.Scope.Global import sbt.Scope.Global
import sbt.SlashSyntax0._ import sbt.SlashSyntax0._
import sbt.internal.CommandStrings.LoadProject import sbt.internal.CommandStrings.LoadProject
import sbt.internal.SysProp import sbt.internal.SysProp
import sbt.internal.util.AttributeKey import sbt.internal.util.{ AttributeKey, Terminal }
import sbt.io.syntax._ import sbt.io.syntax._
import sbt.nio.FileChanges import sbt.nio.FileChanges
import sbt.nio.FileStamp import sbt.nio.FileStamp
@ -28,6 +28,7 @@ import sbt.util.Logger
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration.{ Deadline => SDeadline, _ } import scala.concurrent.duration.{ Deadline => SDeadline, _ }
import scala.io.AnsiColor
/** /**
* This class is used to determine whether sbt needs to automatically reload * This class is used to determine whether sbt needs to automatically reload
@ -103,7 +104,28 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
!resetState !resetState
} }
@inline private def forceCheck = fileTreeRepository.isEmpty @inline private def forceCheck = fileTreeRepository.isEmpty
private[sbt] def needsReload(state: State, logger: Logger, cmd: String) = { private[sbt] def needsReload(
state: State,
exec: Exec
): Boolean = {
val isSetTerminal = exec.commandLine.startsWith(SetTerminal)
val name =
if (isSetTerminal)
exec.commandLine.split(s"$SetTerminal ").lastOption.filterNot(_.isEmpty)
else exec.source.map(_.channelName)
val loggerOrTerminal =
name.flatMap(StandardMain.exchange.channelForName(_).map(_.terminal)) match {
case Some(t) => Right(t)
case _ => Left(state.globalLogging.full)
}
needsReload(state, loggerOrTerminal, exec.commandLine)
}
private def needsReload(
state: State,
loggerOrTerminal: Either[Logger, Terminal],
cmd: String
): Boolean = {
(needCheck(state, cmd) && (forceCheck || needUpdate.compareAndSet(true, false))) && { (needCheck(state, cmd) && (forceCheck || needUpdate.compareAndSet(true, false))) && {
val extracted = Project.extract(state) val extracted = Project.extract(state)
val onChanges = extracted.get(Global / onChangedBuildSource) val onChanges = extracted.get(Global / onChangedBuildSource)
@ -122,14 +144,24 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
else "") else "")
val prefix = rawPrefix.linesIterator.filterNot(_.trim.isEmpty).mkString("\n") val prefix = rawPrefix.linesIterator.filterNot(_.trim.isEmpty).mkString("\n")
if (onChanges == ReloadOnSourceChanges) { if (onChanges == ReloadOnSourceChanges) {
logger.info(s"$prefix\nReloading sbt...") val msg = s"$prefix\nReloading sbt..."
loggerOrTerminal match {
case Right(t) => msg.linesIterator.foreach(l => t.printStream.println(s"[info] $l"))
case Left(l) => l.info(msg)
}
true true
} else { } else {
val tail = "Apply these changes by running `reload`.\nAutomatically reload the " + val tail = "Apply these changes by running `reload`.\nAutomatically reload the " +
"build when source changes are detected by setting " + "build when source changes are detected by setting " +
"`Global / onChangedBuildSource := ReloadOnSourceChanges`.\nDisable this " + "`Global / onChangedBuildSource := ReloadOnSourceChanges`.\nDisable this " +
"warning by setting `Global / onChangedBuildSource := IgnoreSourceChanges`." "warning by setting `Global / onChangedBuildSource := IgnoreSourceChanges`."
logger.warn(s"$prefix\n$tail") val msg = s"$prefix\n$tail"
loggerOrTerminal match {
case Right(t) =>
val prefix = s"[${Def.withColor("warn", Some(AnsiColor.YELLOW), t.isColorEnabled)}]"
msg.linesIterator.foreach(l => t.printStream.println(s"$prefix $l"))
case Left(l) => l.warn(msg)
}
false false
} }
case _ => false case _ => false
@ -160,7 +192,7 @@ private[sbt] object CheckBuildSources {
private[sbt] def needReloadImpl: Def.Initialize[Task[StateTransform]] = Def.task { private[sbt] def needReloadImpl: Def.Initialize[Task[StateTransform]] = Def.task {
val st = state.value val st = state.value
st.get(CheckBuildSourcesKey) match { st.get(CheckBuildSourcesKey) match {
case Some(cbs) if (cbs.needsReload(st, st.globalLogging.full, "")) => case Some(cbs) if (cbs.needsReload(st, Exec("", None))) =>
StateTransform("reload" :: (_: State)) StateTransform("reload" :: (_: State))
case _ => StateTransform(identity) case _ => StateTransform(identity)
} }