Create BSP connection file at server startup

This commit is contained in:
Adrien Piquerez 2020-05-18 09:35:14 +02:00
parent c80fe525c6
commit a31747758c
8 changed files with 151 additions and 10 deletions

View File

@ -10,22 +10,24 @@ package internal
package server
import java.io.{ File, IOException }
import java.net.{ SocketTimeoutException, InetAddress, ServerSocket, Socket }
import java.net.{ InetAddress, ServerSocket, Socket, SocketTimeoutException }
import java.util.concurrent.atomic.AtomicBoolean
import java.nio.file.attribute.{ UserPrincipal, AclEntry, AclEntryPermission, AclEntryType }
import java.nio.file.attribute.{ AclEntry, AclEntryPermission, AclEntryType, UserPrincipal }
import java.security.SecureRandom
import java.math.BigInteger
import scala.concurrent.{ Future, Promise }
import scala.util.{ Try, Success, Failure }
import scala.util.{ Failure, Success, Try }
import sbt.internal.protocol.{ PortFile, TokenFile }
import sbt.util.Logger
import sbt.io.IO
import sbt.io.syntax._
import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter }
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
import sbt.internal.protocol.codec._
import sbt.internal.util.ErrorHandling
import sbt.internal.util.Util.isWindows
import org.scalasbt.ipcsocket._
import sbt.internal.bsp.BuildServerConnection
private[sbt] sealed trait ServerInstance {
def shutdown(): Unit
@ -85,6 +87,7 @@ private[sbt] object Server {
serverSocketOpt = Option(serverSocket)
log.info(s"sbt server started at ${connection.shortName}")
writePortfile()
writeBspConnectionDetails()
running.set(true)
p.success(())
while (running.get()) {
@ -194,6 +197,13 @@ private[sbt] object Server {
IO.write(portfile, CompactPrinter(json))
}
private[this] def writeBspConnectionDetails(): Unit = {
import bsp.codec.JsonProtocol._
val details = BuildServerConnection.details(sbtVersion)
val json = Converter.toJson(details).get
IO.write(bspConnectionFile, CompactPrinter(json), append = false)
}
private[sbt] def prepareSocketfile(): Unit = {
if (socketfile.exists) {
IO.delete(socketfile)
@ -211,7 +221,9 @@ private[sbt] case class ServerConnection(
portfile: File,
tokenfile: File,
socketfile: File,
pipeName: String
pipeName: String,
bspConnectionFile: File,
sbtVersion: String
) {
def shortName: String = {
connectionType match {

View File

@ -118,6 +118,7 @@ private[sbt] final class CommandExchange {
private[sbt] def runServer(s: State): State = {
lazy val port = s.get(serverPort).getOrElse(5001)
lazy val host = s.get(serverHost).getOrElse("127.0.0.1")
lazy val sbtVersion = s.configuration.provider.id.version
lazy val auth: Set[ServerAuthentication] =
s.get(serverAuthentication).getOrElse(Set(ServerAuthentication.Token))
lazy val connectionType = s.get(serverConnectionType).getOrElse(ConnectionType.Tcp)
@ -147,6 +148,7 @@ private[sbt] final class CommandExchange {
val tokenfile = serverDir / h / "token.json"
val socketfile = serverDir / h / "sock"
val pipeName = "sbt-server-" + h
val bspConnectionFile = s.baseDir / ".bsp" / "sbt.json"
val connection = ServerConnection(
connectionType,
host,
@ -156,6 +158,8 @@ private[sbt] final class CommandExchange {
tokenfile,
socketfile,
pipeName,
bspConnectionFile,
sbtVersion
)
val serverInstance = Server.start(connection, onIncomingSocket, s.log)
// don't throw exception when it times out

View File

@ -25,11 +25,9 @@ import sjsonnew.support.scalajson.unsafe.Converter
object BuildServerProtocol {
import sbt.internal.bsp.codec.JsonProtocol._
private val bspVersion = "2.0.0-M5"
private val languageIds = Vector("scala")
private val bspTargetConfigs = Set("compile", "test")
private val capabilities = BuildServerCapabilities(
CompileProvider(languageIds),
CompileProvider(BuildServerConnection.languages),
dependencySourcesProvider = true
)
@ -141,7 +139,13 @@ object BuildServerProtocol {
{
case r: JsonRpcRequestMessage if r.method == "build/initialize" =>
val _ = Converter.fromJson[InitializeBuildParams](json(r)).get
val response = InitializeBuildResult("sbt", sbtVersion, bspVersion, capabilities, None)
val response = InitializeBuildResult(
"sbt",
sbtVersion,
BuildServerConnection.bspVersion,
capabilities,
None
)
callback.jsonRpcRespond(response, Some(r.id)); ()
case r: JsonRpcRequestMessage if r.method == "workspace/buildTargets" =>
@ -223,7 +227,7 @@ object BuildServerProtocol {
Some(baseDirectory),
tags,
capabilities,
languageIds,
BuildServerConnection.languages,
projectDependencies.join.value.toVector,
dataKind = Some("scala"),
data = Some(Converter.toJsonUnsafe(compileData)),

View File

@ -0,0 +1,56 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.bsp
/**
* https://build-server-protocol.github.io/docs/server-discovery.html
* @param name The name of the build tool
* @param version The version of the build tool
* @param bspVersion The bsp version of the build tool
* @param languages A collection of languages supported by this BSP server
* @param argv Command arguments runnable via system processes to start a BSP server
*/
final class BspConnectionDetails private (
val name: String,
val version: String,
val bspVersion: String,
val languages: Vector[String],
val argv: Vector[String]) extends Serializable {
override def equals(o: Any): Boolean = o match {
case x: BspConnectionDetails => (this.name == x.name) && (this.version == x.version) && (this.bspVersion == x.bspVersion) && (this.languages == x.languages) && (this.argv == x.argv)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.BspConnectionDetails".##) + name.##) + version.##) + bspVersion.##) + languages.##) + argv.##)
}
override def toString: String = {
"BspConnectionDetails(" + name + ", " + version + ", " + bspVersion + ", " + languages + ", " + argv + ")"
}
private[this] def copy(name: String = name, version: String = version, bspVersion: String = bspVersion, languages: Vector[String] = languages, argv: Vector[String] = argv): BspConnectionDetails = {
new BspConnectionDetails(name, version, bspVersion, languages, argv)
}
def withName(name: String): BspConnectionDetails = {
copy(name = name)
}
def withVersion(version: String): BspConnectionDetails = {
copy(version = version)
}
def withBspVersion(bspVersion: String): BspConnectionDetails = {
copy(bspVersion = bspVersion)
}
def withLanguages(languages: Vector[String]): BspConnectionDetails = {
copy(languages = languages)
}
def withArgv(argv: Vector[String]): BspConnectionDetails = {
copy(argv = argv)
}
}
object BspConnectionDetails {
def apply(name: String, version: String, bspVersion: String, languages: Vector[String], argv: Vector[String]): BspConnectionDetails = new BspConnectionDetails(name, version, bspVersion, languages, argv)
}

View File

@ -0,0 +1,35 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.bsp.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait BspConnectionDetailsFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val BspConnectionDetailsFormat: JsonFormat[sbt.internal.bsp.BspConnectionDetails] = new JsonFormat[sbt.internal.bsp.BspConnectionDetails] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.BspConnectionDetails = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val name = unbuilder.readField[String]("name")
val version = unbuilder.readField[String]("version")
val bspVersion = unbuilder.readField[String]("bspVersion")
val languages = unbuilder.readField[Vector[String]]("languages")
val argv = unbuilder.readField[Vector[String]]("argv")
unbuilder.endObject()
sbt.internal.bsp.BspConnectionDetails(name, version, bspVersion, languages, argv)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.bsp.BspConnectionDetails, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("name", obj.name)
builder.addField("version", obj.version)
builder.addField("bspVersion", obj.bspVersion)
builder.addField("languages", obj.languages)
builder.addField("argv", obj.argv)
builder.endObject()
}
}
}

View File

@ -37,4 +37,5 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.internal.bsp.codec.ScalacOptionsParamsFormats
with sbt.internal.bsp.codec.ScalacOptionsItemFormats
with sbt.internal.bsp.codec.ScalacOptionsResultFormats
with sbt.internal.bsp.codec.BspConnectionDetailsFormats
object JsonProtocol extends JsonProtocol

View File

@ -416,3 +416,21 @@ type ScalacOptionsItem {
## The output directory for classfiles produced by this target
classDirectory: java.net.URI
}
## https://build-server-protocol.github.io/docs/server-discovery.html
type BspConnectionDetails {
## The name of the build tool
name: String!
## The version of the build tool
version: String!
## The bsp version of the build tool
bspVersion: String!
## A collection of languages supported by this BSP server
languages: [String]
## Command arguments runnable via system processes to start a BSP server
argv: [String]
}

View File

@ -0,0 +1,11 @@
package sbt.internal.bsp
object BuildServerConnection {
final val name = "sbt"
final val bspVersion = "2.0.0-M5"
final val languages = Vector("scala")
final val argv = Vector("sbt", "-bsp")
def details(sbtVersion: String): BspConnectionDetails = {
BspConnectionDetails(name, sbtVersion, bspVersion, languages, argv)
}
}