mirror of https://github.com/sbt/sbt.git
start an instance of sbt in the background
This commit is contained in:
parent
a2347332ab
commit
bd0e44c292
|
|
@ -20,4 +20,4 @@ install:
|
|||
- SET PATH=C:\sbt\sbt\bin;%PATH%
|
||||
- SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8
|
||||
test_script:
|
||||
- sbt "scripted actions/* server/*"
|
||||
- sbt "scripted actions/*" "testOnly sbt.ServerSpec"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ env:
|
|||
- SBT_CMD="scripted dependency-management/*4of4"
|
||||
- SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*"
|
||||
- SBT_CMD="scripted project/*1of2"
|
||||
- SBT_CMD="scripted project/*2of2 server/*"
|
||||
- SBT_CMD="scripted project/*2of2"
|
||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
||||
- SBT_CMD="scripted source-dependencies/*2of3"
|
||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ lazy val actionsProj = (project in file("main-actions"))
|
|||
|
||||
lazy val protocolProj = (project in file("protocol"))
|
||||
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
||||
.dependsOn(collectionProj)
|
||||
.settings(
|
||||
testedBaseSettings,
|
||||
name := "Protocol",
|
||||
|
|
@ -441,7 +442,7 @@ lazy val sbtProj = (project in file("sbt"))
|
|||
.dependsOn(mainProj, scriptedSbtProj % "test->test")
|
||||
.enablePlugins(BuildInfoPlugin)
|
||||
.settings(
|
||||
baseSettings,
|
||||
testedBaseSettings,
|
||||
name := "sbt",
|
||||
normalizedName := "sbt",
|
||||
crossScalaVersions := Seq(baseScalaVersion),
|
||||
|
|
@ -453,6 +454,8 @@ lazy val sbtProj = (project in file("sbt"))
|
|||
buildInfoObject in Test := "TestBuildInfo",
|
||||
buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile),
|
||||
connectInput in run in Test := true,
|
||||
outputStrategy in run in Test := Some(StdoutOutput),
|
||||
fork in Test := true,
|
||||
)
|
||||
.configure(addSbtCompilerBridge)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2017, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under BSD-3-Clause license (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package protocol
|
||||
|
||||
import java.io.File
|
||||
import java.net.{ Socket, URI, InetAddress }
|
||||
import sjsonnew.BasicJsonProtocol
|
||||
import sjsonnew.support.scalajson.unsafe.{ Parser, Converter }
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||
import sbt.internal.protocol.{ PortFile, TokenFile }
|
||||
import sbt.internal.protocol.codec.{ PortFileFormats, TokenFileFormats }
|
||||
import sbt.internal.util.Util.isWindows
|
||||
import org.scalasbt.ipcsocket._
|
||||
|
||||
object ClientSocket {
|
||||
private lazy val fileFormats = new BasicJsonProtocol with PortFileFormats with TokenFileFormats {}
|
||||
|
||||
def socket(portfile: File): (Socket, Option[String]) = {
|
||||
import fileFormats._
|
||||
val json: JValue = Parser.parseFromFile(portfile).get
|
||||
val p = Converter.fromJson[PortFile](json).get
|
||||
val uri = new URI(p.uri)
|
||||
// println(uri)
|
||||
val token = p.tokenfilePath map { tp =>
|
||||
val tokeFile = new File(tp)
|
||||
val json: JValue = Parser.parseFromFile(tokeFile).get
|
||||
val t = Converter.fromJson[TokenFile](json).get
|
||||
t.token
|
||||
}
|
||||
val sk = uri.getScheme match {
|
||||
case "local" if isWindows =>
|
||||
new Win32NamedPipeSocket("""\\.\pipe\""" + uri.getSchemeSpecificPart)
|
||||
case "local" => new UnixDomainSocket(uri.getSchemeSpecificPart)
|
||||
case "tcp" => new Socket(InetAddress.getByName(uri.getHost), uri.getPort)
|
||||
case _ => sys.error(s"Unsupported uri: $uri")
|
||||
}
|
||||
(sk, token)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
package example
|
||||
|
||||
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 delimiter: Byte = '\n'.toByte
|
||||
|
||||
lazy val connection = getConnection
|
||||
lazy val out = connection.getOutputStream
|
||||
lazy val in = connection.getInputStream
|
||||
|
||||
val t = getToken
|
||||
val msg0 = s"""{ "type": "InitCommand", "token": "$t" }"""
|
||||
|
||||
writeLine(s"Content-Length: ${ msg0.size + 2 }")
|
||||
writeLine("Content-Type: application/sbt-x1")
|
||||
writeLine("")
|
||||
writeLine(msg0)
|
||||
out.flush
|
||||
|
||||
writeLine("Content-Length: 49")
|
||||
writeLine("Content-Type: application/sbt-x1")
|
||||
writeLine("")
|
||||
// 12345678901234567890123456789012345678901234567890
|
||||
writeLine("""{ "type": "ExecCommand", "commandLine": "exit" }""")
|
||||
writeLine("")
|
||||
out.flush
|
||||
|
||||
val baseDirectory = new File(args(0))
|
||||
IO.write(baseDirectory / "ok.txt", "ok")
|
||||
|
||||
def getToken: String = {
|
||||
val tokenfile = new File(getTokenFileUri)
|
||||
val json: JValue = Parser.parseFromFile(tokenfile).get
|
||||
json match {
|
||||
case JObject(fields) =>
|
||||
(fields find { _.field == "token" } map { _.value }) match {
|
||||
case Some(JString(value)) => value
|
||||
case _ =>
|
||||
sys.error("json doesn't token field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have token field")
|
||||
}
|
||||
}
|
||||
|
||||
def getTokenFileUri: URI = {
|
||||
val portfile = baseDirectory / "project" / "target" / "active.json"
|
||||
val json: JValue = Parser.parseFromFile(portfile).get
|
||||
json match {
|
||||
case JObject(fields) =>
|
||||
(fields find { _.field == "tokenfileUri" } map { _.value }) match {
|
||||
case Some(JString(value)) => new URI(value)
|
||||
case _ =>
|
||||
sys.error("json doesn't tokenfile field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have tokenfile field")
|
||||
}
|
||||
}
|
||||
|
||||
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 == "uri" } map { _.value }) match {
|
||||
case Some(JString(value)) =>
|
||||
val u = new URI(value)
|
||||
u.getPort
|
||||
case _ =>
|
||||
sys.error("json doesn't uri field that is JString")
|
||||
}
|
||||
case _ => sys.error("json doesn't have uri field")
|
||||
}
|
||||
}
|
||||
|
||||
def getConnection: Socket =
|
||||
try {
|
||||
new Socket(InetAddress.getByName(host), getPort)
|
||||
} catch {
|
||||
case _ =>
|
||||
Thread.sleep(1000)
|
||||
getConnection
|
||||
}
|
||||
|
||||
def writeLine(s: String): Unit = {
|
||||
if (s != "") {
|
||||
out.write(s.getBytes("UTF-8"))
|
||||
}
|
||||
writeEndLine
|
||||
}
|
||||
|
||||
def writeEndLine(): Unit = {
|
||||
val retByte: Byte = '\r'.toByte
|
||||
val delimiter: Byte = '\n'.toByte
|
||||
|
||||
out.write(retByte.toInt)
|
||||
out.write(delimiter.toInt)
|
||||
out.flush
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
lazy val runClient = taskKey[Unit]("")
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
serverConnectionType in Global := ConnectionType.Tcp,
|
||||
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""")
|
||||
}).value
|
||||
)
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
> show serverPort
|
||||
> runClient
|
||||
|
||||
-> shell
|
||||
|
||||
$ exists ok.txt
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
Global / serverLog / logLevel := Level.Debug,
|
||||
name := "handshake",
|
||||
scalaVersion := "2.12.3",
|
||||
)
|
||||
|
|
@ -22,7 +22,7 @@ object RunFromSourceMain {
|
|||
|
||||
// this arrangement is because Scala does not always properly optimize away
|
||||
// the tail recursion in a catch statement
|
||||
@tailrec private def run(baseDir: File, args: Seq[String]): Unit =
|
||||
@tailrec private[sbt] def run(baseDir: File, args: Seq[String]): Unit =
|
||||
runImpl(baseDir, args) match {
|
||||
case Some((baseDir, args)) => run(baseDir, args)
|
||||
case None => ()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2017, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under BSD-3-Clause license (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
|
||||
import org.scalatest._
|
||||
import scala.concurrent._
|
||||
import java.io.{ InputStream, OutputStream }
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.{ ThreadFactory, ThreadPoolExecutor }
|
||||
import sbt.protocol.ClientSocket
|
||||
|
||||
class ServerSpec extends AsyncFlatSpec with Matchers {
|
||||
import ServerSpec._
|
||||
|
||||
"server" should "start" in {
|
||||
withBuildSocket("handshake") { (out, in, tkn) =>
|
||||
writeLine(
|
||||
"""{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""",
|
||||
out)
|
||||
Thread.sleep(100)
|
||||
val l2 = contentLength(in)
|
||||
println(l2)
|
||||
readLine(in)
|
||||
readLine(in)
|
||||
val x2 = readContentLength(in, l2)
|
||||
println(x2)
|
||||
assert(1 == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ServerSpec {
|
||||
private val serverTestBase: File = new File(".").getAbsoluteFile / "sbt" / "src" / "server-test"
|
||||
private val nextThreadId = new AtomicInteger(1)
|
||||
private val threadGroup = Thread.currentThread.getThreadGroup()
|
||||
val readBuffer = new Array[Byte](4096)
|
||||
var buffer: Vector[Byte] = Vector.empty
|
||||
var bytesRead = 0
|
||||
private val delimiter: Byte = '\n'.toByte
|
||||
private val RetByte = '\r'.toByte
|
||||
|
||||
private val threadFactory = new ThreadFactory() {
|
||||
override def newThread(runnable: Runnable): Thread = {
|
||||
val thread =
|
||||
new Thread(threadGroup,
|
||||
runnable,
|
||||
s"sbt-test-server-threads-${nextThreadId.getAndIncrement}")
|
||||
// Do NOT setDaemon because then the code in TaskExit.scala in sbt will insta-kill
|
||||
// the backgrounded process, at least for the case of the run task.
|
||||
thread
|
||||
}
|
||||
}
|
||||
|
||||
private val executor = new ThreadPoolExecutor(
|
||||
0, /* corePoolSize */
|
||||
1, /* maxPoolSize, max # of servers */
|
||||
2,
|
||||
java.util.concurrent.TimeUnit.SECONDS,
|
||||
/* keep alive unused threads this long (if corePoolSize < maxPoolSize) */
|
||||
new java.util.concurrent.SynchronousQueue[Runnable](),
|
||||
threadFactory
|
||||
)
|
||||
|
||||
def backgroundRun(baseDir: File, args: Seq[String]): Unit = {
|
||||
executor.execute(new Runnable {
|
||||
def run(): Unit = {
|
||||
RunFromSourceMain.run(baseDir, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def shutdown(): Unit = executor.shutdown()
|
||||
|
||||
def withBuildSocket(testBuild: String)(
|
||||
f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = {
|
||||
IO.withTemporaryDirectory { temp =>
|
||||
IO.copyDirectory(serverTestBase / testBuild, temp / testBuild)
|
||||
withBuildSocket(temp / testBuild)(f)
|
||||
}
|
||||
}
|
||||
|
||||
def sendJsonRpc(message: String, out: OutputStream): Unit = {
|
||||
writeLine(s"""Content-Length: ${message.size + 2}""", out)
|
||||
writeLine("", out)
|
||||
writeLine(message, out)
|
||||
}
|
||||
|
||||
def contentLength(in: InputStream): Int = {
|
||||
readLine(in) map { line =>
|
||||
line.drop(16).toInt
|
||||
} getOrElse (0)
|
||||
}
|
||||
|
||||
def readLine(in: InputStream): Option[String] = {
|
||||
if (buffer.isEmpty) {
|
||||
val bytesRead = in.read(readBuffer)
|
||||
if (bytesRead > 0) {
|
||||
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
|
||||
}
|
||||
}
|
||||
val delimPos = buffer.indexOf(delimiter)
|
||||
if (delimPos > 0) {
|
||||
val chunk0 = buffer.take(delimPos)
|
||||
buffer = buffer.drop(delimPos + 1)
|
||||
// remove \r at the end of line.
|
||||
if (chunk0.size > 0 && chunk0.indexOf(RetByte) == chunk0.size - 1)
|
||||
Some(new String(chunk0.dropRight(1).toArray, "utf-8"))
|
||||
else Some(new String(chunk0.toArray, "utf-8"))
|
||||
} else None // no EOL yet, so skip this turn.
|
||||
}
|
||||
|
||||
def readContentLength(in: InputStream, length: Int): Option[String] = {
|
||||
if (buffer.isEmpty) {
|
||||
val bytesRead = in.read(readBuffer)
|
||||
if (bytesRead > 0) {
|
||||
buffer = buffer ++ readBuffer.toVector.take(bytesRead)
|
||||
}
|
||||
}
|
||||
if (length <= buffer.size) {
|
||||
val chunk = buffer.take(length)
|
||||
buffer = buffer.drop(length)
|
||||
Some(new String(chunk.toArray, "utf-8"))
|
||||
} else None // have not read enough yet, so skip this turn.
|
||||
}
|
||||
|
||||
def writeLine(s: String, out: OutputStream): Unit = {
|
||||
def writeEndLine(): Unit = {
|
||||
val retByte: Byte = '\r'.toByte
|
||||
val delimiter: Byte = '\n'.toByte
|
||||
out.write(retByte.toInt)
|
||||
out.write(delimiter.toInt)
|
||||
out.flush
|
||||
}
|
||||
|
||||
if (s != "") {
|
||||
out.write(s.getBytes("UTF-8"))
|
||||
}
|
||||
writeEndLine
|
||||
}
|
||||
|
||||
def withBuildSocket(baseDirectory: File)(
|
||||
f: (OutputStream, InputStream, Option[String]) => Future[Assertion]): Future[Assertion] = {
|
||||
backgroundRun(baseDirectory, Nil)
|
||||
|
||||
val portfile = baseDirectory / "project" / "target" / "active.json"
|
||||
|
||||
def waitForPortfile(n: Int): Unit =
|
||||
if (portfile.exists) ()
|
||||
else {
|
||||
if (n <= 0) sys.error(s"Timeout. $portfile is not found.")
|
||||
else {
|
||||
Thread.sleep(1000)
|
||||
waitForPortfile(n - 1)
|
||||
}
|
||||
}
|
||||
waitForPortfile(10)
|
||||
val (sk, tkn) = ClientSocket.socket(portfile)
|
||||
val out = sk.getOutputStream
|
||||
val in = sk.getInputStream
|
||||
|
||||
sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }""",
|
||||
out)
|
||||
|
||||
try {
|
||||
f(out, in, tkn)
|
||||
} finally {
|
||||
sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": 9, "method": "sbt/exec", "params": { "commandLine": "exit" } }""",
|
||||
out)
|
||||
shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue