From c80fe525c62424beb045f807ef34b02732447a16 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Thu, 14 May 2020 16:22:33 +0200 Subject: [PATCH] add BspClient --- .../scala/sbt/internal/client/BspClient.scala | 78 +++++++++++++++++++ main/src/main/scala/sbt/Main.scala | 30 ++++--- 2 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 main-command/src/main/scala/sbt/internal/client/BspClient.scala diff --git a/main-command/src/main/scala/sbt/internal/client/BspClient.scala b/main-command/src/main/scala/sbt/internal/client/BspClient.scala new file mode 100644 index 000000000..22a099a9a --- /dev/null +++ b/main-command/src/main/scala/sbt/internal/client/BspClient.scala @@ -0,0 +1,78 @@ +package sbt.internal.client + +import java.io.{ File, InputStream, OutputStream } +import java.net.Socket + +import sbt.Exit +import sbt.io.syntax._ +import sbt.protocol.ClientSocket + +import scala.sys.process.Process +import scala.util.control.NonFatal + +class BspClient private (sbtServer: Socket) { + + private def transferTo(input: InputStream, output: OutputStream): Unit = { + val buffer = Array.ofDim[Byte](1024) + while (true) { + val size = input.read(buffer) + output.write(buffer, 0, size) + output.flush() + } + } + + private def run(): Exit = { + try { + val redirection = new Thread { + override def run(): Unit = transferTo(sbtServer.getInputStream, System.out) + } + + redirection.start() + transferTo(System.in, sbtServer.getOutputStream) + + Exit(0) + } catch { + case NonFatal(_) => Exit(1) + } + } +} + +object BspClient { + def run(configuration: xsbti.AppConfiguration): Exit = { + val baseDirectory = configuration.baseDirectory + val portFile = baseDirectory / "project" / "target" / "active.json" + try { + if (!portFile.exists) { + forkServer(baseDirectory, portFile) + } + val (socket, _) = ClientSocket.socket(portFile) + new BspClient(socket).run() + } catch { + case NonFatal(_) => Exit(1) + } + } + + /** + * Forks another instance of sbt in the background. + * This instance must be shutdown explicitly via `sbt -client shutdown` + */ + def forkServer(baseDirectory: File, portfile: File): Unit = { + val args = List[String]() + val launchOpts = List("-Xms2048M", "-Xmx2048M", "-Xss2M") + + val launcherJarString = sys.props.get("java.class.path") match { + case Some(cp) => + cp.split(File.pathSeparator) + .headOption + .getOrElse(sys.error("launcher JAR classpath not found")) + case _ => sys.error("property java.class.path expected") + } + + val cmd = "java" :: launchOpts ::: "-jar" :: launcherJarString :: args + val process = Process(cmd, baseDirectory).run() + + while (process.isAlive() && !portfile.exists) Thread.sleep(100) + + if (!process.isAlive()) sys.error("sbt server exited") + } +} diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 23a6e5b36..0ab32f035 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -19,6 +19,7 @@ import sbt.compiler.EvalImports import sbt.internal.Aggregation.AnyKeys import sbt.internal.CommandStrings.BootCommand import sbt.internal._ +import sbt.internal.client.BspClient import sbt.internal.inc.ScalaInstance import sbt.internal.nio.CheckBuildSources import sbt.internal.util.Types.{ const, idFun } @@ -51,18 +52,23 @@ private[sbt] object xMain { val clientModByEnv = SysProp.client val userCommands = configuration.arguments.map(_.trim) val isClient: String => Boolean = cmd => (cmd == DashClient) || (cmd == DashDashClient) - Terminal.withStreams { - if (clientModByEnv || userCommands.exists(isClient)) { - val args = userCommands.toList.filterNot(isClient) - NetworkClient.run(configuration, args) - Exit(0) - } else { - val state = StandardMain.initialState( - configuration, - Seq(defaults, early), - runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil - ) - StandardMain.runManaged(state) + val isBsp: String => Boolean = cmd => (cmd == "-bsp") || (cmd == "--bsp") + if (userCommands.exists(isBsp)) { + BspClient.run(configuration) + } else { + Terminal.withStreams { + if (clientModByEnv || userCommands.exists(isClient)) { + val args = userCommands.toList.filterNot(isClient) + NetworkClient.run(configuration, args) + Exit(0) + } else { + val state = StandardMain.initialState( + configuration, + Seq(defaults, early), + runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil + ) + StandardMain.runManaged(state) + } } } } finally {