diff --git a/build.sbt b/build.sbt index eebbfdd3c..c2b7db293 100644 --- a/build.sbt +++ b/build.sbt @@ -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( diff --git a/main-command/src/main/scala/sbt/internal/CommandChannel.scala b/main-command/src/main/scala/sbt/internal/CommandChannel.scala index db53b9558..3e0d7a518 100644 --- a/main-command/src/main/scala/sbt/internal/CommandChannel.scala +++ b/main-command/src/main/scala/sbt/internal/CommandChannel.scala @@ -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 diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 160291f31..3eea88c11 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -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 { diff --git a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala index b503e503c..9a20f8253 100644 --- a/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala +++ b/main-command/src/main/scala/sbt/internal/server/ServerHandler.scala @@ -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 diff --git a/main-command/src/main/scala/sbt/internal/ui/UITask.scala b/main-command/src/main/scala/sbt/internal/ui/UITask.scala index 6e9fcd1be..b4ceb047d 100644 --- a/main-command/src/main/scala/sbt/internal/ui/UITask.scala +++ b/main-command/src/main/scala/sbt/internal/ui/UITask.scala @@ -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) diff --git a/main/src/main/scala/sbt/BackgroundJobService.scala b/main/src/main/scala/sbt/BackgroundJobService.scala index 62aaec76d..2b113c1c1 100644 --- a/main/src/main/scala/sbt/BackgroundJobService.scala +++ b/main/src/main/scala/sbt/BackgroundJobService.scala @@ -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 diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index fa9b6f509..f3a36453c 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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 diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 94b1f7def..dd290c3db 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -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) diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index 218cb3289..232204d46 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -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 { diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index b063cfa28..9bb371a78 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -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" diff --git a/main/src/main/scala/sbt/internal/server/ClientJob.scala b/main/src/main/scala/sbt/internal/server/ClientJob.scala new file mode 100644 index 000000000..e016b98f0 --- /dev/null +++ b/main/src/main/scala/sbt/internal/server/ClientJob.scala @@ -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 + } +} diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 2bf2878bd..5ea191812 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -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)) diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 6bf3558c6..6eebe449a 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -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 => diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala index b900d641d..0a5f72a07 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/InitializeOption.scala @@ -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)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala index f7f3a09e7..a5b04d6a4 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/protocol/codec/InitializeOptionFormats.scala @@ -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() } } diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/ClientJobParams.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/ClientJobParams.scala new file mode 100644 index 000000000..d72bf2b9f --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/ClientJobParams.scala @@ -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)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/FilePath.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/FilePath.scala new file mode 100644 index 000000000..24647f3c0 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/FilePath.scala @@ -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) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/JvmRunInfo.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/JvmRunInfo.scala new file mode 100644 index 000000000..d0d6b5b73 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/JvmRunInfo.scala @@ -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) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/NativeRunInfo.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/NativeRunInfo.scala new file mode 100644 index 000000000..5caffe8fd --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/NativeRunInfo.scala @@ -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) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/RunInfo.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/RunInfo.scala new file mode 100644 index 000000000..855bd06d3 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/RunInfo.scala @@ -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)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/ClientJobParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/ClientJobParamsFormats.scala new file mode 100644 index 000000000..e045d628c --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/ClientJobParamsFormats.scala @@ -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() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/FilePathFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/FilePathFormats.scala new file mode 100644 index 000000000..ebbac551f --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/FilePathFormats.scala @@ -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() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JsonProtocol.scala new file mode 100644 index 000000000..fa29c174c --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JsonProtocol.scala @@ -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 \ No newline at end of file diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JvmRunInfoFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JvmRunInfoFormats.scala new file mode 100644 index 000000000..793828b5e --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/JvmRunInfoFormats.scala @@ -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() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/NativeRunInfoFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/NativeRunInfoFormats.scala new file mode 100644 index 000000000..73588aa9f --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/NativeRunInfoFormats.scala @@ -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() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/worker/codec/RunInfoFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/RunInfoFormats.scala new file mode 100644 index 000000000..16e66747e --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/worker/codec/RunInfoFormats.scala @@ -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() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala b/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala index ddfe85f45..26511bc1e 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/InitCommand.scala @@ -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)) } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala index 6f95b6f48..a705d1b48 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/CommandMessageFormats.scala @@ -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") } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala index 827b6dc7c..7d552b17b 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/InitCommandFormats.scala @@ -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() } } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala index 2df56d1ad..32852fe44 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/JsonProtocol.scala @@ -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 diff --git a/protocol/src/main/contraband/portfile.contra b/protocol/src/main/contraband/portfile.contra index 2e138c315..ffd5dc6c9 100644 --- a/protocol/src/main/contraband/portfile.contra +++ b/protocol/src/main/contraband/portfile.contra @@ -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") } diff --git a/protocol/src/main/contraband/server.contra b/protocol/src/main/contraband/server.contra index 18ec0a0d2..176db450a 100644 --- a/protocol/src/main/contraband/server.contra +++ b/protocol/src/main/contraband/server.contra @@ -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. diff --git a/protocol/src/main/contraband/worker.contra b/protocol/src/main/contraband/worker.contra new file mode 100644 index 000000000..45a68eb74 --- /dev/null +++ b/protocol/src/main/contraband/worker.contra @@ -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 +} diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 2dcd5ae98..1384fb4f2 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -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 diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 3335f53f3..07fc33924 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -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) + } + } } diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index 5445c0da5..cda9e5888 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -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 + ) + ) } diff --git a/server-test/src/server-test/client/build.sbt b/server-test/src/server-test/client/build.sbt index 3225bd76d..686d2a7a8 100644 --- a/server-test/src/server-test/client/build.sbt +++ b/server-test/src/server-test/client/build.sbt @@ -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") := { () } diff --git a/server-test/src/server-test/client/src/main/scala/A.scala b/server-test/src/server-test/client/src/main/scala/A.scala index 69c493db2..171b96e91 100644 --- a/server-test/src/server-test/client/src/main/scala/A.scala +++ b/server-test/src/server-test/client/src/main/scala/A.scala @@ -1 +1,3 @@ -object A +class A + +@main def hello() = println("Hello, World!") diff --git a/server-test/src/server-test/client/src/test/scala/FooSpec.scala b/server-test/src/server-test/client/src/test/scala/FooSpec.scala index 269be5624..fb5352fd9 100644 --- a/server-test/src/server-test/client/src/test/scala/FooSpec.scala +++ b/server-test/src/server-test/client/src/test/scala/FooSpec.scala @@ -1,3 +1,3 @@ package test.pkg -class FooSpec extends org.scalatest.FlatSpec +class FooSpec extends munit.FunSuite diff --git a/server-test/src/test/scala/testpkg/ClientTest.scala b/server-test/src/test/scala/testpkg/ClientTest.scala index b4352062a..be9a29a13 100644 --- a/server-test/src/test/scala/testpkg/ClientTest.scala +++ b/server-test/src/test/scala/testpkg/ClientTest.scala @@ -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", diff --git a/server-test/src/test/scala/testpkg/TestServer.scala b/server-test/src/test/scala/testpkg/TestServer.scala index dac89ff79..c3a78bc6a 100644 --- a/server-test/src/test/scala/testpkg/TestServer.scala +++ b/server-test/src/test/scala/testpkg/TestServer.scala @@ -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] = {