From f3b3148c5898d13593cc0531fd618346ad38731d Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 18 Nov 2020 12:47:28 -0800 Subject: [PATCH] Use NetworkClient to implement `sbt -bsp` Network client already supports the -bsp command (since 65ab7c94d038955d48eefbf7951fb9cd399ab7ef). This commit reworks the BspClient.run method so that it delegates to the NetworkClient. The advantage to doing it this way is that improvements to starting up the sbt server by the thin client will automatically propagate to the -bsp command. The way that it is implemented, all of the output generated during server startup will be redirected to System.err which is useful for debugging without messing up the bsp protocol, which relies on only bsp messages being written to System.out. --- .../scala/sbt/internal/client/BspClient.scala | 58 +------------------ .../sbt/internal/client/NetworkClient.scala | 32 +++++----- main/src/main/scala/sbt/Main.scala | 10 ++-- 3 files changed, 27 insertions(+), 73 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/client/BspClient.scala b/main-command/src/main/scala/sbt/internal/client/BspClient.scala index c9942ba99..19b8e106a 100644 --- a/main-command/src/main/scala/sbt/internal/client/BspClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/BspClient.scala @@ -7,20 +7,12 @@ package sbt.internal.client -import java.io.{ File, InputStream, OutputStream } +import java.io.{ InputStream, OutputStream } import java.net.Socket import java.util.concurrent.atomic.AtomicBoolean import sbt.Exit -import sbt.io.syntax._ -import sbt.protocol.ClientSocket - import scala.util.control.NonFatal -import java.lang.ProcessBuilder.Redirect - -class BspClient private (sbtServer: Socket) { - private def run(): Exit = Exit(BspClient.bspRun(sbtServer)) -} object BspClient { private[sbt] def bspRun(sbtServer: Socket): Int = { @@ -72,52 +64,6 @@ object BspClient { thread } 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("--detach-stdio") - val launchOpts = List( - "-Dfile.encoding=UTF-8", - "-Dsbt.io.virtual=true", - "-Xms1024M", - "-Xmx1024M", - "-Xss4M", - "-XX:ReservedCodeCacheSize=128m" - ) - - 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 processBuilder = - new ProcessBuilder(cmd: _*) - .directory(baseDirectory) - .redirectInput(Redirect.PIPE) - - val process = processBuilder.start() - - while (process.isAlive && !portfile.exists) Thread.sleep(100) - - if (!process.isAlive) sys.error("sbt server exited") + Exit(NetworkClient.run(configuration, configuration.arguments.toList, redirectOutput = 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 c89715661..ccf33bbe4 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -1093,16 +1093,20 @@ object NetworkClient { terminal: Terminal, useJNI: Boolean ): Int = { + val printStream = if (args.bsp) errorStream else terminal.printStream val client = simpleClient( args.withBaseDirectory(baseDirectory), inputStream, + printStream, errorStream, useJNI, - terminal ) + clientImpl(client, args.bsp) + } + private def clientImpl(client: NetworkClient, isBsp: Boolean): Int = { try { - if (args.bsp) { + if (isBsp) { val (socket, _) = client.connectOrStartServerAndConnect(promptCompleteUsers = false, retry = true) BspClient.bspRun(socket) @@ -1214,16 +1218,18 @@ object NetworkClient { } def run(configuration: xsbti.AppConfiguration, arguments: List[String]): Int = - try { - val client = new NetworkClient(configuration, parseArgs(arguments.toArray)) - try { - if (client.connect(log = true, promptCompleteUsers = false)) client.run() - else 1 - } catch { case _: Throwable => 1 } finally client.close() - } catch { - case NonFatal(e) => - e.printStackTrace() - 1 - } + run(configuration, arguments, false) + def run( + configuration: xsbti.AppConfiguration, + arguments: List[String], + redirectOutput: Boolean + ): Int = { + val term = Terminal.console + val err = new PrintStream(term.errorStream) + val out = if (redirectOutput) err else new PrintStream(term.outputStream) + val args = parseArgs(arguments.toArray).withBaseDirectory(configuration.baseDirectory) + val client = simpleClient(args, term.inputStream, out, err, useJNI = false) + clientImpl(client, args.bsp) + } private class AccessDeniedException extends Throwable } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index d7b857fa4..1f5888c78 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -64,10 +64,6 @@ private[sbt] object xMain { import sbt.internal.CommandStrings.{ BootCommand, DefaultsCommand, InitCommand } import sbt.internal.client.NetworkClient - val bootServerSocket = getSocketOrExit(configuration) match { - case (_, Some(e)) => return e - case (s, _) => s - } // if we detect -Dsbt.client=true or -client, run thin client. val clientModByEnv = SysProp.client val userCommands = configuration.arguments @@ -75,6 +71,12 @@ private[sbt] object xMain { .filterNot(_ == DashDashServer) val isClient: String => Boolean = cmd => (cmd == DashClient) || (cmd == DashDashClient) val isBsp: String => Boolean = cmd => (cmd == "-bsp") || (cmd == "--bsp") + val isServer = !userCommands.exists(c => isBsp(c) || isClient(c)) + val bootServerSocket = if (isServer) getSocketOrExit(configuration) match { + case (_, Some(e)) => return e + case (s, _) => s + } + else None if (userCommands.exists(isBsp)) { BspClient.run(dealiasBaseDirectory(configuration)) } else {