Use NetworkClient to implement `sbt -bsp`

Network client already supports the -bsp command (since
65ab7c94d0). 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.
This commit is contained in:
Ethan Atkins 2020-11-18 12:47:28 -08:00
parent 68933a628d
commit f3b3148c58
3 changed files with 27 additions and 73 deletions

View File

@ -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))
}
}

View File

@ -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
}

View File

@ -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 {