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

View File

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

View File

@ -10,13 +10,13 @@ package internal.nio
import java.nio.file.Path
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.Scope.Global
import sbt.SlashSyntax0._
import sbt.internal.CommandStrings.LoadProject
import sbt.internal.SysProp
import sbt.internal.util.AttributeKey
import sbt.internal.util.{ AttributeKey, Terminal }
import sbt.io.syntax._
import sbt.nio.FileChanges
import sbt.nio.FileStamp
@ -28,6 +28,7 @@ import sbt.util.Logger
import scala.annotation.tailrec
import scala.concurrent.duration.{ Deadline => SDeadline, _ }
import scala.io.AnsiColor
/**
* This class is used to determine whether sbt needs to automatically reload
@ -103,7 +104,28 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
!resetState
}
@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))) && {
val extracted = Project.extract(state)
val onChanges = extracted.get(Global / onChangedBuildSource)
@ -122,14 +144,24 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
else "")
val prefix = rawPrefix.linesIterator.filterNot(_.trim.isEmpty).mkString("\n")
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
} else {
val tail = "Apply these changes by running `reload`.\nAutomatically reload the " +
"build when source changes are detected by setting " +
"`Global / onChangedBuildSource := ReloadOnSourceChanges`.\nDisable this " +
"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
}
case _ => false
@ -160,7 +192,7 @@ private[sbt] object CheckBuildSources {
private[sbt] def needReloadImpl: Def.Initialize[Task[StateTransform]] = Def.task {
val st = state.value
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))
case _ => StateTransform(identity)
}