diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 9b4444970..1c5de693e 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -14,8 +14,10 @@ object BasicCommandStrings { val HelpCommand: String = "help" val CompletionsCommand: String = "completions" val Exit: String = "exit" + val Shutdown: String = "shutdown" val Quit: String = "quit" val TemplateCommand: String = "new" + val Cancel: String = "cancel" /** The command name to terminate the program.*/ val TerminateAction: String = Exit @@ -57,7 +59,8 @@ $HelpCommand def historyHelp = 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 = { val levels = Level.values.toSeq diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 594ebcca1..db0812aaf 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -56,6 +56,7 @@ object BasicCommands { call, early, exit, + shutdown, history, oldshell, client, @@ -356,6 +357,13 @@ object BasicCommands { 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") def continuous: Command = @@ -412,7 +420,7 @@ object BasicCommands { case xs => xs map (_.commandLine) }) NetworkClient.run(s0.configuration, arguments) - "exit" :: s0.copy(remainingCommands = Nil) + TerminateAction :: s0.copy(remainingCommands = Nil) } def read: Command = diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index ea42ec597..40a7d5569 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -18,6 +18,7 @@ import java.util.UUID import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue, TimeUnit } +import sbt.BasicCommandStrings.{ Shutdown, TerminateAction } import sbt.internal.client.NetworkClient.Arguments import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } import sbt.internal.protocol._ @@ -155,13 +156,13 @@ class NetworkClient( val conn = new ServerConnection(sk) { override def onNotification(msg: JsonRpcNotificationMessage): Unit = { msg.method match { - case "shutdown" => + case `Shutdown` => val log = msg.params match { case Some(jvalue) => Converter.fromJson[Boolean](jvalue).getOrElse(true) case _ => false } 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() console.appendLog(Level.Error, "sbt server disconnected") exitClean.set(false) @@ -451,7 +452,7 @@ class NetworkClient( case Success(params) => splitDiagnostics(params); Vector() case Failure(_) => Vector() } - case ("shutdown", Some(_)) => Vector.empty + case (`Shutdown`, Some(_)) => Vector.empty case (msg, _) if msg.startsWith("build/") => Vector.empty case _ => Vector( @@ -557,7 +558,7 @@ class NetworkClient( withSignalHandler(contHandler, Signals.CONT) { interactiveThread.set(Thread.currentThread) val cleaned = arguments.commandArguments - val userCommands = cleaned.takeWhile(_ != "exit") + val userCommands = cleaned.takeWhile(_ != TerminateAction) val interactive = cleaned.isEmpty val exit = cleaned.nonEmpty && userCommands.isEmpty attachUUID.set(sendJson(attach, s"""{"interactive": $interactive}""")) @@ -668,8 +669,8 @@ class NetworkClient( case _ => queue.take } } catch { - case _: InterruptedException if cmd == "shutdown" => result = 0 - case _: InterruptedException => result = if (exitClean.get) 0 else 1 + case _: InterruptedException if cmd == Shutdown => result = 0 + case _: InterruptedException => result = if (exitClean.get) 0 else 1 } } if (result == null) 1 else result diff --git a/main-command/src/main/scala/sbt/internal/ui/UITask.scala b/main-command/src/main/scala/sbt/internal/ui/UITask.scala index 113c7da59..9c5f40c55 100644 --- a/main-command/src/main/scala/sbt/internal/ui/UITask.scala +++ b/main-command/src/main/scala/sbt/internal/ui/UITask.scala @@ -12,6 +12,7 @@ import java.nio.channels.ClosedChannelException import java.util.concurrent.atomic.AtomicBoolean import jline.console.history.PersistentHistory +import sbt.BasicCommandStrings.{ Cancel, TerminateAction, Shutdown } import sbt.BasicKeys.{ historyPath, terminalShellPrompt } import sbt.State import sbt.internal.CommandChannel @@ -54,7 +55,7 @@ private[sbt] object UITask { try { @tailrec def impl(): Either[String, String] = { lineReader.readLine(clear + terminal.prompt.mkPrompt()) match { - case null => Left("exit") + case null => Left(TerminateAction) case s: String => lineReader.getHistory match { case p: PersistentHistory => @@ -63,8 +64,8 @@ private[sbt] object UITask { case _ => } s match { - case "" => impl() - case cmd @ ("shutdown" | "exit" | "cancel") => Left(cmd) + case "" => impl() + case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd) case cmd => if (terminal.prompt != Prompt.Batch) terminal.setPrompt(Prompt.Running) terminal.printStream.write(Int.MinValue) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 58f2e20cd..56a582cdc 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -15,7 +15,7 @@ import java.util.Properties import java.util.concurrent.ForkJoinPool 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.compiler.EvalImports import sbt.internal.Aggregation.AnyKeys @@ -1025,7 +1025,7 @@ object BuiltinCommands { * happen primarily on windows. */ if (Terminal.startedByRemoteClient && !exchange.hasServer) { - Exec("shutdown", None) +: s1 + Exec(Shutdown, None) +: s1 } else { exchange prompt ConsolePromptEvent(s0) val minGCInterval = Project diff --git a/main/src/main/scala/sbt/TemplateCommandUtil.scala b/main/src/main/scala/sbt/TemplateCommandUtil.scala index 0ab284c86..f9ee797ca 100644 --- a/main/src/main/scala/sbt/TemplateCommandUtil.scala +++ b/main/src/main/scala/sbt/TemplateCommandUtil.scala @@ -11,6 +11,7 @@ import java.lang.reflect.InvocationTargetException import java.nio.file.Path import java.io.File +import sbt.BasicCommandStrings.TerminateAction import sbt.io._, syntax._ import sbt.util._ import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ @@ -54,7 +55,7 @@ private[sbt] object TemplateCommandUtil { case xs => xs map (_.commandLine) }) run(infos, arguments, state.configuration, ivyConf, globalBase, scalaModuleInfo, log) - "exit" :: s2.copy(remainingCommands = Nil) + TerminateAction :: s2.copy(remainingCommands = Nil) } private def run( diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 25f74d7e1..358488605 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -13,7 +13,7 @@ import java.net.Socket import java.util.concurrent.atomic._ import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit } -import sbt.BasicCommandStrings.networkExecPrefix +import sbt.BasicCommandStrings.{ Cancel, Shutdown, TerminateAction, networkExecPrefix } import sbt.BasicKeys._ import sbt.internal.protocol.JsonRpcResponseError import sbt.internal.server._ @@ -96,7 +96,7 @@ private[sbt] final class CommandExchange { case _ => } } - Exec("exit", Some(CommandSource(ConsoleChannel.defaultName))) + Exec(TerminateAction, Some(CommandSource(ConsoleChannel.defaultName))) case x => x } case _ => commandQueue.take @@ -105,11 +105,8 @@ private[sbt] final class CommandExchange { poll match { case Some(exec) if exec.source.fold(true)(s => channels.exists(_.name == s.channelName)) => exec.commandLine match { - case "shutdown" => - exec - .withCommandLine("exit") - .withSource(Some(CommandSource(ConsoleChannel.defaultName))) - case "exit" if exec.source.fold(false)(_.channelName.startsWith("network")) => + case `TerminateAction` + if exec.source.fold(false)(_.channelName.startsWith("network")) => channels.collectFirst { case c: NetworkChannel if exec.source.fold(false)(_.channelName == c.name) => c } match { @@ -383,8 +380,7 @@ private[sbt] final class CommandExchange { private[sbt] def shutdown(name: String): Unit = { Option(currentExecRef.get).foreach(cancel) commandQueue.clear() - val exit = - Exec("shutdown", Some(Exec.newExecId), Some(CommandSource(name))) + val exit = Exec(Shutdown, Some(Exec.newExecId), Some(CommandSource(name))) commandQueue.add(exit) () } @@ -415,7 +411,7 @@ private[sbt] final class CommandExchange { case mt: FastTrackTask => mt.task match { 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) => ContinuousCommands.stopWatchImpl(mt.channel.name) mt.channel match { @@ -423,8 +419,8 @@ private[sbt] final class CommandExchange { case _ => mt.channel.prompt(ConsolePromptEvent(lastState.get)) } commandQueue.add(Exec(t, None, None)) - case "exit" => exit(mt) - case "shutdown" => + case `TerminateAction` => exit(mt) + case `Shutdown` => channels.find(_.name == mt.channel.name) match { case Some(c: NetworkChannel) => c.shutdown(false) case _ => diff --git a/main/src/main/scala/sbt/internal/nio/CheckBuildSources.scala b/main/src/main/scala/sbt/internal/nio/CheckBuildSources.scala index 6cc9bcc38..363aa7736 100644 --- a/main/src/main/scala/sbt/internal/nio/CheckBuildSources.scala +++ b/main/src/main/scala/sbt/internal/nio/CheckBuildSources.scala @@ -10,7 +10,7 @@ package internal.nio import java.nio.file.Path 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.Scope.Global import sbt.SlashSyntax0._ @@ -90,7 +90,7 @@ private[sbt] class CheckBuildSources extends AutoCloseable { val commands = allCmds.flatMap(_.split(";").flatMap(_.trim.split(" ").headOption).filterNot(_.isEmpty)) 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) if (!res) { previousStamps.set(getStamps(force = true)) diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 31c0946a2..52d08685d 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -11,6 +11,7 @@ package server import java.net.URI +import sbt.BasicCommandStrings.Shutdown import sbt.BuildSyntax._ import sbt.Def._ import sbt.Keys._ @@ -153,7 +154,7 @@ object BuildServerProtocol { () 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" => val param = Converter.fromJson[SourcesParams](json(r)).get diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 4fa5a37a5..549ccd022 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -15,6 +15,7 @@ import java.nio.channels.ClosedChannelException import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue } 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 } import sbt.internal.protocol.{ @@ -150,7 +151,7 @@ final class NetworkChannel( override def reader: UITask.Reader = () => { try { this.synchronized(this.wait) - Left("exit") + Left(TerminateAction) } catch { case _: InterruptedException => Right("") } @@ -564,7 +565,7 @@ final class NetworkChannel( super.shutdown(logShutdown) if (logShutdown) Terminal.consoleLog(s"shutting down client connection $name") VirtualTerminal.cancelRequests(name) - try jsonRpcNotify("shutdown", logShutdown) + try jsonRpcNotify(Shutdown, logShutdown) catch { case _: IOException => } running.set(false) out.close() diff --git a/main/src/main/scala/sbt/nio/Watch.scala b/main/src/main/scala/sbt/nio/Watch.scala index b8c0c2789..68994055b 100644 --- a/main/src/main/scala/sbt/nio/Watch.scala +++ b/main/src/main/scala/sbt/nio/Watch.scala @@ -13,7 +13,7 @@ import java.time.{ Instant, ZoneId, ZonedDateTime } import java.util.Locale import java.util.concurrent.TimeUnit -import sbt.BasicCommandStrings.ContinuousExecutePrefix +import sbt.BasicCommandStrings.{ ContinuousExecutePrefix, TerminateAction } import sbt._ import sbt.internal.LabeledFunctions._ import sbt.internal.nio.FileEvent @@ -487,7 +487,7 @@ object Watch { Watch.InputOption(4.toChar, "", "interrupt (exits sbt in batch mode)", CancelWatch), Watch.InputOption('r', "re-run the command", Trigger), 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) )