mirror of https://github.com/sbt/sbt.git
JSON port file
This implements JSON-based port file. Thoughout the lifetime of the sbt server there will be `cwd / "project" / "target" / "active.json"`, which contains `url` field. Using this `url` the potential client, such as IDEs can find out which port number to hit. Ref #3508
This commit is contained in:
parent
6b8e716428
commit
9d40404915
|
|
@ -290,6 +290,10 @@ lazy val commandProj = (project in file("main-command"))
|
|||
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
|
||||
contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats,
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Vector(
|
||||
// Changed the signature of Server method. nacho cheese.
|
||||
exclude[DirectMissingMethodProblem]("sbt.internal.server.Server.*")
|
||||
)
|
||||
)
|
||||
.configure(
|
||||
addSbtIO,
|
||||
|
|
|
|||
|
|
@ -5,12 +5,17 @@ package sbt
|
|||
package internal
|
||||
package server
|
||||
|
||||
import java.io.File
|
||||
import java.net.{ SocketTimeoutException, InetAddress, ServerSocket, Socket }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import sbt.util.Logger
|
||||
import sbt.internal.util.ErrorHandling
|
||||
import scala.concurrent.{ Future, Promise }
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
import sbt.internal.util.ErrorHandling
|
||||
import sbt.internal.protocol.PortFile
|
||||
import sbt.util.Logger
|
||||
import sbt.io.IO
|
||||
import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter }
|
||||
import sbt.internal.protocol.codec._
|
||||
|
||||
private[sbt] sealed trait ServerInstance {
|
||||
def shutdown(): Unit
|
||||
|
|
@ -18,14 +23,19 @@ private[sbt] sealed trait ServerInstance {
|
|||
}
|
||||
|
||||
private[sbt] object Server {
|
||||
sealed trait JsonProtocol
|
||||
extends sjsonnew.BasicJsonProtocol
|
||||
with PortFileFormats
|
||||
with TokenFileFormats
|
||||
object JsonProtocol extends JsonProtocol
|
||||
|
||||
def start(host: String,
|
||||
port: Int,
|
||||
onIncomingSocket: Socket => Unit,
|
||||
/*onIncomingCommand: CommandMessage => Unit,*/ log: Logger): ServerInstance =
|
||||
portfile: File,
|
||||
tokenfile: File,
|
||||
log: Logger): ServerInstance =
|
||||
new ServerInstance {
|
||||
|
||||
// val lock = new AnyRef {}
|
||||
// val clients: mutable.ListBuffer[ClientConnection] = mutable.ListBuffer.empty
|
||||
val running = new AtomicBoolean(false)
|
||||
val p: Promise[Unit] = Promise[Unit]()
|
||||
val ready: Future[Unit] = p.future
|
||||
|
|
@ -41,6 +51,7 @@ private[sbt] object Server {
|
|||
case Success(serverSocket) =>
|
||||
serverSocket.setSoTimeout(5000)
|
||||
log.info(s"sbt server started at $host:$port")
|
||||
writePortfile()
|
||||
running.set(true)
|
||||
p.success(())
|
||||
while (running.get()) {
|
||||
|
|
@ -58,8 +69,21 @@ private[sbt] object Server {
|
|||
|
||||
override def shutdown(): Unit = {
|
||||
log.info("shutting down server")
|
||||
if (portfile.exists) {
|
||||
IO.delete(portfile)
|
||||
}
|
||||
if (tokenfile.exists) {
|
||||
IO.delete(tokenfile)
|
||||
}
|
||||
running.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
// This file exists through the lifetime of the server.
|
||||
def writePortfile(): Unit = {
|
||||
import JsonProtocol._
|
||||
val p = PortFile(s"tcp://$host:$port", None)
|
||||
val json = Converter.toJson(p).get
|
||||
IO.write(portfile, CompactPrinter(json))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,9 @@ object StandardMain {
|
|||
val previous = TrapExit.installManager()
|
||||
try {
|
||||
try {
|
||||
MainLoop.runLogged(s)
|
||||
try {
|
||||
MainLoop.runLogged(s)
|
||||
} finally exchange.shutdown
|
||||
} finally DefaultBackgroundJobService.backgroundJobService.shutdown()
|
||||
} finally TrapExit.uninstallManager(previous)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import sjsonnew.JsonFormat
|
|||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.util.{ Success, Failure }
|
||||
import sbt.io.syntax._
|
||||
import sbt.io.Hash
|
||||
|
||||
/**
|
||||
* The command exchange merges multiple command channels (e.g. network and console),
|
||||
|
|
@ -87,7 +89,10 @@ private[sbt] final class CommandExchange {
|
|||
server match {
|
||||
case Some(x) => // do nothing
|
||||
case _ =>
|
||||
val x = Server.start("127.0.0.1", port, onIncomingSocket, s.log)
|
||||
val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json"
|
||||
val h = Hash.halfHashString(portfile.toURL.toString)
|
||||
val tokenfile = BuildPaths.getGlobalBase(s) / "server" / h / "token.json"
|
||||
val x = Server.start("127.0.0.1", port, onIncomingSocket, portfile, tokenfile, s.log)
|
||||
Await.ready(x.ready, Duration("10s"))
|
||||
x.ready.value match {
|
||||
case Some(Success(_)) =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.protocol
|
||||
/**
|
||||
* This file should exist throughout the lifetime of the server.
|
||||
* It can be used to find out the transport protocol (port number etc).
|
||||
*/
|
||||
final class PortFile private (
|
||||
/** URL of the sbt server. */
|
||||
val url: String,
|
||||
val tokenfile: Option[String]) extends Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: PortFile => (this.url == x.url) && (this.tokenfile == x.tokenfile)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (17 + "sbt.internal.protocol.PortFile".##) + url.##) + tokenfile.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"PortFile(" + url + ", " + tokenfile + ")"
|
||||
}
|
||||
protected[this] def copy(url: String = url, tokenfile: Option[String] = tokenfile): PortFile = {
|
||||
new PortFile(url, tokenfile)
|
||||
}
|
||||
def withUrl(url: String): PortFile = {
|
||||
copy(url = url)
|
||||
}
|
||||
def withTokenfile(tokenfile: Option[String]): PortFile = {
|
||||
copy(tokenfile = tokenfile)
|
||||
}
|
||||
def withTokenfile(tokenfile: String): PortFile = {
|
||||
copy(tokenfile = Option(tokenfile))
|
||||
}
|
||||
}
|
||||
object PortFile {
|
||||
|
||||
def apply(url: String, tokenfile: Option[String]): PortFile = new PortFile(url, tokenfile)
|
||||
def apply(url: String, tokenfile: String): PortFile = new PortFile(url, Option(tokenfile))
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.protocol
|
||||
final class TokenFile private (
|
||||
val url: String,
|
||||
val token: String) extends Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: TokenFile => (this.url == x.url) && (this.token == x.token)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (17 + "sbt.internal.protocol.TokenFile".##) + url.##) + token.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TokenFile(" + url + ", " + token + ")"
|
||||
}
|
||||
protected[this] def copy(url: String = url, token: String = token): TokenFile = {
|
||||
new TokenFile(url, token)
|
||||
}
|
||||
def withUrl(url: String): TokenFile = {
|
||||
copy(url = url)
|
||||
}
|
||||
def withToken(token: String): TokenFile = {
|
||||
copy(token = token)
|
||||
}
|
||||
}
|
||||
object TokenFile {
|
||||
|
||||
def apply(url: String, token: String): TokenFile = new TokenFile(url, token)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.protocol.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait PortFileFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val PortFileFormat: JsonFormat[sbt.internal.protocol.PortFile] = new JsonFormat[sbt.internal.protocol.PortFile] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.protocol.PortFile = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val url = unbuilder.readField[String]("url")
|
||||
val tokenfile = unbuilder.readField[Option[String]]("tokenfile")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.protocol.PortFile(url, tokenfile)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.internal.protocol.PortFile, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("url", obj.url)
|
||||
builder.addField("tokenfile", obj.tokenfile)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.protocol.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait TokenFileFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TokenFileFormat: JsonFormat[sbt.internal.protocol.TokenFile] = new JsonFormat[sbt.internal.protocol.TokenFile] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.protocol.TokenFile = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val url = unbuilder.readField[String]("url")
|
||||
val token = unbuilder.readField[String]("token")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.protocol.TokenFile(url, token)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.internal.protocol.TokenFile, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("url", obj.url)
|
||||
builder.addField("token", obj.token)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package sbt.internal.protocol
|
||||
@target(Scala)
|
||||
@codecPackage("sbt.internal.protocol.codec")
|
||||
|
||||
## This file should exist throughout the lifetime of the server.
|
||||
## It can be used to find out the transport protocol (port number etc).
|
||||
type PortFile {
|
||||
## URL of the sbt server.
|
||||
url: String!
|
||||
tokenfile: String
|
||||
}
|
||||
|
||||
type TokenFile {
|
||||
url: String!
|
||||
token: String!
|
||||
}
|
||||
|
|
@ -4,10 +4,11 @@ import java.net.{ URI, Socket, InetAddress, SocketException }
|
|||
import sbt.io._
|
||||
import sbt.io.syntax._
|
||||
import java.io.File
|
||||
import sjsonnew.support.scalajson.unsafe.{ Parser, Converter, CompactPrinter }
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.{ JValue, JObject, JString }
|
||||
|
||||
object Client extends App {
|
||||
val host = "127.0.0.1"
|
||||
val port = 5123
|
||||
val delimiter: Byte = '\n'.toByte
|
||||
|
||||
println("hello")
|
||||
|
|
@ -24,9 +25,25 @@ object Client extends App {
|
|||
val baseDirectory = new File(args(0))
|
||||
IO.write(baseDirectory / "ok.txt", "ok")
|
||||
|
||||
def getPort: Int = {
|
||||
val portfile = baseDirectory / "project" / "target" / "active.json"
|
||||
val json: JValue = Parser.parseFromFile(portfile).get
|
||||
json match {
|
||||
case JObject(fields) =>
|
||||
(fields find { _.field == "url" } map { _.value }) match {
|
||||
case Some(JString(value)) =>
|
||||
val u = new URI(value)
|
||||
u.getPort
|
||||
case _ =>
|
||||
sys.error("json doesn't url field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have url field")
|
||||
}
|
||||
}
|
||||
|
||||
def getConnection: Socket =
|
||||
try {
|
||||
new Socket(InetAddress.getByName(host), port)
|
||||
new Socket(InetAddress.getByName(host), getPort)
|
||||
} catch {
|
||||
case _ =>
|
||||
Thread.sleep(1000)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ lazy val root = (project in file("."))
|
|||
scalaVersion := "2.12.3",
|
||||
serverPort in Global := 5123,
|
||||
libraryDependencies += "org.scala-sbt" %% "io" % "1.0.1",
|
||||
libraryDependencies += "com.eed3si9n" %% "sjson-new-scalajson" % "0.8.0",
|
||||
runClient := (Def.taskDyn {
|
||||
val b = baseDirectory.value
|
||||
(bgRun in Compile).toTask(s""" $b""")
|
||||
|
|
|
|||
Loading…
Reference in New Issue