Merge pull request #8040 from eed3si9n/wip/sandbox

[1.x] Implement client-side run
This commit is contained in:
eugene yokota 2025-03-02 22:07:46 -05:00 committed by GitHub
commit bdaf1d9d32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 925 additions and 139 deletions

View File

@ -758,7 +758,7 @@ lazy val protocolProj = (project in file("protocol"))
// General command support and core commands not specific to a build system
lazy val commandProj = (project in file("main-command"))
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
.dependsOn(protocolProj, completeProj, utilLogging)
.dependsOn(protocolProj, completeProj, utilLogging, runProj)
.settings(
testedBaseSettings,
name := "Command",
@ -1072,6 +1072,7 @@ lazy val mainProj = (project in file("main"))
exclude[IncompatibleTemplateDefProblem]("sbt.internal.server.BuildServerReporter"),
exclude[MissingClassProblem]("sbt.internal.CustomHttp*"),
exclude[ReversedMissingMethodProblem]("sbt.JobHandle.isAutoCancel"),
exclude[ReversedMissingMethodProblem]("sbt.BackgroundJobService.createWorkingDirectory"),
)
)
.configure(

View File

@ -63,6 +63,8 @@ abstract class CommandChannel {
}
}
}
protected def appendExec(commandLine: String, execId: Option[String]): Boolean =
append(Exec(commandLine, execId.orElse(Some(Exec.newExecId)), Some(CommandSource(name))))
def poll: Option[Exec] = Option(commandQueue.poll)
def prompt(e: ConsolePromptEvent): Unit = userThread.onConsolePromptEvent(e)
@ -81,20 +83,21 @@ abstract class CommandChannel {
private[sbt] final def logLevel: Level.Value = level.get
private[this] def setLevel(value: Level.Value, cmd: String): Boolean = {
level.set(value)
append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name))))
appendExec(cmd, None)
}
private[sbt] def onCommand: String => Boolean = {
case "error" => setLevel(Level.Error, "error")
case "debug" => setLevel(Level.Debug, "debug")
case "info" => setLevel(Level.Info, "info")
case "warn" => setLevel(Level.Warn, "warn")
case cmd =>
if (cmd.nonEmpty) append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name))))
else false
}
private[sbt] def onFastTrackTask: String => Boolean = { s: String =>
private[sbt] def onCommandLine(cmd: String): Boolean =
cmd match {
case "error" => setLevel(Level.Error, "error")
case "debug" => setLevel(Level.Debug, "debug")
case "info" => setLevel(Level.Info, "info")
case "warn" => setLevel(Level.Warn, "warn")
case cmd =>
if (cmd.nonEmpty) appendExec(cmd, None)
else false
}
private[sbt] def onFastTrackTask(cmd: String): Boolean = {
fastTrack.synchronized(fastTrack.forEach { q =>
q.add(new FastTrackTask(this, s))
q.add(new FastTrackTask(this, cmd))
()
})
true

View File

@ -23,8 +23,16 @@ import java.text.DateFormat
import sbt.BasicCommandStrings.{ DashDashDetachStdio, DashDashServer, Shutdown, TerminateAction }
import sbt.internal.client.NetworkClient.Arguments
import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams }
import sbt.internal.worker.{ ClientJobParams, JvmRunInfo, NativeRunInfo, RunInfo }
import sbt.internal.protocol._
import sbt.internal.util.{ ConsoleAppender, ConsoleOut, Signals, Terminal, Util }
import sbt.internal.util.{
ConsoleAppender,
ConsoleOut,
MessageOnlyException,
Signals,
Terminal,
Util
}
import sbt.io.IO
import sbt.io.syntax._
import sbt.protocol._
@ -43,6 +51,7 @@ import Serialization.{
attach,
cancelReadSystemIn,
cancelRequest,
clientJob,
promptChannel,
readSystemIn,
systemIn,
@ -63,6 +72,7 @@ import Serialization.{
}
import NetworkClient.Arguments
import java.util.concurrent.TimeoutException
import sbt.util.Logger
trait ConsoleInterface {
def appendLog(level: Level.Value, message: => String): Unit
@ -166,6 +176,11 @@ class NetworkClient(
case null => inputThread.set(new RawInputThread)
case _ =>
}
private lazy val log: Logger = new Logger {
def trace(t: => Throwable): Unit = ()
def success(message: => String): Unit = ()
def log(level: Level.Value, message: => String): Unit = console.appendLog(level, message)
}
private[sbt] def connectOrStartServerAndConnect(
promptCompleteUsers: Boolean,
@ -295,7 +310,18 @@ class NetworkClient(
}
// initiate handshake
val execId = UUID.randomUUID.toString
val initCommand = InitCommand(tkn, Option(execId), Some(true))
val skipAnalysis = true
val opts = InitializeOption(
token = tkn,
skipAnalysis = Some(skipAnalysis),
canWork = Some(true),
)
val initCommand = InitCommand(
token = tkn, // duplicated with opts for compatibility
execId = Option(execId),
skipAnalysis = Some(skipAnalysis), // duplicated with opts for compatibility
initializationOptions = Some(opts),
)
conn.sendString(Serialization.serializeCommandAsJsonMessage(initCommand))
connectionHolder.set(conn)
conn
@ -641,6 +667,12 @@ class NetworkClient(
case Success(params) => splitDiagnostics(params); Vector()
case Failure(_) => Vector()
}
case (`clientJob`, Some(json)) =>
import sbt.internal.worker.codec.JsonProtocol._
Converter.fromJson[ClientJobParams](json) match {
case Success(params) => clientSideRun(params).get; Vector.empty
case Failure(_) => Vector.empty
}
case (`Shutdown`, Some(_)) => Vector.empty
case (msg, _) if msg.startsWith("build/") => Vector.empty
case _ =>
@ -687,6 +719,58 @@ class NetworkClient(
}
}
private def clientSideRun(params: ClientJobParams): Try[Unit] =
params.runInfo match {
case Some(info) => clientSideRun(info)
case _ => Failure(new MessageOnlyException(s"runInfo is not specified in $params"))
}
private def clientSideRun(runInfo: RunInfo): Try[Unit] = {
def jvmRun(info: JvmRunInfo): Try[Unit] = {
val option = ForkOptions(
javaHome = info.javaHome.map(new File(_)),
outputStrategy = None, // TODO: Handle buffered output etc
bootJars = Vector.empty,
workingDirectory = info.workingDirectory.map(new File(_)),
runJVMOptions = info.jvmOptions,
connectInput = info.connectInput,
envVars = info.environmentVariables,
)
// ForkRun handles exit code handling and cancellation
val runner = new ForkRun(option)
runner
.run(
mainClass = info.mainClass,
classpath = info.classpath.map(_.path).map(new File(_)),
options = info.args,
log = log
)
}
def nativeRun(info: NativeRunInfo): Try[Unit] = {
import java.lang.{ ProcessBuilder => JProcessBuilder }
val option = ForkOptions(
javaHome = None,
outputStrategy = None, // TODO: Handle buffered output etc
bootJars = Vector.empty,
workingDirectory = info.workingDirectory.map(new File(_)),
runJVMOptions = Vector.empty,
connectInput = info.connectInput,
envVars = info.environmentVariables,
)
val command = info.cmd :: info.args.toList
val jpb = new JProcessBuilder(command: _*)
val exitCode = try Fork.blockForExitCode(Fork.forkInternal(option, Nil, jpb))
catch {
case _: InterruptedException =>
log.warn("run canceled")
1
}
Run.processExitCode(exitCode, "runner")
}
if (runInfo.jvm) jvmRun(runInfo.jvmRunInfo.getOrElse(sys.error("missing jvmRunInfo")))
else nativeRun(runInfo.nativeRunInfo.getOrElse(sys.error("missing nativeRunInfo")))
}
def onRequest(msg: JsonRpcRequestMessage): Unit = {
import sbt.protocol.codec.JsonProtocol._
(msg.method, msg.params) match {

View File

@ -79,6 +79,7 @@ trait ServerCallback {
private[sbt] def authOptions: Set[ServerAuthentication]
private[sbt] def authenticate(token: String): Boolean
private[sbt] def setInitialized(value: Boolean): Unit
private[sbt] def setInitializeOption(opts: InitializeOption): Unit
private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit
private[sbt] def onCompletionRequest(execId: Option[String], cp: CP): Unit
private[sbt] def onCancellationRequest(execId: Option[String], crp: CRP): Unit

View File

@ -28,7 +28,7 @@ private[sbt] trait UITask extends Runnable with AutoCloseable {
private[sbt] val reader: UITask.Reader
private[this] final def handleInput(s: Either[String, String]): Boolean = s match {
case Left(m) => channel.onFastTrackTask(m)
case Right(cmd) => channel.onCommand(cmd)
case Right(cmd) => channel.onCommandLine(cmd)
}
private[this] val isStopped = new AtomicBoolean(false)
override def run(): Unit = {
@ -56,6 +56,20 @@ private[sbt] object UITask {
object Reader {
// Avoid filling the stack trace since it isn't helpful here
object interrupted extends InterruptedException
/**
* Return Left for fast track commands, otherwise return Right(...).
*/
def splitCommand(cmd: String): Either[String, String] =
// We need to put the empty string on the fast track queue so that we can
// reprompt the user if another command is running on the server.
if (cmd.isEmpty()) Left("")
else
cmd match {
case Shutdown | TerminateAction | Cancel => Left(cmd)
case cmd => Right(cmd)
}
def terminalReader(parser: Parser[_])(
terminal: Terminal,
state: State
@ -78,15 +92,8 @@ private[sbt] object UITask {
Right("") // should be unreachable
// JLine returns null on ctrl+d when there is no other input. This interprets
// ctrl+d with no imput as an exit
case None => Left(TerminateAction)
case Some(s: String) =>
s.trim() match {
// We need to put the empty string on the fast track queue so that we can
// reprompt the user if another command is running on the server.
case "" => Left("")
case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd)
case cmd => Right(cmd)
}
case None => Left(TerminateAction)
case Some(s: String) => splitCommand(s.trim())
}
}
terminal.setPrompt(Prompt.Pending)

View File

@ -70,6 +70,8 @@ abstract class BackgroundJobService extends Closeable {
def waitFor(job: JobHandle): Unit
private[sbt] def createWorkingDirectory: File
/** Copies classpath to temporary directories. */
def copyClasspath(products: Classpath, full: Classpath, workingDirectory: File): Classpath

View File

@ -51,6 +51,7 @@ import sbt.internal.server.{
BspCompileTask,
BuildServerProtocol,
BuildServerReporter,
ClientJob,
Definition,
LanguageServerProtocol,
ServerHandler,
@ -222,7 +223,7 @@ object Defaults extends BuildCommon {
closeClassLoaders :== SysProp.closeClassLoaders,
allowZombieClassLoaders :== true,
packageTimestamp :== Package.defaultTimestamp,
) ++ BuildServerProtocol.globalSettings
) ++ BuildServerProtocol.globalSettings ++ ClientJob.globalSettings
private[sbt] lazy val globalIvyCore: Seq[Setting[_]] =
Seq(
@ -2717,7 +2718,7 @@ object Defaults extends BuildCommon {
lazy val configSettings: Seq[Setting[_]] =
Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++
Classpaths.compilerPluginConfig ++ deprecationSettings ++
BuildServerProtocol.configSettings
BuildServerProtocol.configSettings ++ ClientJob.configSettings
lazy val compileSettings: Seq[Setting[_]] =
configSettings ++ (mainBgRunMainTask +: mainBgRunTask) ++ Classpaths.addUnmanagedLibrary

View File

@ -29,6 +29,7 @@ import sbt.internal.remotecache.RemoteCacheArtifact
import sbt.internal.server.BuildServerProtocol.BspFullWorkspace
import sbt.internal.server.{ BuildServerReporter, ServerHandler }
import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition }
import sbt.internal.worker.ClientJobParams
import sbt.io._
import sbt.librarymanagement.Configurations.CompilerPlugin
import sbt.librarymanagement.LibraryManagementCodec._
@ -437,6 +438,8 @@ object Keys {
val bspScalaMainClasses = inputKey[Unit]("Implementation of buildTarget/scalaMainClasses").withRank(DTask)
val bspScalaMainClassesItem = taskKey[ScalaMainClassesItem]("").withRank(DTask)
val bspReporter = taskKey[BuildServerReporter]("").withRank(DTask)
val clientJob = inputKey[ClientJobParams]("Translates a task into a job specification").withRank(Invisible)
val clientJobRunInfo = inputKey[ClientJobParams]("Translates the run task into a job specification").withRank(Invisible)
val useCoursier = settingKey[Boolean]("Use Coursier for dependency resolution.").withRank(BSetting)
val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting)

View File

@ -144,6 +144,16 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
override val isAutoCancel = false
}
private[sbt] def createWorkingDirectory: File = {
val id = nextId.getAndIncrement()
createWorkingDirectory(id)
}
private[sbt] def createWorkingDirectory(id: Long): File = {
val workingDir = serviceTempDir / s"job-$id"
IO.createDirectory(workingDir)
workingDir
}
def doRunInBackground(
spawningTask: ScopedKey[_],
state: State,
@ -153,8 +163,7 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
val extracted = Project.extract(state)
val logger =
LogManager.constructBackgroundLog(extracted.structure.data, state, context)(spawningTask)
val workingDir = serviceTempDir / s"job-$id"
IO.createDirectory(workingDir)
val workingDir = createWorkingDirectory(id)
val job = try {
new ThreadJobHandle(id, spawningTask, logger, workingDir, start(logger, workingDir))
} catch {

View File

@ -248,7 +248,7 @@ object BuildServerProtocol {
state.respondEvent(result)
}
}.evaluated,
bspScalaMainClasses / aggregate := false
bspScalaMainClasses / aggregate := false,
)
// This will be scoped to Compile, Test, IntegrationTest etc
@ -345,7 +345,7 @@ object BuildServerProtocol {
} else {
new BuildServerForwarder(meta, logger, underlying)
}
}
},
)
private[sbt] object Method {
final val Initialize = "build/initialize"

View File

@ -0,0 +1,94 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
package server
import java.io.File
import sbt.BuildSyntax._
import sbt.Def._
import sbt.Keys._
import sbt.SlashSyntax0._
import sbt.internal.util.complete.Parser
import sbt.internal.worker.{ ClientJobParams, FilePath, JvmRunInfo, RunInfo }
import sbt.io.IO
import sbt.protocol.Serialization
/**
* A ClientJob represents a unit of work that sbt server process
* can outsourse back to the client. Initially intended for sbtn client-side run.
*/
object ClientJob {
lazy val globalSettings: Seq[Def.Setting[_]] = Seq(
clientJob := clientJobTask.evaluated,
clientJob / aggregate := false,
)
private def clientJobTask: Def.Initialize[InputTask[ClientJobParams]] = Def.inputTaskDyn {
val tokens = spaceDelimited().parsed
val state = Keys.state.value
val p = Act.aggregatedKeyParser(state)
if (tokens.isEmpty) {
sys.error("expected an argument, for example foo/run")
}
val scopedKey = Parser.parse(tokens.head, p) match {
case Right(x :: Nil) => x
case Right(xs) => sys.error("too many keys")
case Left(err) => sys.error(err)
}
if (scopedKey.key == run.key)
clientJobRunInfo.in(scopedKey.scope).toTask(" " + tokens.tail.mkString(" "))
else sys.error(s"unsupported task for clientJob $scopedKey")
}
// This will be scoped to Compile, Test, etc
lazy val configSettings: Seq[Def.Setting[_]] = Seq(
clientJobRunInfo := clientJobRunInfoTask.evaluated,
)
private def clientJobRunInfoTask: Def.Initialize[InputTask[ClientJobParams]] = Def.inputTask {
val state = Keys.state.value
val args = spaceDelimited().parsed
val mainClass = (Keys.run / Keys.mainClass).value
val service = bgJobService.value
val fo = (Keys.run / Keys.forkOptions).value
val workingDir = service.createWorkingDirectory
val cp = service.copyClasspath(
exportedProductJars.value,
fullClasspathAsJars.value,
workingDir,
hashContents = true,
)
val strategy = fo.outputStrategy.map(_.getClass().getSimpleName().filter(_ != '$'))
// sbtn doesn't set java.home, so we need to do the fallback here
val javaHome =
fo.javaHome.map(IO.toURI).orElse(sys.props.get("java.home").map(x => IO.toURI(new File(x))))
val jvmRunInfo = JvmRunInfo(
args = args.toVector,
classpath = cp.map(x => IO.toURI(x.data)).map(FilePath(_, "")).toVector,
mainClass = mainClass.getOrElse(sys.error("no main class")),
connectInput = fo.connectInput,
javaHome = javaHome,
outputStrategy = strategy,
workingDirectory = fo.workingDirectory.map(IO.toURI),
jvmOptions = fo.runJVMOptions,
environmentVariables = fo.envVars.toMap,
)
val info = RunInfo(
jvm = true,
jvmRunInfo = jvmRunInfo,
)
val result = ClientJobParams(
runInfo = info
)
import sbt.internal.worker.codec.JsonProtocol._
state.notifyEvent(Serialization.clientJob, result)
result
}
}

View File

@ -62,6 +62,7 @@ private[sbt] object LanguageServerProtocol {
else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token")
} else ()
setInitialized(true)
setInitializeOption(opt)
if (!opt.skipAnalysis.getOrElse(false)) appendExec("collectAnalyses", None)
jsonRpcRespond(InitializeResult(serverCapabilities), Some(r.id))

View File

@ -24,6 +24,7 @@ import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import sbt.BasicCommandStrings.{ Shutdown, TerminateAction }
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes, LogMessageParams, MessageType }
import sbt.internal.protocol.{
InitializeOption,
JsonRpcNotificationMessage,
JsonRpcRequestMessage,
JsonRpcResponseError,
@ -83,6 +84,10 @@ final class NetworkChannel(
private val delimiter: Byte = '\n'.toByte
private val out = connection.getOutputStream
private var initialized = false
/** Reference to the client-side custom options
*/
private val initializeOption = new AtomicReference[InitializeOption](null)
private val pendingRequests: mutable.Map[String, JsonRpcRequestMessage] = mutable.Map()
private[this] val inputBuffer = new LinkedBlockingQueue[Int]()
@ -124,7 +129,7 @@ final class NetworkChannel(
self.jsonRpcNotify(method, params)
def appendExec(commandLine: String, execId: Option[String]): Boolean =
self.append(Exec(commandLine, execId, Some(CommandSource(name))))
self.appendExec(commandLine, execId)
def appendExec(exec: Exec): Boolean = self.append(exec)
@ -133,6 +138,8 @@ final class NetworkChannel(
private[sbt] def authOptions: Set[ServerAuthentication] = self.authOptions
private[sbt] def authenticate(token: String): Boolean = self.authenticate(token)
private[sbt] def setInitialized(value: Boolean): Unit = self.setInitialized(value)
private[sbt] def setInitializeOption(opts: InitializeOption): Unit =
self.setInitializeOption(opts)
private[sbt] def onSettingQuery(execId: Option[String], req: SettingQuery): Unit =
self.onSettingQuery(execId, req)
private[sbt] def onCompletionRequest(execId: Option[String], cp: CompletionParams): Unit =
@ -141,6 +148,30 @@ final class NetworkChannel(
self.onCancellationRequest(execId, crp)
}
// Take over commandline for network channel
private val networkCommand: PartialFunction[String, String] = {
case cmd if cmd.split(" ").head.split("/").last == "run" =>
s"clientJob $cmd"
}
override protected def appendExec(commandLine: String, execId: Option[String]): Boolean =
if (clientCanWork && networkCommand.isDefinedAt(commandLine))
super.appendExec(networkCommand(commandLine), execId)
else super.appendExec(commandLine, execId)
override private[sbt] def onCommandLine(cmd: String): Boolean =
if (clientCanWork && networkCommand.isDefinedAt(cmd))
appendExec(networkCommand(cmd), None)
else super.onCommandLine(cmd)
protected def setInitializeOption(opts: InitializeOption): Unit = initializeOption.set(opts)
// Returns true if sbtn has declared with canWork: true
protected def clientCanWork: Boolean =
Option(initializeOption.get) match {
case Some(opts) => opts.canWork.getOrElse(false)
case _ => false
}
protected def authenticate(token: String): Boolean = instance.authenticate(token)
protected def setInitialized(value: Boolean): Unit = initialized = value
@ -369,40 +400,6 @@ final class NetworkChannel(
try pendingWrites.put(event -> delimit)
catch { case _: InterruptedException => }
def onCommand(command: CommandMessage): Unit = command match {
case x: InitCommand => onInitCommand(x)
case x: ExecCommand => onExecCommand(x)
case x: SettingQuery => onSettingQuery(None, x)
}
private def onInitCommand(cmd: InitCommand): Unit = {
if (auth(ServerAuthentication.Token)) {
cmd.token match {
case Some(x) =>
authenticate(x) match {
case true =>
initialized = true
notifyEvent(ChannelAcceptedEvent(name))
case _ => sys.error("invalid token")
}
case None => sys.error("init command but without token.")
}
} else {
initialized = true
}
}
private def onExecCommand(cmd: ExecCommand) = {
if (initialized) {
append(
Exec(cmd.commandLine, cmd.execId orElse Some(Exec.newExecId), Some(CommandSource(name)))
)
()
} else {
log.warn(s"ignoring command $cmd before initialization")
}
}
protected def onSettingQuery(execId: Option[String], req: SettingQuery) = {
if (initialized) {
StandardMain.exchange.withState { s =>

View File

@ -4,24 +4,30 @@
// DO NOT EDIT MANUALLY
package sbt.internal.protocol
/**
* Passed into InitializeParams as part of "initialize" request as the user-defined option.
* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
*/
final class InitializeOption private (
val token: Option[String],
val skipAnalysis: Option[Boolean]) extends Serializable {
val skipAnalysis: Option[Boolean],
val canWork: Option[Boolean]) extends Serializable {
private def this(token: Option[String]) = this(token, None)
private def this(token: Option[String]) = this(token, None, None)
private def this(token: Option[String], skipAnalysis: Option[Boolean]) = this(token, skipAnalysis, None)
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: InitializeOption => (this.token == x.token) && (this.skipAnalysis == x.skipAnalysis)
case x: InitializeOption => (this.token == x.token) && (this.skipAnalysis == x.skipAnalysis) && (this.canWork == x.canWork)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.protocol.InitializeOption".##) + token.##) + skipAnalysis.##)
37 * (37 * (37 * (37 * (17 + "sbt.internal.protocol.InitializeOption".##) + token.##) + skipAnalysis.##) + canWork.##)
}
override def toString: String = {
"InitializeOption(" + token + ", " + skipAnalysis + ")"
"InitializeOption(" + token + ", " + skipAnalysis + ", " + canWork + ")"
}
private[this] def copy(token: Option[String] = token, skipAnalysis: Option[Boolean] = skipAnalysis): InitializeOption = {
new InitializeOption(token, skipAnalysis)
private[this] def copy(token: Option[String] = token, skipAnalysis: Option[Boolean] = skipAnalysis, canWork: Option[Boolean] = canWork): InitializeOption = {
new InitializeOption(token, skipAnalysis, canWork)
}
def withToken(token: Option[String]): InitializeOption = {
copy(token = token)
@ -35,6 +41,12 @@ final class InitializeOption private (
def withSkipAnalysis(skipAnalysis: Boolean): InitializeOption = {
copy(skipAnalysis = Option(skipAnalysis))
}
def withCanWork(canWork: Option[Boolean]): InitializeOption = {
copy(canWork = canWork)
}
def withCanWork(canWork: Boolean): InitializeOption = {
copy(canWork = Option(canWork))
}
}
object InitializeOption {
@ -42,4 +54,6 @@ object InitializeOption {
def apply(token: String): InitializeOption = new InitializeOption(Option(token))
def apply(token: Option[String], skipAnalysis: Option[Boolean]): InitializeOption = new InitializeOption(token, skipAnalysis)
def apply(token: String, skipAnalysis: Boolean): InitializeOption = new InitializeOption(Option(token), Option(skipAnalysis))
def apply(token: Option[String], skipAnalysis: Option[Boolean], canWork: Option[Boolean]): InitializeOption = new InitializeOption(token, skipAnalysis, canWork)
def apply(token: String, skipAnalysis: Boolean, canWork: Boolean): InitializeOption = new InitializeOption(Option(token), Option(skipAnalysis), Option(canWork))
}

View File

@ -13,8 +13,9 @@ implicit lazy val InitializeOptionFormat: JsonFormat[sbt.internal.protocol.Initi
unbuilder.beginObject(__js)
val token = unbuilder.readField[Option[String]]("token")
val skipAnalysis = unbuilder.readField[Option[Boolean]]("skipAnalysis")
val canWork = unbuilder.readField[Option[Boolean]]("canWork")
unbuilder.endObject()
sbt.internal.protocol.InitializeOption(token, skipAnalysis)
sbt.internal.protocol.InitializeOption(token, skipAnalysis, canWork)
case None =>
deserializationError("Expected JsObject but found None")
}
@ -23,6 +24,7 @@ implicit lazy val InitializeOptionFormat: JsonFormat[sbt.internal.protocol.Initi
builder.beginObject()
builder.addField("token", obj.token)
builder.addField("skipAnalysis", obj.skipAnalysis)
builder.addField("canWork", obj.canWork)
builder.endObject()
}
}

View File

@ -0,0 +1,45 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker
/**
* Client-side job support.
*
* Notification: sbt/clientJob
*
* Parameter for the sbt/clientJob notification.
* A client-side job represents a unit of work that sbt server
* can outsourse back to the client, for example for run task.
*/
final class ClientJobParams private (
val runInfo: Option[sbt.internal.worker.RunInfo]) extends Serializable {
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: ClientJobParams => (this.runInfo == x.runInfo)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (17 + "sbt.internal.worker.ClientJobParams".##) + runInfo.##)
}
override def toString: String = {
"ClientJobParams(" + runInfo + ")"
}
private[this] def copy(runInfo: Option[sbt.internal.worker.RunInfo] = runInfo): ClientJobParams = {
new ClientJobParams(runInfo)
}
def withRunInfo(runInfo: Option[sbt.internal.worker.RunInfo]): ClientJobParams = {
copy(runInfo = runInfo)
}
def withRunInfo(runInfo: sbt.internal.worker.RunInfo): ClientJobParams = {
copy(runInfo = Option(runInfo))
}
}
object ClientJobParams {
def apply(runInfo: Option[sbt.internal.worker.RunInfo]): ClientJobParams = new ClientJobParams(runInfo)
def apply(runInfo: sbt.internal.worker.RunInfo): ClientJobParams = new ClientJobParams(Option(runInfo))
}

View File

@ -0,0 +1,36 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker
final class FilePath private (
val path: java.net.URI,
val digest: String) extends Serializable {
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: FilePath => (this.path == x.path) && (this.digest == x.digest)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.worker.FilePath".##) + path.##) + digest.##)
}
override def toString: String = {
"FilePath(" + path + ", " + digest + ")"
}
private[this] def copy(path: java.net.URI = path, digest: String = digest): FilePath = {
new FilePath(path, digest)
}
def withPath(path: java.net.URI): FilePath = {
copy(path = path)
}
def withDigest(digest: String): FilePath = {
copy(digest = digest)
}
}
object FilePath {
def apply(path: java.net.URI, digest: String): FilePath = new FilePath(path, digest)
}

View File

@ -0,0 +1,84 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker
final class JvmRunInfo private (
val args: Vector[String],
val classpath: Vector[sbt.internal.worker.FilePath],
val mainClass: String,
val connectInput: Boolean,
val javaHome: Option[java.net.URI],
val outputStrategy: Option[String],
val workingDirectory: Option[java.net.URI],
val jvmOptions: Vector[String],
val environmentVariables: scala.collection.immutable.Map[String, String],
val inputs: Vector[sbt.internal.worker.FilePath],
val outputs: Vector[sbt.internal.worker.FilePath]) extends Serializable {
private def this(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: Option[java.net.URI], outputStrategy: Option[String], workingDirectory: Option[java.net.URI], jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String]) = this(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, Vector(), Vector())
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: JvmRunInfo => (this.args == x.args) && (this.classpath == x.classpath) && (this.mainClass == x.mainClass) && (this.connectInput == x.connectInput) && (this.javaHome == x.javaHome) && (this.outputStrategy == x.outputStrategy) && (this.workingDirectory == x.workingDirectory) && (this.jvmOptions == x.jvmOptions) && (this.environmentVariables == x.environmentVariables) && (this.inputs == x.inputs) && (this.outputs == x.outputs)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.worker.JvmRunInfo".##) + args.##) + classpath.##) + mainClass.##) + connectInput.##) + javaHome.##) + outputStrategy.##) + workingDirectory.##) + jvmOptions.##) + environmentVariables.##) + inputs.##) + outputs.##)
}
override def toString: String = {
"JvmRunInfo(" + args + ", " + classpath + ", " + mainClass + ", " + connectInput + ", " + javaHome + ", " + outputStrategy + ", " + workingDirectory + ", " + jvmOptions + ", " + environmentVariables + ", " + inputs + ", " + outputs + ")"
}
private[this] def copy(args: Vector[String] = args, classpath: Vector[sbt.internal.worker.FilePath] = classpath, mainClass: String = mainClass, connectInput: Boolean = connectInput, javaHome: Option[java.net.URI] = javaHome, outputStrategy: Option[String] = outputStrategy, workingDirectory: Option[java.net.URI] = workingDirectory, jvmOptions: Vector[String] = jvmOptions, environmentVariables: scala.collection.immutable.Map[String, String] = environmentVariables, inputs: Vector[sbt.internal.worker.FilePath] = inputs, outputs: Vector[sbt.internal.worker.FilePath] = outputs): JvmRunInfo = {
new JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, inputs, outputs)
}
def withArgs(args: Vector[String]): JvmRunInfo = {
copy(args = args)
}
def withClasspath(classpath: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = {
copy(classpath = classpath)
}
def withMainClass(mainClass: String): JvmRunInfo = {
copy(mainClass = mainClass)
}
def withConnectInput(connectInput: Boolean): JvmRunInfo = {
copy(connectInput = connectInput)
}
def withJavaHome(javaHome: Option[java.net.URI]): JvmRunInfo = {
copy(javaHome = javaHome)
}
def withJavaHome(javaHome: java.net.URI): JvmRunInfo = {
copy(javaHome = Option(javaHome))
}
def withOutputStrategy(outputStrategy: Option[String]): JvmRunInfo = {
copy(outputStrategy = outputStrategy)
}
def withOutputStrategy(outputStrategy: String): JvmRunInfo = {
copy(outputStrategy = Option(outputStrategy))
}
def withWorkingDirectory(workingDirectory: Option[java.net.URI]): JvmRunInfo = {
copy(workingDirectory = workingDirectory)
}
def withWorkingDirectory(workingDirectory: java.net.URI): JvmRunInfo = {
copy(workingDirectory = Option(workingDirectory))
}
def withJvmOptions(jvmOptions: Vector[String]): JvmRunInfo = {
copy(jvmOptions = jvmOptions)
}
def withEnvironmentVariables(environmentVariables: scala.collection.immutable.Map[String, String]): JvmRunInfo = {
copy(environmentVariables = environmentVariables)
}
def withInputs(inputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = {
copy(inputs = inputs)
}
def withOutputs(outputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = {
copy(outputs = outputs)
}
}
object JvmRunInfo {
def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: Option[java.net.URI], outputStrategy: Option[String], workingDirectory: Option[java.net.URI], jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables)
def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: java.net.URI, outputStrategy: String, workingDirectory: java.net.URI, jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, Option(javaHome), Option(outputStrategy), Option(workingDirectory), jvmOptions, environmentVariables)
def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: Option[java.net.URI], outputStrategy: Option[String], workingDirectory: Option[java.net.URI], jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, inputs, outputs)
def apply(args: Vector[String], classpath: Vector[sbt.internal.worker.FilePath], mainClass: String, connectInput: Boolean, javaHome: java.net.URI, outputStrategy: String, workingDirectory: java.net.URI, jvmOptions: Vector[String], environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): JvmRunInfo = new JvmRunInfo(args, classpath, mainClass, connectInput, Option(javaHome), Option(outputStrategy), Option(workingDirectory), jvmOptions, environmentVariables, inputs, outputs)
}

View File

@ -0,0 +1,69 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker
final class NativeRunInfo private (
val cmd: String,
val args: Vector[String],
val connectInput: Boolean,
val outputStrategy: Option[String],
val workingDirectory: Option[java.net.URI],
val environmentVariables: scala.collection.immutable.Map[String, String],
val inputs: Vector[sbt.internal.worker.FilePath],
val outputs: Vector[sbt.internal.worker.FilePath]) extends Serializable {
private def this(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: Option[String], workingDirectory: Option[java.net.URI], environmentVariables: scala.collection.immutable.Map[String, String]) = this(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, Vector(), Vector())
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: NativeRunInfo => (this.cmd == x.cmd) && (this.args == x.args) && (this.connectInput == x.connectInput) && (this.outputStrategy == x.outputStrategy) && (this.workingDirectory == x.workingDirectory) && (this.environmentVariables == x.environmentVariables) && (this.inputs == x.inputs) && (this.outputs == x.outputs)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.worker.NativeRunInfo".##) + cmd.##) + args.##) + connectInput.##) + outputStrategy.##) + workingDirectory.##) + environmentVariables.##) + inputs.##) + outputs.##)
}
override def toString: String = {
"NativeRunInfo(" + cmd + ", " + args + ", " + connectInput + ", " + outputStrategy + ", " + workingDirectory + ", " + environmentVariables + ", " + inputs + ", " + outputs + ")"
}
private[this] def copy(cmd: String = cmd, args: Vector[String] = args, connectInput: Boolean = connectInput, outputStrategy: Option[String] = outputStrategy, workingDirectory: Option[java.net.URI] = workingDirectory, environmentVariables: scala.collection.immutable.Map[String, String] = environmentVariables, inputs: Vector[sbt.internal.worker.FilePath] = inputs, outputs: Vector[sbt.internal.worker.FilePath] = outputs): NativeRunInfo = {
new NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, inputs, outputs)
}
def withCmd(cmd: String): NativeRunInfo = {
copy(cmd = cmd)
}
def withArgs(args: Vector[String]): NativeRunInfo = {
copy(args = args)
}
def withConnectInput(connectInput: Boolean): NativeRunInfo = {
copy(connectInput = connectInput)
}
def withOutputStrategy(outputStrategy: Option[String]): NativeRunInfo = {
copy(outputStrategy = outputStrategy)
}
def withOutputStrategy(outputStrategy: String): NativeRunInfo = {
copy(outputStrategy = Option(outputStrategy))
}
def withWorkingDirectory(workingDirectory: Option[java.net.URI]): NativeRunInfo = {
copy(workingDirectory = workingDirectory)
}
def withWorkingDirectory(workingDirectory: java.net.URI): NativeRunInfo = {
copy(workingDirectory = Option(workingDirectory))
}
def withEnvironmentVariables(environmentVariables: scala.collection.immutable.Map[String, String]): NativeRunInfo = {
copy(environmentVariables = environmentVariables)
}
def withInputs(inputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = {
copy(inputs = inputs)
}
def withOutputs(outputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = {
copy(outputs = outputs)
}
}
object NativeRunInfo {
def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: Option[String], workingDirectory: Option[java.net.URI], environmentVariables: scala.collection.immutable.Map[String, String]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables)
def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: String, workingDirectory: java.net.URI, environmentVariables: scala.collection.immutable.Map[String, String]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, Option(outputStrategy), Option(workingDirectory), environmentVariables)
def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: Option[String], workingDirectory: Option[java.net.URI], environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, inputs, outputs)
def apply(cmd: String, args: Vector[String], connectInput: Boolean, outputStrategy: String, workingDirectory: java.net.URI, environmentVariables: scala.collection.immutable.Map[String, String], inputs: Vector[sbt.internal.worker.FilePath], outputs: Vector[sbt.internal.worker.FilePath]): NativeRunInfo = new NativeRunInfo(cmd, args, connectInput, Option(outputStrategy), Option(workingDirectory), environmentVariables, inputs, outputs)
}

View File

@ -0,0 +1,49 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker
final class RunInfo private (
val jvm: Boolean,
val jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo],
val nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo]) extends Serializable {
private def this(jvm: Boolean, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo]) = this(jvm, jvmRunInfo, None)
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: RunInfo => (this.jvm == x.jvm) && (this.jvmRunInfo == x.jvmRunInfo) && (this.nativeRunInfo == x.nativeRunInfo)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (37 * (17 + "sbt.internal.worker.RunInfo".##) + jvm.##) + jvmRunInfo.##) + nativeRunInfo.##)
}
override def toString: String = {
"RunInfo(" + jvm + ", " + jvmRunInfo + ", " + nativeRunInfo + ")"
}
private[this] def copy(jvm: Boolean = jvm, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo] = jvmRunInfo, nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo] = nativeRunInfo): RunInfo = {
new RunInfo(jvm, jvmRunInfo, nativeRunInfo)
}
def withJvm(jvm: Boolean): RunInfo = {
copy(jvm = jvm)
}
def withJvmRunInfo(jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo]): RunInfo = {
copy(jvmRunInfo = jvmRunInfo)
}
def withJvmRunInfo(jvmRunInfo: sbt.internal.worker.JvmRunInfo): RunInfo = {
copy(jvmRunInfo = Option(jvmRunInfo))
}
def withNativeRunInfo(nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo]): RunInfo = {
copy(nativeRunInfo = nativeRunInfo)
}
def withNativeRunInfo(nativeRunInfo: sbt.internal.worker.NativeRunInfo): RunInfo = {
copy(nativeRunInfo = Option(nativeRunInfo))
}
}
object RunInfo {
def apply(jvm: Boolean, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo]): RunInfo = new RunInfo(jvm, jvmRunInfo)
def apply(jvm: Boolean, jvmRunInfo: sbt.internal.worker.JvmRunInfo): RunInfo = new RunInfo(jvm, Option(jvmRunInfo))
def apply(jvm: Boolean, jvmRunInfo: Option[sbt.internal.worker.JvmRunInfo], nativeRunInfo: Option[sbt.internal.worker.NativeRunInfo]): RunInfo = new RunInfo(jvm, jvmRunInfo, nativeRunInfo)
def apply(jvm: Boolean, jvmRunInfo: sbt.internal.worker.JvmRunInfo, nativeRunInfo: sbt.internal.worker.NativeRunInfo): RunInfo = new RunInfo(jvm, Option(jvmRunInfo), Option(nativeRunInfo))
}

View File

@ -0,0 +1,27 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait ClientJobParamsFormats { self: sbt.internal.worker.codec.RunInfoFormats with sbt.internal.worker.codec.JvmRunInfoFormats with sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol with sbt.internal.worker.codec.NativeRunInfoFormats =>
implicit lazy val ClientJobParamsFormat: JsonFormat[sbt.internal.worker.ClientJobParams] = new JsonFormat[sbt.internal.worker.ClientJobParams] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.ClientJobParams = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val runInfo = unbuilder.readField[Option[sbt.internal.worker.RunInfo]]("runInfo")
unbuilder.endObject()
sbt.internal.worker.ClientJobParams(runInfo)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.worker.ClientJobParams, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("runInfo", obj.runInfo)
builder.endObject()
}
}
}

View File

@ -0,0 +1,29 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait FilePathFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val FilePathFormat: JsonFormat[sbt.internal.worker.FilePath] = new JsonFormat[sbt.internal.worker.FilePath] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.FilePath = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val path = unbuilder.readField[java.net.URI]("path")
val digest = unbuilder.readField[String]("digest")
unbuilder.endObject()
sbt.internal.worker.FilePath(path, digest)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.worker.FilePath, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("path", obj.path)
builder.addField("digest", obj.digest)
builder.endObject()
}
}
}

View File

@ -0,0 +1,13 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker.codec
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.internal.worker.codec.FilePathFormats
with sbt.internal.worker.codec.JvmRunInfoFormats
with sbt.internal.worker.codec.NativeRunInfoFormats
with sbt.internal.worker.codec.RunInfoFormats
with sbt.internal.worker.codec.ClientJobParamsFormats
object JsonProtocol extends JsonProtocol

View File

@ -0,0 +1,47 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait JvmRunInfoFormats { self: sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val JvmRunInfoFormat: JsonFormat[sbt.internal.worker.JvmRunInfo] = new JsonFormat[sbt.internal.worker.JvmRunInfo] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.JvmRunInfo = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val args = unbuilder.readField[Vector[String]]("args")
val classpath = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("classpath")
val mainClass = unbuilder.readField[String]("mainClass")
val connectInput = unbuilder.readField[Boolean]("connectInput")
val javaHome = unbuilder.readField[Option[java.net.URI]]("javaHome")
val outputStrategy = unbuilder.readField[Option[String]]("outputStrategy")
val workingDirectory = unbuilder.readField[Option[java.net.URI]]("workingDirectory")
val jvmOptions = unbuilder.readField[Vector[String]]("jvmOptions")
val environmentVariables = unbuilder.readField[scala.collection.immutable.Map[String, String]]("environmentVariables")
val inputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("inputs")
val outputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("outputs")
unbuilder.endObject()
sbt.internal.worker.JvmRunInfo(args, classpath, mainClass, connectInput, javaHome, outputStrategy, workingDirectory, jvmOptions, environmentVariables, inputs, outputs)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.worker.JvmRunInfo, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("args", obj.args)
builder.addField("classpath", obj.classpath)
builder.addField("mainClass", obj.mainClass)
builder.addField("connectInput", obj.connectInput)
builder.addField("javaHome", obj.javaHome)
builder.addField("outputStrategy", obj.outputStrategy)
builder.addField("workingDirectory", obj.workingDirectory)
builder.addField("jvmOptions", obj.jvmOptions)
builder.addField("environmentVariables", obj.environmentVariables)
builder.addField("inputs", obj.inputs)
builder.addField("outputs", obj.outputs)
builder.endObject()
}
}
}

View File

@ -0,0 +1,41 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait NativeRunInfoFormats { self: sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val NativeRunInfoFormat: JsonFormat[sbt.internal.worker.NativeRunInfo] = new JsonFormat[sbt.internal.worker.NativeRunInfo] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.NativeRunInfo = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val cmd = unbuilder.readField[String]("cmd")
val args = unbuilder.readField[Vector[String]]("args")
val connectInput = unbuilder.readField[Boolean]("connectInput")
val outputStrategy = unbuilder.readField[Option[String]]("outputStrategy")
val workingDirectory = unbuilder.readField[Option[java.net.URI]]("workingDirectory")
val environmentVariables = unbuilder.readField[scala.collection.immutable.Map[String, String]]("environmentVariables")
val inputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("inputs")
val outputs = unbuilder.readField[Vector[sbt.internal.worker.FilePath]]("outputs")
unbuilder.endObject()
sbt.internal.worker.NativeRunInfo(cmd, args, connectInput, outputStrategy, workingDirectory, environmentVariables, inputs, outputs)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.worker.NativeRunInfo, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("cmd", obj.cmd)
builder.addField("args", obj.args)
builder.addField("connectInput", obj.connectInput)
builder.addField("outputStrategy", obj.outputStrategy)
builder.addField("workingDirectory", obj.workingDirectory)
builder.addField("environmentVariables", obj.environmentVariables)
builder.addField("inputs", obj.inputs)
builder.addField("outputs", obj.outputs)
builder.endObject()
}
}
}

View File

@ -0,0 +1,31 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.worker.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait RunInfoFormats { self: sbt.internal.worker.codec.JvmRunInfoFormats with sbt.internal.worker.codec.FilePathFormats with sjsonnew.BasicJsonProtocol with sbt.internal.worker.codec.NativeRunInfoFormats =>
implicit lazy val RunInfoFormat: JsonFormat[sbt.internal.worker.RunInfo] = new JsonFormat[sbt.internal.worker.RunInfo] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.worker.RunInfo = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val jvm = unbuilder.readField[Boolean]("jvm")
val jvmRunInfo = unbuilder.readField[Option[sbt.internal.worker.JvmRunInfo]]("jvmRunInfo")
val nativeRunInfo = unbuilder.readField[Option[sbt.internal.worker.NativeRunInfo]]("nativeRunInfo")
unbuilder.endObject()
sbt.internal.worker.RunInfo(jvm, jvmRunInfo, nativeRunInfo)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.worker.RunInfo, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("jvm", obj.jvm)
builder.addField("jvmRunInfo", obj.jvmRunInfo)
builder.addField("nativeRunInfo", obj.nativeRunInfo)
builder.endObject()
}
}
}

View File

@ -7,22 +7,24 @@ package sbt.protocol
final class InitCommand private (
val token: Option[String],
val execId: Option[String],
val skipAnalysis: Option[Boolean]) extends sbt.protocol.CommandMessage() with Serializable {
val skipAnalysis: Option[Boolean],
val initializationOptions: Option[sbt.internal.protocol.InitializeOption]) extends sbt.protocol.CommandMessage() with Serializable {
private def this(token: Option[String], execId: Option[String]) = this(token, execId, None)
private def this(token: Option[String], execId: Option[String]) = this(token, execId, None, None)
private def this(token: Option[String], execId: Option[String], skipAnalysis: Option[Boolean]) = this(token, execId, skipAnalysis, None)
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: InitCommand => (this.token == x.token) && (this.execId == x.execId) && (this.skipAnalysis == x.skipAnalysis)
case x: InitCommand => (this.token == x.token) && (this.execId == x.execId) && (this.skipAnalysis == x.skipAnalysis) && (this.initializationOptions == x.initializationOptions)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (37 * (17 + "sbt.protocol.InitCommand".##) + token.##) + execId.##) + skipAnalysis.##)
37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.InitCommand".##) + token.##) + execId.##) + skipAnalysis.##) + initializationOptions.##)
}
override def toString: String = {
"InitCommand(" + token + ", " + execId + ", " + skipAnalysis + ")"
"InitCommand(" + token + ", " + execId + ", " + skipAnalysis + ", " + initializationOptions + ")"
}
private[this] def copy(token: Option[String] = token, execId: Option[String] = execId, skipAnalysis: Option[Boolean] = skipAnalysis): InitCommand = {
new InitCommand(token, execId, skipAnalysis)
private[this] def copy(token: Option[String] = token, execId: Option[String] = execId, skipAnalysis: Option[Boolean] = skipAnalysis, initializationOptions: Option[sbt.internal.protocol.InitializeOption] = initializationOptions): InitCommand = {
new InitCommand(token, execId, skipAnalysis, initializationOptions)
}
def withToken(token: Option[String]): InitCommand = {
copy(token = token)
@ -42,6 +44,12 @@ final class InitCommand private (
def withSkipAnalysis(skipAnalysis: Boolean): InitCommand = {
copy(skipAnalysis = Option(skipAnalysis))
}
def withInitializationOptions(initializationOptions: Option[sbt.internal.protocol.InitializeOption]): InitCommand = {
copy(initializationOptions = initializationOptions)
}
def withInitializationOptions(initializationOptions: sbt.internal.protocol.InitializeOption): InitCommand = {
copy(initializationOptions = Option(initializationOptions))
}
}
object InitCommand {
@ -49,4 +57,6 @@ object InitCommand {
def apply(token: String, execId: String): InitCommand = new InitCommand(Option(token), Option(execId))
def apply(token: Option[String], execId: Option[String], skipAnalysis: Option[Boolean]): InitCommand = new InitCommand(token, execId, skipAnalysis)
def apply(token: String, execId: String, skipAnalysis: Boolean): InitCommand = new InitCommand(Option(token), Option(execId), Option(skipAnalysis))
def apply(token: Option[String], execId: Option[String], skipAnalysis: Option[Boolean], initializationOptions: Option[sbt.internal.protocol.InitializeOption]): InitCommand = new InitCommand(token, execId, skipAnalysis, initializationOptions)
def apply(token: String, execId: String, skipAnalysis: Boolean, initializationOptions: sbt.internal.protocol.InitializeOption): InitCommand = new InitCommand(Option(token), Option(execId), Option(skipAnalysis), Option(initializationOptions))
}

View File

@ -6,6 +6,6 @@
package sbt.protocol.codec
import _root_.sjsonnew.JsonFormat
trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats with sbt.protocol.codec.TerminalSetEchoCommandFormats with sbt.protocol.codec.TerminalSetRawModeCommandFormats =>
trait CommandMessageFormats { self: sbt.internal.protocol.codec.InitializeOptionFormats with sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats with sbt.protocol.codec.TerminalSetEchoCommandFormats with sbt.protocol.codec.TerminalSetRawModeCommandFormats =>
implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat11[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery, sbt.protocol.TerminalSetAttributesCommand, sbt.protocol.TerminalAttributesQuery, sbt.protocol.TerminalGetSizeQuery, sbt.protocol.TerminalSetSizeCommand, sbt.protocol.TerminalSetEchoCommand, sbt.protocol.TerminalSetRawModeCommand]("type")
}

View File

@ -5,7 +5,7 @@
// DO NOT EDIT MANUALLY
package sbt.protocol.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait InitCommandFormats { self: sjsonnew.BasicJsonProtocol =>
trait InitCommandFormats { self: sbt.internal.protocol.codec.InitializeOptionFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val InitCommandFormat: JsonFormat[sbt.protocol.InitCommand] = new JsonFormat[sbt.protocol.InitCommand] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.InitCommand = {
__jsOpt match {
@ -14,8 +14,9 @@ implicit lazy val InitCommandFormat: JsonFormat[sbt.protocol.InitCommand] = new
val token = unbuilder.readField[Option[String]]("token")
val execId = unbuilder.readField[Option[String]]("execId")
val skipAnalysis = unbuilder.readField[Option[Boolean]]("skipAnalysis")
val initializationOptions = unbuilder.readField[Option[sbt.internal.protocol.InitializeOption]]("initializationOptions")
unbuilder.endObject()
sbt.protocol.InitCommand(token, execId, skipAnalysis)
sbt.protocol.InitCommand(token, execId, skipAnalysis, initializationOptions)
case None =>
deserializationError("Expected JsObject but found None")
}
@ -25,6 +26,7 @@ implicit lazy val InitCommandFormat: JsonFormat[sbt.protocol.InitCommand] = new
builder.addField("token", obj.token)
builder.addField("execId", obj.execId)
builder.addField("skipAnalysis", obj.skipAnalysis)
builder.addField("initializationOptions", obj.initializationOptions)
builder.endObject()
}
}

View File

@ -4,7 +4,8 @@
// DO NOT EDIT MANUALLY
package sbt.protocol.codec
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
trait JsonProtocol extends sbt.internal.protocol.codec.InitializeOptionFormats
with sjsonnew.BasicJsonProtocol
with sbt.protocol.codec.InitCommandFormats
with sbt.protocol.codec.ExecCommandFormats
with sbt.protocol.codec.SettingQueryFormats

View File

@ -16,7 +16,10 @@ type TokenFile {
token: String!
}
## Passed into InitializeParams as part of "initialize" request as the user-defined option.
## https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
type InitializeOption {
token: String
skipAnalysis: Boolean @since("1.4.0")
canWork: Boolean @since("1.10.8")
}

View File

@ -11,6 +11,7 @@ type InitCommand implements CommandMessage {
token: String
execId: String
skipAnalysis: Boolean @since("1.4.0")
initializationOptions: sbt.internal.protocol.InitializeOption @since("1.10.8")
}
## Command to execute sbt command.

View File

@ -0,0 +1,51 @@
package sbt.internal.worker
@target(Scala)
@codecPackage("sbt.internal.worker.codec")
@fullCodec("JsonProtocol")
type FilePath {
path: java.net.URI!
digest: String!
}
type JvmRunInfo {
args: [String],
classpath: [sbt.internal.worker.FilePath],
mainClass: String!
connectInput: Boolean!
javaHome: java.net.URI
outputStrategy: String
workingDirectory: java.net.URI
jvmOptions: [String]
environmentVariables: StringStringMap!
inputs: [sbt.internal.worker.FilePath] @since("0.1.0"),
outputs: [sbt.internal.worker.FilePath] @since("0.1.0"),
}
type NativeRunInfo {
cmd: String!,
args: [String],
connectInput: Boolean!
outputStrategy: String
workingDirectory: java.net.URI
environmentVariables: StringStringMap!
inputs: [sbt.internal.worker.FilePath] @since("0.1.0"),
outputs: [sbt.internal.worker.FilePath] @since("0.1.0"),
}
type RunInfo {
jvm: Boolean!
jvmRunInfo: sbt.internal.worker.JvmRunInfo,
nativeRunInfo: sbt.internal.worker.NativeRunInfo @since("0.1.0"),
}
## Client-side job support.
##
## Notification: sbt/clientJob
##
## Parameter for the sbt/clientJob notification.
## A client-side job represents a unit of work that sbt server
## can outsourse back to the client, for example for run task.
type ClientJobParams {
runInfo: sbt.internal.worker.RunInfo
}

View File

@ -27,6 +27,7 @@ object Serialization {
private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8"
val readSystemIn = "sbt/readSystemIn"
val cancelReadSystemIn = "sbt/cancelReadSystemIn"
val clientJob = "sbt/clientJob"
val systemIn = "sbt/systemIn"
val systemOut = "sbt/systemOut"
val systemErr = "sbt/systemErr"
@ -67,15 +68,10 @@ object Serialization {
command match {
case x: InitCommand =>
val execId = x.execId.getOrElse(UUID.randomUUID.toString)
val analysis = s""""skipAnalysis" : ${x.skipAnalysis.getOrElse(false)}"""
val opt = x.token match {
case Some(t) =>
val json: JValue = Converter.toJson[String](t).get
val v = CompactPrinter(json)
s"""{ "token": $v, $analysis }"""
case None => s"{ $analysis }"
}
s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "initialize", "params": { "initializationOptions": $opt } }"""
val opts = x.initializationOptions.getOrElse(sys.error("expected initializationOptions"))
import sbt.protocol.codec.JsonProtocol._
val optsJson = CompactPrinter(Converter.toJson(opts).get)
s"""{ "jsonrpc": "2.0", "id": "$execId", "method": "initialize", "params": { "initializationOptions": $optsJson } }"""
case x: ExecCommand =>
val execId = x.execId.getOrElse(UUID.randomUUID.toString)
val json: JValue = Converter.toJson[String](x.commandLine).get

View File

@ -33,15 +33,8 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
* It is configured according to `config`.
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command.
*/
def apply(config: ForkOptions, arguments: Seq[String]): Int = {
val p = fork(config, arguments)
RunningProcesses.add(p)
try p.exitValue()
finally {
if (p.isAlive()) p.destroy()
RunningProcesses.remove(p)
}
}
def apply(config: ForkOptions, arguments: Seq[String]): Int =
Fork.blockForExitCode(fork(config, arguments))
/**
* Forks the configured process and returns a `Process` that can be used to wait for completion or to terminate the forked process.
@ -50,37 +43,22 @@ final class Fork(val commandName: String, val runnerClass: Option[String]) {
* If `runnerClass` is defined for this Fork instance, it is prepended to `arguments` to define the arguments passed to the forked command.
*/
def fork(config: ForkOptions, arguments: Seq[String]): Process = {
import config.{ envVars => env, _ }
import config._
val executable = Fork.javaCommand(javaHome, commandName).getAbsolutePath
val preOptions = makeOptions(runJVMOptions, bootJars, arguments)
val (classpathEnv, options) = Fork.fitClasspath(preOptions)
val command = executable +: options
val environment: List[(String, String)] = env.toList ++
(classpathEnv map { value =>
Fork.ClasspathEnvKey -> value
})
val jpb =
if (Fork.shouldUseArgumentsFile(options))
new JProcessBuilder(executable, Fork.createArgumentsFile(options))
else
new JProcessBuilder(command.toArray: _*)
workingDirectory foreach (jpb directory _)
environment foreach { case (k, v) => jpb.environment.put(k, v) }
if (connectInput) {
jpb.redirectInput(Redirect.INHERIT)
()
}
val process = Process(jpb)
outputStrategy.getOrElse(StdoutOutput: OutputStrategy) match {
case StdoutOutput => process.run(connectInput = false)
case out: BufferedOutput =>
out.logger.buffer { process.run(out.logger, connectInput = false) }
case out: LoggedOutput => process.run(out.logger, connectInput = false)
case out: CustomOutput => (process #> out.output).run(connectInput = false)
val extraEnv = classpathEnv.toList.map { value =>
Fork.ClasspathEnvKey -> value
}
Fork.forkInternal(config, extraEnv, jpb)
}
private[this] def makeOptions(
jvmOptions: Seq[String],
bootJars: Iterable[File],
@ -185,4 +163,36 @@ object Fork {
pw.close()
s"@${file.getAbsolutePath}"
}
private[sbt] def forkInternal(
config: ForkOptions,
extraEnv: List[(String, String)],
jpb: JProcessBuilder
): Process = {
import config.{ envVars => env, _ }
val environment: List[(String, String)] = env.toList ++ extraEnv
workingDirectory.foreach(jpb directory _)
environment.foreach { case (k, v) => jpb.environment.put(k, v) }
if (connectInput) {
jpb.redirectInput(Redirect.INHERIT)
()
}
val process = Process(jpb)
outputStrategy.getOrElse(StdoutOutput: OutputStrategy) match {
case StdoutOutput => process.run(connectInput = false)
case out: BufferedOutput =>
out.logger.buffer { process.run(out.logger, connectInput = false) }
case out: LoggedOutput => process.run(out.logger, connectInput = false)
case out: CustomOutput => (process #> out.output).run(connectInput = false)
}
}
private[sbt] def blockForExitCode(p: Process): Int = {
RunningProcesses.add(p)
try p.exitValue()
finally {
if (p.isAlive()) p.destroy()
RunningProcesses.remove(p)
}
}
}

View File

@ -26,25 +26,16 @@ sealed trait ScalaRun {
}
class ForkRun(config: ForkOptions) extends ScalaRun {
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] = {
def processExitCode(exitCode: Int, label: String): Try[Unit] =
if (exitCode == 0) Success(())
else
Failure(
new MessageOnlyException(
s"""Nonzero exit code returned from $label: $exitCode""".stripMargin
)
)
log.info(s"running (fork) $mainClass ${Run.runOptionsStr(options)}")
val c = configLogged(log)
val scalaOpts = scalaOptions(mainClass, classpath, options)
val exitCode = try Fork.java(c, scalaOpts)
catch {
case _: InterruptedException =>
log.warn("Run canceled.")
log.warn("run canceled")
1
}
processExitCode(exitCode, "runner")
Run.processExitCode(exitCode, "runner")
}
def fork(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Process = {
@ -195,4 +186,13 @@ object Run {
case str if str.contains(" ") => "\"" + str + "\""
case str => str
}).mkString(" ")
private[sbt] def processExitCode(exitCode: Int, label: String): Try[Unit] =
if (exitCode == 0) Success(())
else
Failure(
new MessageOnlyException(
s"""nonzero exit code returned from $label: $exitCode""".stripMargin
)
)
}

View File

@ -1,7 +1,9 @@
scalaVersion := "3.6.3"
TaskKey[Unit]("willSucceed") := println("success")
TaskKey[Unit]("willFail") := { throw new Exception("failed") }
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test"
libraryDependencies += "org.scalameta" %% "munit" % "1.0.4" % Test
TaskKey[Unit]("fooBar") := { () }

View File

@ -1 +1,3 @@
object A
class A
@main def hello() = println("Hello, World!")

View File

@ -1,3 +1,3 @@
package test.pkg
class FooSpec extends org.scalatest.FlatSpec
class FooSpec extends munit.FunSuite

View File

@ -57,7 +57,7 @@ object ClientTest extends AbstractServerTest {
case r => r
}
}
private def client(args: String*): Int = {
private def client(args: String*): Int =
background(
NetworkClient.client(
testPath.toFile,
@ -68,6 +68,19 @@ object ClientTest extends AbstractServerTest {
false
)
)
private def clientWithStdoutLines(args: String*): (Int, Seq[String]) = {
val out = new CachingPrintStream
val exitCode = background(
NetworkClient.client(
testPath.toFile,
args.toArray,
NullInputStream,
out,
NullPrintStream,
false
)
)
(exitCode, out.lines)
}
// This ensures that the completion command will send a tab that triggers
// sbt to call definedTestNames or discoveredMainClasses if there hasn't
@ -107,6 +120,11 @@ object ClientTest extends AbstractServerTest {
test("three commands with middle failure") { _ =>
assert(client("compile;willFail;willSucceed") == 1)
}
test("run") { _ =>
val (exitCode, lines) = clientWithStdoutLines("run")
assert(exitCode == 0)
assert(lines.toList.exists(_.endsWith("Hello, World!")))
}
test("compi completions") { _ =>
val expected = Vector(
"compile",

View File

@ -220,7 +220,7 @@ case class TestServer(
// initiate handshake
sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "skipAnalysis": true } } }"""
s"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "skipAnalysis": true, "canWork": true } } }"""
)
def test(f: TestServer => Future[Assertion]): Future[Assertion] = {