mirror of https://github.com/sbt/sbt.git
Add State extension
Fixes https://github.com/sbt/sbt/issues/3112 This unpacks Extracted as State's extension methods. In addition this provides a way of responding via LSP.
This commit is contained in:
parent
1111b3c581
commit
25a79d0ac6
|
|
@ -675,6 +675,7 @@ lazy val protocolProj = (project in file("protocol"))
|
||||||
exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQuerySuccess.copy$default$*"),
|
exclude[DirectMissingMethodProblem]("sbt.protocol.SettingQuerySuccess.copy$default$*"),
|
||||||
// ignore missing methods in sbt.internal
|
// ignore missing methods in sbt.internal
|
||||||
exclude[DirectMissingMethodProblem]("sbt.internal.*"),
|
exclude[DirectMissingMethodProblem]("sbt.internal.*"),
|
||||||
|
exclude[MissingTypesProblem]("sbt.internal.protocol.JsonRpcResponseError"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,8 @@ private[sbt] trait BuildSyntax {
|
||||||
def dependsOn(deps: ClasspathDep[ProjectReference]*): DslEntry = DslEntry.DslDependsOn(deps)
|
def dependsOn(deps: ClasspathDep[ProjectReference]*): DslEntry = DslEntry.DslDependsOn(deps)
|
||||||
// avoid conflict with `sbt.Keys.aggregate`
|
// avoid conflict with `sbt.Keys.aggregate`
|
||||||
def aggregateProjects(refs: ProjectReference*): DslEntry = DslEntry.DslAggregate(refs)
|
def aggregateProjects(refs: ProjectReference*): DslEntry = DslEntry.DslAggregate(refs)
|
||||||
|
|
||||||
|
implicit def sbtStateToUpperStateOps(s: State): UpperStateOps =
|
||||||
|
new UpperStateOps.UpperStateOpsImpl(s)
|
||||||
}
|
}
|
||||||
private[sbt] object BuildSyntax extends BuildSyntax
|
private[sbt] object BuildSyntax extends BuildSyntax
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import java.util.Properties
|
||||||
import jline.TerminalFactory
|
import jline.TerminalFactory
|
||||||
import sbt.internal.{ Aggregation, ShutdownHooks }
|
import sbt.internal.{ Aggregation, ShutdownHooks }
|
||||||
import sbt.internal.langserver.ErrorCodes
|
import sbt.internal.langserver.ErrorCodes
|
||||||
|
import sbt.internal.protocol.JsonRpcResponseError
|
||||||
import sbt.internal.util.complete.Parser
|
import sbt.internal.util.complete.Parser
|
||||||
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking }
|
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking }
|
||||||
import sbt.io.{ IO, Using }
|
import sbt.io.{ IO, Using }
|
||||||
|
|
@ -230,6 +231,9 @@ object MainLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
case err: JsonRpcResponseError =>
|
||||||
|
StandardMain.exchange.respondError(err, exec.execId, channelName.map(CommandSource(_)))
|
||||||
|
throw err
|
||||||
case err: Throwable =>
|
case err: Throwable =>
|
||||||
val errorEvent = ExecStatusEvent(
|
val errorEvent = ExecStatusEvent(
|
||||||
"Error",
|
"Error",
|
||||||
|
|
@ -237,9 +241,10 @@ object MainLoop {
|
||||||
exec.execId,
|
exec.execId,
|
||||||
Vector(),
|
Vector(),
|
||||||
ExitCode(ErrorCodes.UnknownError),
|
ExitCode(ErrorCodes.UnknownError),
|
||||||
|
Option(err.getMessage),
|
||||||
)
|
)
|
||||||
import sbt.protocol.codec.JsonProtocol._
|
import sbt.protocol.codec.JsonProtocol._
|
||||||
StandardMain.exchange publishEvent errorEvent
|
StandardMain.exchange.publishEvent(errorEvent)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import sjsonnew.JsonFormat
|
||||||
|
import Def.Setting
|
||||||
|
import sbt.internal.{ BuildStructure, LoadedBuildUnit, SessionSettings }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends State with setting-level knowledge.
|
||||||
|
*/
|
||||||
|
trait UpperStateOps extends Any {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProjectRef to the current project of the state session that can be change using
|
||||||
|
* `project` commmand.
|
||||||
|
*/
|
||||||
|
def currentRef: ProjectRef
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current project of the state session that can be change using `project` commmand.
|
||||||
|
*/
|
||||||
|
def currentProject: ResolvedProject
|
||||||
|
|
||||||
|
private[sbt] def structure: BuildStructure
|
||||||
|
|
||||||
|
private[sbt] def session: SessionSettings
|
||||||
|
|
||||||
|
private[sbt] def currentUnit: LoadedBuildUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value assigned to `key` in the computed settings map.
|
||||||
|
* If the project axis is not explicitly specified, it is resolved to be the current project according to the extracted `session`.
|
||||||
|
* Other axes are resolved to be `Zero` if they are not specified.
|
||||||
|
*/
|
||||||
|
def setting[A](key: SettingKey[A]): A
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value assigned to `key` in the computed settings map.
|
||||||
|
* If the project axis is not explicitly specified, it is resolved to be the current project according to the extracted `session`.
|
||||||
|
* Other axes are resolved to be `Zero` if they are not specified.
|
||||||
|
*/
|
||||||
|
def taskValue[A](key: TaskKey[A]): Task[A]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the task specified by `key` and returns the transformed State and the resulting value of the task.
|
||||||
|
* If the project axis is not defined for the key, it is resolved to be the current project.
|
||||||
|
* Other axes are resolved to `Zero` if unspecified.
|
||||||
|
*
|
||||||
|
* This method requests execution of only the given task and does not aggregate execution.
|
||||||
|
* See `runAggregated` for that.
|
||||||
|
*
|
||||||
|
* To avoid race conditions, this should NOT be called from a task.
|
||||||
|
*/
|
||||||
|
def unsafeRunTask[A](key: TaskKey[A]): (State, A)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the input task specified by `key`, using the `input` as the input to it, and returns the transformed State
|
||||||
|
* and the resulting value of the input task.
|
||||||
|
*
|
||||||
|
* If the project axis is not defined for the key, it is resolved to be the current project.
|
||||||
|
* Other axes are resolved to `Zero` if unspecified.
|
||||||
|
*
|
||||||
|
* This method requests execution of only the given task and does not aggregate execution.
|
||||||
|
* To avoid race conditions, this should NOT be called from a task.
|
||||||
|
*/
|
||||||
|
def unsafeRunInputTask[A](key: InputKey[A], input: String): (State, A)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the tasks selected by aggregating `key` and returns the transformed State.
|
||||||
|
* If the project axis is not defined for the key, it is resolved to be the current project.
|
||||||
|
* The project axis is what determines where aggregation starts, so ensure this is set to what you want.
|
||||||
|
* Other axes are resolved to `Zero` if unspecified.
|
||||||
|
*
|
||||||
|
* To avoid race conditions, this should NOT be called from a task.
|
||||||
|
*/
|
||||||
|
def unsafeRunAggregated[A](key: TaskKey[A]): State
|
||||||
|
|
||||||
|
/** Appends the given settings to all the build state settings, including session settings. */
|
||||||
|
def appendWithSession(settings: Seq[Setting[_]]): State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the given settings to the original build state settings, discarding any settings
|
||||||
|
* appended to the session in the process.
|
||||||
|
*/
|
||||||
|
def appendWithoutSession(settings: Seq[Setting[_]], state: State): State
|
||||||
|
|
||||||
|
def respondEvent[A: JsonFormat](event: A): Unit
|
||||||
|
def respondError(code: Long, message: String): Unit
|
||||||
|
def notifyEvent[A: JsonFormat](method: String, params: A): Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
object UpperStateOps {
|
||||||
|
lazy val exchange = StandardMain.exchange
|
||||||
|
|
||||||
|
implicit class UpperStateOpsImpl(val s: State) extends AnyVal with UpperStateOps {
|
||||||
|
def extract: Extracted = Project.extract(s)
|
||||||
|
|
||||||
|
def currentRef = extract.currentRef
|
||||||
|
|
||||||
|
def currentProject: ResolvedProject = extract.currentProject
|
||||||
|
|
||||||
|
private[sbt] def structure: BuildStructure = Project.structure(s)
|
||||||
|
|
||||||
|
private[sbt] def currentUnit: LoadedBuildUnit = extract.currentUnit
|
||||||
|
|
||||||
|
private[sbt] def session: SessionSettings = Project.session(s)
|
||||||
|
|
||||||
|
def setting[A](key: SettingKey[A]): A = extract.get(key)
|
||||||
|
|
||||||
|
def taskValue[A](key: TaskKey[A]): Task[A] = extract.get(key)
|
||||||
|
|
||||||
|
def unsafeRunTask[A](key: TaskKey[A]): (State, A) = extract.runTask(key, s)
|
||||||
|
|
||||||
|
def unsafeRunInputTask[A](key: InputKey[A], input: String): (State, A) =
|
||||||
|
extract.runInputTask(key, input, s)
|
||||||
|
|
||||||
|
def unsafeRunAggregated[A](key: TaskKey[A]): State =
|
||||||
|
extract.runAggregated(key, s)
|
||||||
|
|
||||||
|
def appendWithSession(settings: Seq[Setting[_]]): State =
|
||||||
|
extract.appendWithSession(settings, s)
|
||||||
|
|
||||||
|
def appendWithoutSession(settings: Seq[Setting[_]], state: State): State =
|
||||||
|
extract.appendWithoutSession(settings, s)
|
||||||
|
|
||||||
|
def respondEvent[A: JsonFormat](event: A): Unit = {
|
||||||
|
exchange.respondEvent(event, s.currentCommand.flatMap(_.execId), s.source)
|
||||||
|
}
|
||||||
|
def respondError(code: Long, message: String): Unit = {
|
||||||
|
exchange.respondError(code, message, s.currentCommand.flatMap(_.execId), s.source)
|
||||||
|
}
|
||||||
|
def notifyEvent[A: JsonFormat](method: String, params: A): Unit = {
|
||||||
|
exchange.notifyEvent(method, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ import java.util.concurrent.atomic._
|
||||||
|
|
||||||
import sbt.BasicKeys._
|
import sbt.BasicKeys._
|
||||||
import sbt.nio.Watch.NullLogger
|
import sbt.nio.Watch.NullLogger
|
||||||
|
import sbt.internal.protocol.JsonRpcResponseError
|
||||||
import sbt.internal.langserver.{ LogMessageParams, MessageType }
|
import sbt.internal.langserver.{ LogMessageParams, MessageType }
|
||||||
import sbt.internal.server._
|
import sbt.internal.server._
|
||||||
import sbt.internal.util.codec.JValueFormats
|
import sbt.internal.util.codec.JValueFormats
|
||||||
|
|
@ -52,6 +53,17 @@ private[sbt] final class CommandExchange {
|
||||||
private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {}
|
private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {}
|
||||||
|
|
||||||
def channels: List[CommandChannel] = channelBuffer.toList
|
def channels: List[CommandChannel] = channelBuffer.toList
|
||||||
|
private[this] def removeChannels(toDel: List[CommandChannel]): Unit = {
|
||||||
|
toDel match {
|
||||||
|
case Nil => // do nothing
|
||||||
|
case xs =>
|
||||||
|
channelBufferLock.synchronized {
|
||||||
|
channelBuffer --= xs
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def subscribe(c: CommandChannel): Unit = channelBufferLock.synchronized {
|
def subscribe(c: CommandChannel): Unit = channelBufferLock.synchronized {
|
||||||
channelBuffer.append(c)
|
channelBuffer.append(c)
|
||||||
c.register(commandChannelQueue)
|
c.register(commandChannelQueue)
|
||||||
|
|
@ -181,6 +193,69 @@ private[sbt] final class CommandExchange {
|
||||||
server = None
|
server = None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is an interface to directly respond events.
|
||||||
|
private[sbt] def respondError(
|
||||||
|
code: Long,
|
||||||
|
message: String,
|
||||||
|
execId: Option[String],
|
||||||
|
source: Option[CommandSource]
|
||||||
|
): Unit = {
|
||||||
|
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
|
||||||
|
channels.foreach {
|
||||||
|
case _: ConsoleChannel =>
|
||||||
|
case c: NetworkChannel =>
|
||||||
|
try {
|
||||||
|
// broadcast to all network channels
|
||||||
|
c.respondError(code, message, execId, source)
|
||||||
|
} catch {
|
||||||
|
case _: IOException =>
|
||||||
|
toDel += c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeChannels(toDel.toList)
|
||||||
|
}
|
||||||
|
|
||||||
|
private[sbt] def respondError(
|
||||||
|
err: JsonRpcResponseError,
|
||||||
|
execId: Option[String],
|
||||||
|
source: Option[CommandSource]
|
||||||
|
): Unit = {
|
||||||
|
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
|
||||||
|
channels.foreach {
|
||||||
|
case _: ConsoleChannel =>
|
||||||
|
case c: NetworkChannel =>
|
||||||
|
try {
|
||||||
|
// broadcast to all network channels
|
||||||
|
c.respondError(err, execId, source)
|
||||||
|
} catch {
|
||||||
|
case _: IOException =>
|
||||||
|
toDel += c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeChannels(toDel.toList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an interface to directly respond events.
|
||||||
|
private[sbt] def respondEvent[A: JsonFormat](
|
||||||
|
event: A,
|
||||||
|
execId: Option[String],
|
||||||
|
source: Option[CommandSource]
|
||||||
|
): Unit = {
|
||||||
|
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
|
||||||
|
channels.foreach {
|
||||||
|
case _: ConsoleChannel =>
|
||||||
|
case c: NetworkChannel =>
|
||||||
|
try {
|
||||||
|
// broadcast to all network channels
|
||||||
|
c.respondEvent(event, execId, source)
|
||||||
|
} catch {
|
||||||
|
case _: IOException =>
|
||||||
|
toDel += c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeChannels(toDel.toList)
|
||||||
|
}
|
||||||
|
|
||||||
// This is an interface to directly notify events.
|
// This is an interface to directly notify events.
|
||||||
private[sbt] def notifyEvent[A: JsonFormat](method: String, params: A): Unit = {
|
private[sbt] def notifyEvent[A: JsonFormat](method: String, params: A): Unit = {
|
||||||
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
|
val toDel: ListBuffer[CommandChannel] = ListBuffer.empty
|
||||||
|
|
@ -195,14 +270,7 @@ private[sbt] final class CommandExchange {
|
||||||
toDel += c
|
toDel += c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toDel.toList match {
|
removeChannels(toDel.toList)
|
||||||
case Nil => // do nothing
|
|
||||||
case xs =>
|
|
||||||
channelBufferLock.synchronized {
|
|
||||||
channelBuffer --= xs
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def tryTo(x: => Unit, c: CommandChannel, toDel: ListBuffer[CommandChannel]): Unit =
|
private def tryTo(x: => Unit, c: CommandChannel, toDel: ListBuffer[CommandChannel]): Unit =
|
||||||
|
|
@ -248,14 +316,7 @@ private[sbt] final class CommandExchange {
|
||||||
tryTo(c.publishEvent(event), c, toDel)
|
tryTo(c.publishEvent(event), c, toDel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toDel.toList match {
|
removeChannels(toDel.toList)
|
||||||
case Nil => // do nothing
|
|
||||||
case xs =>
|
|
||||||
channelBufferLock.synchronized {
|
|
||||||
channelBuffer --= xs
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] def toLogMessageParams(event: StringEvent): LogMessageParams = {
|
private[sbt] def toLogMessageParams(event: StringEvent): LogMessageParams = {
|
||||||
|
|
@ -290,14 +351,7 @@ private[sbt] final class CommandExchange {
|
||||||
toDel += c
|
toDel += c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toDel.toList match {
|
removeChannels(toDel.toList)
|
||||||
case Nil => // do nothing
|
|
||||||
case xs =>
|
|
||||||
channelBufferLock.synchronized {
|
|
||||||
channelBuffer --= xs
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fanout publishEvent
|
// fanout publishEvent
|
||||||
|
|
@ -328,13 +382,6 @@ private[sbt] final class CommandExchange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toDel.toList match {
|
removeChannels(toDel.toList)
|
||||||
case Nil => // do nothing
|
|
||||||
case xs =>
|
|
||||||
channelBufferLock.synchronized {
|
|
||||||
channelBuffer --= xs
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,15 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { self =>
|
||||||
): Unit =
|
): Unit =
|
||||||
jsonRpcRespondErrorImpl(execId, code, message, Option(Converter.toJson[A](data).get))
|
jsonRpcRespondErrorImpl(execId, code, message, Option(Converter.toJson[A](data).get))
|
||||||
|
|
||||||
|
private[sbt] def jsonRpcRespondError(
|
||||||
|
execId: Option[String],
|
||||||
|
err: JsonRpcResponseError
|
||||||
|
): Unit = {
|
||||||
|
val m = JsonRpcResponseMessage("2.0", execId, None, Option(err))
|
||||||
|
val bytes = Serialization.serializeResponseMessage(m)
|
||||||
|
publishBytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
private[this] def jsonRpcRespondErrorImpl(
|
private[this] def jsonRpcRespondErrorImpl(
|
||||||
execId: Option[String],
|
execId: Option[String],
|
||||||
code: Long,
|
code: Long,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,11 @@ import sbt.internal.langserver.{ ErrorCodes, CancelRequestParams }
|
||||||
import sbt.internal.util.{ ObjectEvent, StringEvent }
|
import sbt.internal.util.{ ObjectEvent, StringEvent }
|
||||||
import sbt.internal.util.complete.Parser
|
import sbt.internal.util.complete.Parser
|
||||||
import sbt.internal.util.codec.JValueFormats
|
import sbt.internal.util.codec.JValueFormats
|
||||||
import sbt.internal.protocol.{ JsonRpcRequestMessage, JsonRpcNotificationMessage }
|
import sbt.internal.protocol.{
|
||||||
|
JsonRpcResponseError,
|
||||||
|
JsonRpcRequestMessage,
|
||||||
|
JsonRpcNotificationMessage
|
||||||
|
}
|
||||||
import sbt.util.Logger
|
import sbt.util.Logger
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
|
|
@ -241,6 +245,25 @@ final class NetworkChannel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private[sbt] def respondError(
|
||||||
|
err: JsonRpcResponseError,
|
||||||
|
execId: Option[String],
|
||||||
|
source: Option[CommandSource]
|
||||||
|
): Unit = jsonRpcRespondError(execId, err)
|
||||||
|
|
||||||
|
private[sbt] def respondError(
|
||||||
|
code: Long,
|
||||||
|
message: String,
|
||||||
|
execId: Option[String],
|
||||||
|
source: Option[CommandSource]
|
||||||
|
): Unit = jsonRpcRespondError(execId, code, message)
|
||||||
|
|
||||||
|
private[sbt] def respondEvent[A: JsonFormat](
|
||||||
|
event: A,
|
||||||
|
execId: Option[String],
|
||||||
|
source: Option[CommandSource]
|
||||||
|
): Unit = jsonRpcRespond(event, execId)
|
||||||
|
|
||||||
private[sbt] def notifyEvent[A: JsonFormat](method: String, params: A): Unit = {
|
private[sbt] def notifyEvent[A: JsonFormat](method: String, params: A): Unit = {
|
||||||
if (isLanguageServerProtocol) {
|
if (isLanguageServerProtocol) {
|
||||||
jsonRpcNotify(method, params)
|
jsonRpcNotify(method, params)
|
||||||
|
|
@ -255,9 +278,10 @@ final class NetworkChannel(
|
||||||
case entry: StringEvent => logMessage(entry.level, entry.message)
|
case entry: StringEvent => logMessage(entry.level, entry.message)
|
||||||
case entry: ExecStatusEvent =>
|
case entry: ExecStatusEvent =>
|
||||||
entry.exitCode match {
|
entry.exitCode match {
|
||||||
case None => jsonRpcRespond(event, entry.execId)
|
case None => jsonRpcRespond(event, entry.execId)
|
||||||
case Some(0) => jsonRpcRespond(event, entry.execId)
|
case Some(0) => jsonRpcRespond(event, entry.execId)
|
||||||
case Some(exitCode) => jsonRpcRespondError(entry.execId, exitCode, "")
|
case Some(exitCode) =>
|
||||||
|
jsonRpcRespondError(entry.execId, exitCode, entry.message.getOrElse(""))
|
||||||
}
|
}
|
||||||
case _ => jsonRpcRespond(event, execId)
|
case _ => jsonRpcRespond(event, execId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ package sbt.internal.protocol
|
||||||
final class JsonRpcResponseError private (
|
final class JsonRpcResponseError private (
|
||||||
val code: Long,
|
val code: Long,
|
||||||
val message: String,
|
val message: String,
|
||||||
val data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]) extends Serializable {
|
val data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]) extends RuntimeException(message) with Serializable {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ final class JsonRpcResponseError private (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object JsonRpcResponseError {
|
object JsonRpcResponseError {
|
||||||
|
def apply(code: Long, message: String): JsonRpcResponseError = new JsonRpcResponseError(code, message, None)
|
||||||
def apply(code: Long, message: String, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): JsonRpcResponseError = new JsonRpcResponseError(code, message, data)
|
def apply(code: Long, message: String, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): JsonRpcResponseError = new JsonRpcResponseError(code, message, data)
|
||||||
def apply(code: Long, message: String, data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): JsonRpcResponseError = new JsonRpcResponseError(code, message, Option(data))
|
def apply(code: Long, message: String, data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): JsonRpcResponseError = new JsonRpcResponseError(code, message, Option(data))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,24 @@ final class ExecStatusEvent private (
|
||||||
val channelName: Option[String],
|
val channelName: Option[String],
|
||||||
val execId: Option[String],
|
val execId: Option[String],
|
||||||
val commandQueue: Vector[String],
|
val commandQueue: Vector[String],
|
||||||
val exitCode: Option[Long]) extends sbt.protocol.EventMessage() with Serializable {
|
val exitCode: Option[Long],
|
||||||
|
val message: Option[String]) extends sbt.protocol.EventMessage() with Serializable {
|
||||||
|
|
||||||
private def this(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String]) = this(status, channelName, execId, commandQueue, None)
|
private def this(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String]) = this(status, channelName, execId, commandQueue, None, None)
|
||||||
|
private def this(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String], exitCode: Option[Long]) = this(status, channelName, execId, commandQueue, exitCode, None)
|
||||||
|
|
||||||
override def equals(o: Any): Boolean = o match {
|
override def equals(o: Any): Boolean = o match {
|
||||||
case x: ExecStatusEvent => (this.status == x.status) && (this.channelName == x.channelName) && (this.execId == x.execId) && (this.commandQueue == x.commandQueue) && (this.exitCode == x.exitCode)
|
case x: ExecStatusEvent => (this.status == x.status) && (this.channelName == x.channelName) && (this.execId == x.execId) && (this.commandQueue == x.commandQueue) && (this.exitCode == x.exitCode) && (this.message == x.message)
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
override def hashCode: Int = {
|
override def hashCode: Int = {
|
||||||
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.ExecStatusEvent".##) + status.##) + channelName.##) + execId.##) + commandQueue.##) + exitCode.##)
|
37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.ExecStatusEvent".##) + status.##) + channelName.##) + execId.##) + commandQueue.##) + exitCode.##) + message.##)
|
||||||
}
|
}
|
||||||
override def toString: String = {
|
override def toString: String = {
|
||||||
"ExecStatusEvent(" + status + ", " + channelName + ", " + execId + ", " + commandQueue + ", " + exitCode + ")"
|
"ExecStatusEvent(" + status + ", " + channelName + ", " + execId + ", " + commandQueue + ", " + exitCode + ", " + message + ")"
|
||||||
}
|
}
|
||||||
private[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue, exitCode: Option[Long] = exitCode): ExecStatusEvent = {
|
private[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue, exitCode: Option[Long] = exitCode, message: Option[String] = message): ExecStatusEvent = {
|
||||||
new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode)
|
new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode, message)
|
||||||
}
|
}
|
||||||
def withStatus(status: String): ExecStatusEvent = {
|
def withStatus(status: String): ExecStatusEvent = {
|
||||||
copy(status = status)
|
copy(status = status)
|
||||||
|
|
@ -51,6 +53,12 @@ final class ExecStatusEvent private (
|
||||||
def withExitCode(exitCode: Long): ExecStatusEvent = {
|
def withExitCode(exitCode: Long): ExecStatusEvent = {
|
||||||
copy(exitCode = Option(exitCode))
|
copy(exitCode = Option(exitCode))
|
||||||
}
|
}
|
||||||
|
def withMessage(message: Option[String]): ExecStatusEvent = {
|
||||||
|
copy(message = message)
|
||||||
|
}
|
||||||
|
def withMessage(message: String): ExecStatusEvent = {
|
||||||
|
copy(message = Option(message))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
object ExecStatusEvent {
|
object ExecStatusEvent {
|
||||||
|
|
||||||
|
|
@ -58,4 +66,6 @@ object ExecStatusEvent {
|
||||||
def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue)
|
def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue)
|
||||||
def apply(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String], exitCode: Option[Long]): ExecStatusEvent = new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode)
|
def apply(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String], exitCode: Option[Long]): ExecStatusEvent = new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode)
|
||||||
def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String], exitCode: Long): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue, Option(exitCode))
|
def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String], exitCode: Long): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue, Option(exitCode))
|
||||||
|
def apply(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String], exitCode: Option[Long], message: Option[String]): ExecStatusEvent = new ExecStatusEvent(status, channelName, execId, commandQueue, exitCode, message)
|
||||||
|
def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String], exitCode: Long, message: String): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue, Option(exitCode), Option(message))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,9 @@ implicit lazy val ExecStatusEventFormat: JsonFormat[sbt.protocol.ExecStatusEvent
|
||||||
val execId = unbuilder.readField[Option[String]]("execId")
|
val execId = unbuilder.readField[Option[String]]("execId")
|
||||||
val commandQueue = unbuilder.readField[Vector[String]]("commandQueue")
|
val commandQueue = unbuilder.readField[Vector[String]]("commandQueue")
|
||||||
val exitCode = unbuilder.readField[Option[Long]]("exitCode")
|
val exitCode = unbuilder.readField[Option[Long]]("exitCode")
|
||||||
|
val message = unbuilder.readField[Option[String]]("message")
|
||||||
unbuilder.endObject()
|
unbuilder.endObject()
|
||||||
sbt.protocol.ExecStatusEvent(status, channelName, execId, commandQueue, exitCode)
|
sbt.protocol.ExecStatusEvent(status, channelName, execId, commandQueue, exitCode, message)
|
||||||
case None =>
|
case None =>
|
||||||
deserializationError("Expected JsObject but found None")
|
deserializationError("Expected JsObject but found None")
|
||||||
}
|
}
|
||||||
|
|
@ -29,6 +30,7 @@ implicit lazy val ExecStatusEventFormat: JsonFormat[sbt.protocol.ExecStatusEvent
|
||||||
builder.addField("execId", obj.execId)
|
builder.addField("execId", obj.execId)
|
||||||
builder.addField("commandQueue", obj.commandQueue)
|
builder.addField("commandQueue", obj.commandQueue)
|
||||||
builder.addField("exitCode", obj.exitCode)
|
builder.addField("exitCode", obj.exitCode)
|
||||||
|
builder.addField("message", obj.message)
|
||||||
builder.endObject()
|
builder.endObject()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,12 @@ type JsonRpcResponseError
|
||||||
## information about the error. Can be omitted.
|
## information about the error. Can be omitted.
|
||||||
data: sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
data: sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||||
|
|
||||||
|
#xinterface RuntimeException(message)
|
||||||
|
|
||||||
#xtostring s"""JsonRpcResponseError($code, $message, ${sbt.protocol.Serialization.compactPrintJsonOpt(data)})"""
|
#xtostring s"""JsonRpcResponseError($code, $message, ${sbt.protocol.Serialization.compactPrintJsonOpt(data)})"""
|
||||||
|
|
||||||
|
#xcompanion def apply(code: Long, message: String): JsonRpcResponseError = new JsonRpcResponseError(code, message, None)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type JsonRpcNotificationMessage implements JsonRpcMessage
|
type JsonRpcNotificationMessage implements JsonRpcMessage
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ type ExecStatusEvent implements EventMessage {
|
||||||
execId: String
|
execId: String
|
||||||
commandQueue: [String]
|
commandQueue: [String]
|
||||||
exitCode: Long @since("1.1.2")
|
exitCode: Long @since("1.1.2")
|
||||||
|
message: String @since("1.4.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingQueryResponse implements EventMessage {}
|
interface SettingQueryResponse implements EventMessage {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import sbt.internal.server.{ ServerHandler, ServerIntent }
|
||||||
|
|
||||||
|
ThisBuild / scalaVersion := "2.12.10"
|
||||||
|
|
||||||
|
Global / serverLog / logLevel := Level.Debug
|
||||||
|
// custom handler
|
||||||
|
Global / serverHandlers += ServerHandler({ callback =>
|
||||||
|
import callback._
|
||||||
|
import sjsonnew.BasicJsonProtocol._
|
||||||
|
import sbt.internal.protocol.JsonRpcRequestMessage
|
||||||
|
ServerIntent(
|
||||||
|
{
|
||||||
|
case r: JsonRpcRequestMessage if r.method == "foo/export" =>
|
||||||
|
appendExec(Exec("fooExport", Some(r.id), Some(CommandSource(callback.name))))
|
||||||
|
()
|
||||||
|
case r: JsonRpcRequestMessage if r.method == "foo/fail" =>
|
||||||
|
appendExec(Exec("fooFail", Some(r.id), Some(CommandSource(callback.name))))
|
||||||
|
()
|
||||||
|
case r: JsonRpcRequestMessage if r.method == "foo/customfail" =>
|
||||||
|
appendExec(Exec("fooCustomFail", Some(r.id), Some(CommandSource(callback.name))))
|
||||||
|
()
|
||||||
|
case r: JsonRpcRequestMessage if r.method == "foo/notification" =>
|
||||||
|
appendExec(Exec("fooNotification", Some(r.id), Some(CommandSource(callback.name))))
|
||||||
|
()
|
||||||
|
case r: JsonRpcRequestMessage if r.method == "foo/rootClasspath" =>
|
||||||
|
appendExec(Exec("fooClasspath", Some(r.id), Some(CommandSource(callback.name))))
|
||||||
|
()
|
||||||
|
},
|
||||||
|
PartialFunction.empty
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
lazy val fooClasspath = taskKey[Unit]("")
|
||||||
|
lazy val root = (project in file("."))
|
||||||
|
.settings(
|
||||||
|
name := "response",
|
||||||
|
commands += Command.command("fooExport") { s0: State =>
|
||||||
|
val (s1, cp) = s0.unsafeRunTask(Compile / fullClasspath)
|
||||||
|
s0.respondEvent(cp.map(_.data))
|
||||||
|
s1
|
||||||
|
},
|
||||||
|
commands += Command.command("fooFail") { s0: State =>
|
||||||
|
sys.error("fail message")
|
||||||
|
},
|
||||||
|
commands += Command.command("fooCustomFail") { s0: State =>
|
||||||
|
import sbt.internal.protocol.JsonRpcResponseError
|
||||||
|
throw JsonRpcResponseError(500, "some error")
|
||||||
|
},
|
||||||
|
commands += Command.command("fooNotification") { s0: State =>
|
||||||
|
import CacheImplicits._
|
||||||
|
s0.notifyEvent("foo/something", "something")
|
||||||
|
s0
|
||||||
|
},
|
||||||
|
fooClasspath := {
|
||||||
|
val s = state.value
|
||||||
|
val cp = (Compile / fullClasspath).value
|
||||||
|
s.respondEvent(cp.map(_.data))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package testpkg
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
// starts svr using server-test/response and perform custom server tests
|
||||||
|
object ResponseTest extends AbstractServerTest {
|
||||||
|
override val testDirectory: String = "response"
|
||||||
|
|
||||||
|
test("response from a command") { _ =>
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
"""{ "jsonrpc": "2.0", "id": "10", "method": "foo/export", "params": {} }"""
|
||||||
|
)
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
println(s)
|
||||||
|
(s contains """"id":"10"""") &&
|
||||||
|
(s contains "scala-library.jar")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("response from a task") { _ =>
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
"""{ "jsonrpc": "2.0", "id": "11", "method": "foo/rootClasspath", "params": {} }"""
|
||||||
|
)
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
println(s)
|
||||||
|
(s contains """"id":"11"""") &&
|
||||||
|
(s contains "scala-library.jar")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("a command failure") { _ =>
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
"""{ "jsonrpc": "2.0", "id": "12", "method": "foo/fail", "params": {} }"""
|
||||||
|
)
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
println(s)
|
||||||
|
(s contains """"error":{"code":-33000,"message":"fail message"""")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("a command failure with custom code") { _ =>
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
"""{ "jsonrpc": "2.0", "id": "13", "method": "foo/customfail", "params": {} }"""
|
||||||
|
)
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
println(s)
|
||||||
|
(s contains """"error":{"code":500,"message":"some error"""")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("a command with a notification") { _ =>
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
"""{ "jsonrpc": "2.0", "id": "14", "method": "foo/notification", "params": {} }"""
|
||||||
|
)
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
println(s)
|
||||||
|
(s contains """{"jsonrpc":"2.0","method":"foo/something","params":"something"}""")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue