diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 83b38b4c8..2ff80e17e 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -15,6 +15,7 @@ import jline.TerminalFactory import sbt.io.{ IO, Using } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking } import sbt.internal.util.complete.DefaultParsers +import sbt.internal.langserver.ErrorCodes import sbt.util.Logger import sbt.protocol._ @@ -155,13 +156,63 @@ object MainLoop { state.log error errMsg state.fail } - StandardMain.exchange publishEventMessage ExecStatusEvent( + val doneEvent = ExecStatusEvent( "Done", channelName, exec.execId, - newState.remainingCommands.toVector map (_.commandLine)) + newState.remainingCommands.toVector map (_.commandLine), + exitCode(newState, state), + ) + if (doneEvent.execId.isDefined) { // send back a response or error + import sbt.protocol.codec.JsonProtocol._ + StandardMain.exchange publishEvent doneEvent + } else { // send back a notification + StandardMain.exchange publishEventMessage doneEvent + } newState } def logFullException(e: Throwable, log: Logger): Unit = State.logFullException(e, log) + + private[this] type ExitCode = Option[Long] + private[this] object ExitCode { + def apply(n: Long): ExitCode = Option(n) + val Success: ExitCode = ExitCode(0) + val Unknown: ExitCode = None + } + + private[this] def exitCode(state: State, prevState: State): ExitCode = { + exitCodeFromStateNext(state) match { + case ExitCode.Success => exitCodeFromStateOnFailure(state, prevState) + case x => x + } + } + + // State's "next" field indicates the next action for the command processor to take + // we'll use that to determine if the command failed + private[this] def exitCodeFromStateNext(state: State): ExitCode = { + state.next match { + case State.Continue => ExitCode.Success + case State.ClearGlobalLog => ExitCode.Success + case State.KeepLastLog => ExitCode.Success + case ret: State.Return => + ret.result match { + case exit: xsbti.Exit => ExitCode(exit.code().toLong) + case _: xsbti.Continue => ExitCode.Success + case _: xsbti.Reboot => ExitCode.Success + case x => + val clazz = if (x eq null) "" else " (class: " + x.getClass + ")" + state.log debug s"Unknown main result: $x$clazz" + ExitCode.Unknown + } + } + } + + // the shell command specifies an onFailure so that if an exception is thrown + // it's handled by executing the shell again, instead of the state failing + // so we also use that to indicate that the execution failed + private[this] def exitCodeFromStateOnFailure(state: State, prevState: State): ExitCode = + if (prevState.onFailure.isDefined && state.onFailure.isEmpty) ExitCode(ErrorCodes.UnknownError) + else ExitCode.Success + } diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 7c27063de..f8dfdf784 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -240,7 +240,13 @@ final class NetworkChannel(val name: String, if (isLanguageServerProtocol) { event match { case entry: StringEvent => logMessage(entry.level, entry.message) - case _ => langRespond(event, execId) + case entry: ExecStatusEvent => + entry.exitCode match { + case None => langRespond(event, entry.execId) + case Some(0) => langRespond(event, entry.execId) + case Some(exitCode) => langError(entry.execId, exitCode, "") + } + case _ => langRespond(event, execId) } } else { contentType match {