mirror of https://github.com/sbt/sbt.git
start an instance of sbt in the background
This commit is contained in:
parent
4b1de14f87
commit
1f9a8bf310
|
|
@ -20,4 +20,4 @@ install:
|
||||||
- SET PATH=C:\sbt\sbt\bin;%PATH%
|
- SET PATH=C:\sbt\sbt\bin;%PATH%
|
||||||
- SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8
|
- SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g -Dfile.encoding=UTF8
|
||||||
test_script:
|
test_script:
|
||||||
- sbt "scripted actions/* server/*"
|
- sbt "scripted actions/*" "testOnly sbt.ServerSpec"
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ env:
|
||||||
- SBT_CMD="scripted dependency-management/*4of4"
|
- SBT_CMD="scripted dependency-management/*4of4"
|
||||||
- SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*"
|
- SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*"
|
||||||
- SBT_CMD="scripted project/*1of2"
|
- 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/*1of3"
|
||||||
- SBT_CMD="scripted source-dependencies/*2of3"
|
- SBT_CMD="scripted source-dependencies/*2of3"
|
||||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
- SBT_CMD="scripted source-dependencies/*3of3"
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,7 @@ lazy val actionsProj = (project in file("main-actions"))
|
||||||
|
|
||||||
lazy val protocolProj = (project in file("protocol"))
|
lazy val protocolProj = (project in file("protocol"))
|
||||||
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
||||||
|
.dependsOn(collectionProj)
|
||||||
.settings(
|
.settings(
|
||||||
testedBaseSettings,
|
testedBaseSettings,
|
||||||
scalacOptions -= "-Ywarn-unused",
|
scalacOptions -= "-Ywarn-unused",
|
||||||
|
|
@ -466,7 +467,7 @@ lazy val mainProj = (project in file("main"))
|
||||||
lazy val sbtProj = (project in file("sbt"))
|
lazy val sbtProj = (project in file("sbt"))
|
||||||
.dependsOn(mainProj, scriptedSbtProj % "test->test")
|
.dependsOn(mainProj, scriptedSbtProj % "test->test")
|
||||||
.settings(
|
.settings(
|
||||||
baseSettings,
|
testedBaseSettings,
|
||||||
name := "sbt",
|
name := "sbt",
|
||||||
normalizedName := "sbt",
|
normalizedName := "sbt",
|
||||||
crossScalaVersions := Seq(baseScalaVersion),
|
crossScalaVersions := Seq(baseScalaVersion),
|
||||||
|
|
@ -480,6 +481,7 @@ lazy val sbtProj = (project in file("sbt"))
|
||||||
buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile),
|
buildInfoKeys in Test := Seq[BuildInfoKey](fullClasspath in Compile),
|
||||||
connectInput in run in Test := true,
|
connectInput in run in Test := true,
|
||||||
outputStrategy in run in Test := Some(StdoutOutput),
|
outputStrategy in run in Test := Some(StdoutOutput),
|
||||||
|
fork in Test := true,
|
||||||
)
|
)
|
||||||
.configure(addSbtCompilerBridge)
|
.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
|
// this arrangement is because Scala does not always properly optimize away
|
||||||
// the tail recursion in a catch statement
|
// 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 {
|
runImpl(baseDir, args) match {
|
||||||
case Some((baseDir, args)) => run(baseDir, args)
|
case Some((baseDir, args)) => run(baseDir, args)
|
||||||
case None => ()
|
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