Add shutdown command

Shutdown was being handled as a special case in CommandExchange. This
promotes it to a full fledged command. Also replace instance of
hard-coded strings with constants.
This commit is contained in:
Ethan Atkins 2020-06-27 11:27:57 -07:00
parent 261084bbb2
commit 77b1e38e41
11 changed files with 45 additions and 33 deletions

View File

@ -14,8 +14,10 @@ object BasicCommandStrings {
val HelpCommand: String = "help" val HelpCommand: String = "help"
val CompletionsCommand: String = "completions" val CompletionsCommand: String = "completions"
val Exit: String = "exit" val Exit: String = "exit"
val Shutdown: String = "shutdown"
val Quit: String = "quit" val Quit: String = "quit"
val TemplateCommand: String = "new" val TemplateCommand: String = "new"
val Cancel: String = "cancel"
/** The command name to terminate the program.*/ /** The command name to terminate the program.*/
val TerminateAction: String = Exit val TerminateAction: String = Exit
@ -57,7 +59,8 @@ $HelpCommand <regular expression>
def historyHelp = def historyHelp =
Help(Nil, (HistoryHelpBrief +: HistoryCommands.descriptions).toMap, Set(HistoryCommands.Start)) Help(Nil, (HistoryHelpBrief +: HistoryCommands.descriptions).toMap, Set(HistoryCommands.Start))
def exitBrief: String = "Terminates the build." def exitBrief: String = "Terminates the remote client or the build when called from the console."
def shutdownBrief: String = "Terminates the build."
def logLevelHelp: Help = { def logLevelHelp: Help = {
val levels = Level.values.toSeq val levels = Level.values.toSeq

View File

@ -56,6 +56,7 @@ object BasicCommands {
call, call,
early, early,
exit, exit,
shutdown,
history, history,
oldshell, oldshell,
client, client,
@ -356,6 +357,13 @@ object BasicCommands {
case _ => s exit true case _ => s exit true
} }
} }
def shutdown: Command = Command.command(Shutdown, shutdownBrief, shutdownBrief) { s =>
s.source match {
case Some(c) if c.channelName.startsWith("network") =>
s"${DisconnectNetworkChannel} ${c.channelName}" :: (Exec(Shutdown, None) +: s)
case _ => s exit true
}
}
@deprecated("Replaced by BuiltInCommands.continuous", "1.3.0") @deprecated("Replaced by BuiltInCommands.continuous", "1.3.0")
def continuous: Command = def continuous: Command =
@ -412,7 +420,7 @@ object BasicCommands {
case xs => xs map (_.commandLine) case xs => xs map (_.commandLine)
}) })
NetworkClient.run(s0.configuration, arguments) NetworkClient.run(s0.configuration, arguments)
"exit" :: s0.copy(remainingCommands = Nil) TerminateAction :: s0.copy(remainingCommands = Nil)
} }
def read: Command = def read: Command =

View File

@ -18,6 +18,7 @@ import java.util.UUID
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue, TimeUnit } import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue, TimeUnit }
import sbt.BasicCommandStrings.{ Shutdown, TerminateAction }
import sbt.internal.client.NetworkClient.Arguments import sbt.internal.client.NetworkClient.Arguments
import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams }
import sbt.internal.protocol._ import sbt.internal.protocol._
@ -155,13 +156,13 @@ class NetworkClient(
val conn = new ServerConnection(sk) { val conn = new ServerConnection(sk) {
override def onNotification(msg: JsonRpcNotificationMessage): Unit = { override def onNotification(msg: JsonRpcNotificationMessage): Unit = {
msg.method match { msg.method match {
case "shutdown" => case `Shutdown` =>
val log = msg.params match { val log = msg.params match {
case Some(jvalue) => Converter.fromJson[Boolean](jvalue).getOrElse(true) case Some(jvalue) => Converter.fromJson[Boolean](jvalue).getOrElse(true)
case _ => false case _ => false
} }
if (running.compareAndSet(true, false) && log) { if (running.compareAndSet(true, false) && log) {
if (!arguments.commandArguments.contains("shutdown")) { if (!arguments.commandArguments.contains(Shutdown)) {
if (Terminal.console.getLastLine.fold(true)(_.nonEmpty)) errorStream.println() if (Terminal.console.getLastLine.fold(true)(_.nonEmpty)) errorStream.println()
console.appendLog(Level.Error, "sbt server disconnected") console.appendLog(Level.Error, "sbt server disconnected")
exitClean.set(false) exitClean.set(false)
@ -451,7 +452,7 @@ class NetworkClient(
case Success(params) => splitDiagnostics(params); Vector() case Success(params) => splitDiagnostics(params); Vector()
case Failure(_) => Vector() case Failure(_) => Vector()
} }
case ("shutdown", Some(_)) => Vector.empty case (`Shutdown`, Some(_)) => Vector.empty
case (msg, _) if msg.startsWith("build/") => Vector.empty case (msg, _) if msg.startsWith("build/") => Vector.empty
case _ => case _ =>
Vector( Vector(
@ -557,7 +558,7 @@ class NetworkClient(
withSignalHandler(contHandler, Signals.CONT) { withSignalHandler(contHandler, Signals.CONT) {
interactiveThread.set(Thread.currentThread) interactiveThread.set(Thread.currentThread)
val cleaned = arguments.commandArguments val cleaned = arguments.commandArguments
val userCommands = cleaned.takeWhile(_ != "exit") val userCommands = cleaned.takeWhile(_ != TerminateAction)
val interactive = cleaned.isEmpty val interactive = cleaned.isEmpty
val exit = cleaned.nonEmpty && userCommands.isEmpty val exit = cleaned.nonEmpty && userCommands.isEmpty
attachUUID.set(sendJson(attach, s"""{"interactive": $interactive}""")) attachUUID.set(sendJson(attach, s"""{"interactive": $interactive}"""))
@ -668,8 +669,8 @@ class NetworkClient(
case _ => queue.take case _ => queue.take
} }
} catch { } catch {
case _: InterruptedException if cmd == "shutdown" => result = 0 case _: InterruptedException if cmd == Shutdown => result = 0
case _: InterruptedException => result = if (exitClean.get) 0 else 1 case _: InterruptedException => result = if (exitClean.get) 0 else 1
} }
} }
if (result == null) 1 else result if (result == null) 1 else result

View File

@ -12,6 +12,7 @@ import java.nio.channels.ClosedChannelException
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import jline.console.history.PersistentHistory import jline.console.history.PersistentHistory
import sbt.BasicCommandStrings.{ Cancel, TerminateAction, Shutdown }
import sbt.BasicKeys.{ historyPath, terminalShellPrompt } import sbt.BasicKeys.{ historyPath, terminalShellPrompt }
import sbt.State import sbt.State
import sbt.internal.CommandChannel import sbt.internal.CommandChannel
@ -54,7 +55,7 @@ private[sbt] object UITask {
try { try {
@tailrec def impl(): Either[String, String] = { @tailrec def impl(): Either[String, String] = {
lineReader.readLine(clear + terminal.prompt.mkPrompt()) match { lineReader.readLine(clear + terminal.prompt.mkPrompt()) match {
case null => Left("exit") case null => Left(TerminateAction)
case s: String => case s: String =>
lineReader.getHistory match { lineReader.getHistory match {
case p: PersistentHistory => case p: PersistentHistory =>
@ -63,8 +64,8 @@ private[sbt] object UITask {
case _ => case _ =>
} }
s match { s match {
case "" => impl() case "" => impl()
case cmd @ ("shutdown" | "exit" | "cancel") => Left(cmd) case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd)
case cmd => case cmd =>
if (terminal.prompt != Prompt.Batch) terminal.setPrompt(Prompt.Running) if (terminal.prompt != Prompt.Batch) terminal.setPrompt(Prompt.Running)
terminal.printStream.write(Int.MinValue) terminal.printStream.write(Int.MinValue)

View File

@ -15,7 +15,7 @@ import java.util.Properties
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import sbt.BasicCommandStrings.{ SetTerminal, Shell, TemplateCommand, networkExecPrefix } import sbt.BasicCommandStrings.{ SetTerminal, Shell, Shutdown, TemplateCommand, networkExecPrefix }
import sbt.Project.LoadAction import sbt.Project.LoadAction
import sbt.compiler.EvalImports import sbt.compiler.EvalImports
import sbt.internal.Aggregation.AnyKeys import sbt.internal.Aggregation.AnyKeys
@ -1025,7 +1025,7 @@ object BuiltinCommands {
* happen primarily on windows. * happen primarily on windows.
*/ */
if (Terminal.startedByRemoteClient && !exchange.hasServer) { if (Terminal.startedByRemoteClient && !exchange.hasServer) {
Exec("shutdown", None) +: s1 Exec(Shutdown, None) +: s1
} else { } else {
exchange prompt ConsolePromptEvent(s0) exchange prompt ConsolePromptEvent(s0)
val minGCInterval = Project val minGCInterval = Project

View File

@ -11,6 +11,7 @@ import java.lang.reflect.InvocationTargetException
import java.nio.file.Path import java.nio.file.Path
import java.io.File import java.io.File
import sbt.BasicCommandStrings.TerminateAction
import sbt.io._, syntax._ import sbt.io._, syntax._
import sbt.util._ import sbt.util._
import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._
@ -54,7 +55,7 @@ private[sbt] object TemplateCommandUtil {
case xs => xs map (_.commandLine) case xs => xs map (_.commandLine)
}) })
run(infos, arguments, state.configuration, ivyConf, globalBase, scalaModuleInfo, log) run(infos, arguments, state.configuration, ivyConf, globalBase, scalaModuleInfo, log)
"exit" :: s2.copy(remainingCommands = Nil) TerminateAction :: s2.copy(remainingCommands = Nil)
} }
private def run( private def run(

View File

@ -13,7 +13,7 @@ import java.net.Socket
import java.util.concurrent.atomic._ import java.util.concurrent.atomic._
import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit } import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
import sbt.BasicCommandStrings.networkExecPrefix import sbt.BasicCommandStrings.{ Cancel, Shutdown, TerminateAction, networkExecPrefix }
import sbt.BasicKeys._ import sbt.BasicKeys._
import sbt.internal.protocol.JsonRpcResponseError import sbt.internal.protocol.JsonRpcResponseError
import sbt.internal.server._ import sbt.internal.server._
@ -96,7 +96,7 @@ private[sbt] final class CommandExchange {
case _ => case _ =>
} }
} }
Exec("exit", Some(CommandSource(ConsoleChannel.defaultName))) Exec(TerminateAction, Some(CommandSource(ConsoleChannel.defaultName)))
case x => x case x => x
} }
case _ => commandQueue.take case _ => commandQueue.take
@ -105,11 +105,8 @@ private[sbt] final class CommandExchange {
poll match { poll match {
case Some(exec) if exec.source.fold(true)(s => channels.exists(_.name == s.channelName)) => case Some(exec) if exec.source.fold(true)(s => channels.exists(_.name == s.channelName)) =>
exec.commandLine match { exec.commandLine match {
case "shutdown" => case `TerminateAction`
exec if exec.source.fold(false)(_.channelName.startsWith("network")) =>
.withCommandLine("exit")
.withSource(Some(CommandSource(ConsoleChannel.defaultName)))
case "exit" if exec.source.fold(false)(_.channelName.startsWith("network")) =>
channels.collectFirst { channels.collectFirst {
case c: NetworkChannel if exec.source.fold(false)(_.channelName == c.name) => c case c: NetworkChannel if exec.source.fold(false)(_.channelName == c.name) => c
} match { } match {
@ -383,8 +380,7 @@ private[sbt] final class CommandExchange {
private[sbt] def shutdown(name: String): Unit = { private[sbt] def shutdown(name: String): Unit = {
Option(currentExecRef.get).foreach(cancel) Option(currentExecRef.get).foreach(cancel)
commandQueue.clear() commandQueue.clear()
val exit = val exit = Exec(Shutdown, Some(Exec.newExecId), Some(CommandSource(name)))
Exec("shutdown", Some(Exec.newExecId), Some(CommandSource(name)))
commandQueue.add(exit) commandQueue.add(exit)
() ()
} }
@ -415,7 +411,7 @@ private[sbt] final class CommandExchange {
case mt: FastTrackTask => case mt: FastTrackTask =>
mt.task match { mt.task match {
case `attach` => mt.channel.prompt(ConsolePromptEvent(lastState.get)) case `attach` => mt.channel.prompt(ConsolePromptEvent(lastState.get))
case "cancel" => Option(currentExecRef.get).foreach(cancel) case `Cancel` => Option(currentExecRef.get).foreach(cancel)
case t if t.startsWith(ContinuousCommands.stopWatch) => case t if t.startsWith(ContinuousCommands.stopWatch) =>
ContinuousCommands.stopWatchImpl(mt.channel.name) ContinuousCommands.stopWatchImpl(mt.channel.name)
mt.channel match { mt.channel match {
@ -423,8 +419,8 @@ private[sbt] final class CommandExchange {
case _ => mt.channel.prompt(ConsolePromptEvent(lastState.get)) case _ => mt.channel.prompt(ConsolePromptEvent(lastState.get))
} }
commandQueue.add(Exec(t, None, None)) commandQueue.add(Exec(t, None, None))
case "exit" => exit(mt) case `TerminateAction` => exit(mt)
case "shutdown" => case `Shutdown` =>
channels.find(_.name == mt.channel.name) match { channels.find(_.name == mt.channel.name) match {
case Some(c: NetworkChannel) => c.shutdown(false) case Some(c: NetworkChannel) => c.shutdown(false)
case _ => case _ =>

View File

@ -10,7 +10,7 @@ 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, TerminateAction } import sbt.BasicCommandStrings.{ RebootCommand, 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._
@ -90,7 +90,7 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
val commands = val commands =
allCmds.flatMap(_.split(";").flatMap(_.trim.split(" ").headOption).filterNot(_.isEmpty)) allCmds.flatMap(_.split(";").flatMap(_.trim.split(" ").headOption).filterNot(_.isEmpty))
val filter = (c: String) => val filter = (c: String) =>
c == LoadProject || c == RebootCommand || c == TerminateAction || c == "shutdown" c == LoadProject || c == RebootCommand || c == TerminateAction || c == Shutdown
val res = !commands.exists(filter) val res = !commands.exists(filter)
if (!res) { if (!res) {
previousStamps.set(getStamps(force = true)) previousStamps.set(getStamps(force = true))

View File

@ -11,6 +11,7 @@ package server
import java.net.URI import java.net.URI
import sbt.BasicCommandStrings.Shutdown
import sbt.BuildSyntax._ import sbt.BuildSyntax._
import sbt.Def._ import sbt.Def._
import sbt.Keys._ import sbt.Keys._
@ -153,7 +154,7 @@ object BuildServerProtocol {
() ()
case r: JsonRpcRequestMessage if r.method == "build/exit" => case r: JsonRpcRequestMessage if r.method == "build/exit" =>
val _ = callback.appendExec("shutdown", Some(r.id)) val _ = callback.appendExec(Shutdown, Some(r.id))
case r: JsonRpcRequestMessage if r.method == "buildTarget/sources" => case r: JsonRpcRequestMessage if r.method == "buildTarget/sources" =>
val param = Converter.fromJson[SourcesParams](json(r)).get val param = Converter.fromJson[SourcesParams](json(r)).get

View File

@ -15,6 +15,7 @@ import java.nio.channels.ClosedChannelException
import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue } import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue }
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import sbt.BasicCommandStrings.{ Shutdown, TerminateAction }
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes, LogMessageParams, MessageType } import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes, LogMessageParams, MessageType }
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes } import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes }
import sbt.internal.protocol.{ import sbt.internal.protocol.{
@ -150,7 +151,7 @@ final class NetworkChannel(
override def reader: UITask.Reader = () => { override def reader: UITask.Reader = () => {
try { try {
this.synchronized(this.wait) this.synchronized(this.wait)
Left("exit") Left(TerminateAction)
} catch { } catch {
case _: InterruptedException => Right("") case _: InterruptedException => Right("")
} }
@ -564,7 +565,7 @@ final class NetworkChannel(
super.shutdown(logShutdown) super.shutdown(logShutdown)
if (logShutdown) Terminal.consoleLog(s"shutting down client connection $name") if (logShutdown) Terminal.consoleLog(s"shutting down client connection $name")
VirtualTerminal.cancelRequests(name) VirtualTerminal.cancelRequests(name)
try jsonRpcNotify("shutdown", logShutdown) try jsonRpcNotify(Shutdown, logShutdown)
catch { case _: IOException => } catch { case _: IOException => }
running.set(false) running.set(false)
out.close() out.close()

View File

@ -13,7 +13,7 @@ import java.time.{ Instant, ZoneId, ZonedDateTime }
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import sbt.BasicCommandStrings.ContinuousExecutePrefix import sbt.BasicCommandStrings.{ ContinuousExecutePrefix, TerminateAction }
import sbt._ import sbt._
import sbt.internal.LabeledFunctions._ import sbt.internal.LabeledFunctions._
import sbt.internal.nio.FileEvent import sbt.internal.nio.FileEvent
@ -487,7 +487,7 @@ object Watch {
Watch.InputOption(4.toChar, "<ctrl-d>", "interrupt (exits sbt in batch mode)", CancelWatch), Watch.InputOption(4.toChar, "<ctrl-d>", "interrupt (exits sbt in batch mode)", CancelWatch),
Watch.InputOption('r', "re-run the command", Trigger), Watch.InputOption('r', "re-run the command", Trigger),
Watch.InputOption('s', "return to shell", Prompt), Watch.InputOption('s', "return to shell", Prompt),
Watch.InputOption('q', "quit sbt", Run("exit")), Watch.InputOption('q', "quit sbt", Run(TerminateAction)),
Watch.InputOption('?', "print options", ShowOptions) Watch.InputOption('?', "print options", ShowOptions)
) )