mirror of https://github.com/sbt/sbt.git
Merge branch 'develop' into coursier_2.0.2
This commit is contained in:
commit
bc150efd55
|
|
@ -84,7 +84,7 @@ for:
|
|||
- path: client\target\bin\sbtn.exe
|
||||
name: sbtn.exe
|
||||
install:
|
||||
- cinst jdk8 -params 'installdir=C:\\jdk8'
|
||||
- cinst adoptopenjdk8 -params 'installdir=C:\\jdk8'
|
||||
- SET CI=true
|
||||
#- choco install windows-sdk-7.1 kb2519277
|
||||
- call "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd"
|
||||
|
|
@ -132,7 +132,7 @@ for:
|
|||
except:
|
||||
- build-graal
|
||||
install:
|
||||
- cinst jdk8 -params 'installdir=C:\\jdk8'
|
||||
- cinst adoptopenjdk8 -params 'installdir=C:\\jdk8'
|
||||
- SET JAVA_HOME=C:\jdk8
|
||||
- SET PATH=C:\jdk8\bin;%PATH%
|
||||
- SET CI=true
|
||||
|
|
@ -157,4 +157,4 @@ for:
|
|||
test_script:
|
||||
# The server tests often fail in CI when run together so just run a single test to ensure
|
||||
# that the thin client works on windows
|
||||
- sbt "-Dsbt.io.virtual=false" "scripted actions/* classloader-cache/* nio/* watch/*" "serverTestProj/testOnly testpkg.ClientTest"
|
||||
- sbt "-Dsbt.io.virtual=false" "scripted actions/* reporter/source-mapper classloader-cache/* nio/* watch/*" "serverTestProj/testOnly testpkg.ClientTest"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ matrix:
|
|||
include:
|
||||
- env:
|
||||
- SBT_LOCAL=true
|
||||
- SBT_VERSION_PROP=-Dsbt.version=1.4.1-SNAPSHOT
|
||||
- SBT_VERSION_PROP=-Dsbt.version=1.4.2-SNAPSHOT
|
||||
- TRAVIS_JDK=adopt@1.8.0-222
|
||||
- SBT_CMD="++$SCALA_213; $UTIL_TESTS; ++$SCALA_212; $UTIL_TESTS; scripted actions/* source-dependencies/*1of3 dependency-management/*1of4 java/*"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[](https://travis-ci.org/sbt/sbt)
|
||||
[](https://travis-ci.com/github/sbt/sbt)
|
||||
[](https://index.scala-lang.org/sbt/sbt)
|
||||
[](https://gitter.im/sbt/sbt)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ import java.nio.file.{ Files, Path => JPath }
|
|||
import scala.util.Try
|
||||
|
||||
ThisBuild / version := {
|
||||
val v = "1.4.1-SNAPSHOT"
|
||||
// update .travis.yml too for dog fooding
|
||||
val v = "1.4.2-SNAPSHOT"
|
||||
nightlyVersion.getOrElse(v)
|
||||
}
|
||||
ThisBuild / versionScheme := Some("early-semver")
|
||||
ThisBuild / scalafmtOnCompile := !(Global / insideCI).value
|
||||
ThisBuild / Test / scalafmtOnCompile := !(Global / insideCI).value
|
||||
ThisBuild / turbo := true
|
||||
ThisBuild / usePipelining := !(Global / insideCI).value
|
||||
ThisBuild / usePipelining := false // !(Global / insideCI).value
|
||||
|
||||
val excludeLint = SettingKey[Set[Def.KeyedInitialize[_]]]("excludeLintKeys")
|
||||
Global / excludeLint := (Global / excludeLint).?.value.getOrElse(Set.empty)
|
||||
|
|
|
|||
|
|
@ -495,8 +495,10 @@ trait Appender extends AutoCloseable {
|
|||
// the output may have unwanted colors but it would still be legible. This should
|
||||
// only be relevant if the log message string itself contains ansi escape sequences
|
||||
// other than color codes which is very unlikely.
|
||||
val toWrite = if (!ansiCodesSupported) {
|
||||
if (useFormat) EscHelpers.stripMoves(msg) else EscHelpers.removeEscapeSequences(msg)
|
||||
val toWrite = if (!ansiCodesSupported || !useFormat && msg.getBytes.contains(27.toByte)) {
|
||||
val (bytes, len) =
|
||||
EscHelpers.strip(msg.getBytes, stripAnsi = !ansiCodesSupported, stripColor = !useFormat)
|
||||
new String(bytes, 0, len)
|
||||
} else msg
|
||||
out.println(toWrite)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,32 +126,76 @@ object EscHelpers {
|
|||
}
|
||||
index
|
||||
}
|
||||
def stripMoves(s: String): String = {
|
||||
val bytes = s.getBytes
|
||||
|
||||
/**
|
||||
* Strips ansi escape and color codes from an input string.
|
||||
*
|
||||
* @param bytes the input bytes
|
||||
* @param stripAnsi toggles whether or not to remove general ansi escape codes
|
||||
* @param stripColor toggles whether or not to remove ansi color codes
|
||||
* @return a string with the escape and color codes removed depending on the input
|
||||
* parameter along with the length of the output string (which may be smaller than
|
||||
* the returned array)
|
||||
*/
|
||||
def strip(bytes: Array[Byte], stripAnsi: Boolean, stripColor: Boolean): (Array[Byte], Int) = {
|
||||
val res = Array.fill[Byte](bytes.length)(0)
|
||||
var i = 0
|
||||
var index = 0
|
||||
var lastEscapeIndex = -1
|
||||
var state = 0
|
||||
def set(b: Byte) = {
|
||||
res(index) = b
|
||||
index += 1
|
||||
}
|
||||
var limit = 0
|
||||
val digit = new ArrayBuffer[Byte]
|
||||
var leftDigit = -1
|
||||
var escIndex = -1
|
||||
bytes.foreach { b =>
|
||||
set(b)
|
||||
if (index < res.length) res(index) = b
|
||||
index += 1
|
||||
limit = math.max(limit, index)
|
||||
if (state == 0) escIndex = -1
|
||||
b match {
|
||||
case 27 =>
|
||||
escIndex = index - 1
|
||||
state = esc
|
||||
lastEscapeIndex = math.max(0, index)
|
||||
case b if b == '[' && state == esc => state = csi
|
||||
case 'm' => state = 0
|
||||
case b if state == csi && (b < 48 || b >= 58) && b != ';' =>
|
||||
case b if (state == esc || state == csi) && b >= 48 && b < 58 =>
|
||||
state = csi
|
||||
digit += b
|
||||
case '[' if state == esc => state = csi
|
||||
case 8 =>
|
||||
state = 0
|
||||
index = math.max(0, lastEscapeIndex - 1)
|
||||
case b =>
|
||||
index = math.max(index - 1, 0)
|
||||
case b if state == csi =>
|
||||
leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
|
||||
state = 0
|
||||
b.toChar match {
|
||||
case 'h' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'J' | 'K' =>
|
||||
if (stripAnsi) index = math.max(escIndex, 0)
|
||||
case 'm' => if (stripColor) index = escIndex
|
||||
case ';' | 's' | 'u' | '?' => state = csi
|
||||
case b =>
|
||||
}
|
||||
digit.clear()
|
||||
case b if state == esc => state = 0
|
||||
case b =>
|
||||
}
|
||||
}
|
||||
new String(res, 0, index)
|
||||
(res, index)
|
||||
}
|
||||
@deprecated("use EscHelpers.strip", "1.4.2")
|
||||
def stripMoves(s: String): String = {
|
||||
val (bytes, len) = strip(s.getBytes, stripAnsi = true, stripColor = false)
|
||||
new String(bytes, 0, len)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the ansi escape sequences from a string and makes a best attempt at
|
||||
* calculating any ansi moves by hand. For example, if the string contains
|
||||
* a backspace character followed by a character, the output string would
|
||||
* replace the character preceding the backspaces with the character proceding it.
|
||||
* This is in contrast to `strip` which just removes all ansi codes entirely.
|
||||
*
|
||||
* @param s the input string
|
||||
* @return a string containing the original characters of the input stream with
|
||||
* the ansi escape codes removed.
|
||||
*/
|
||||
def stripColorsAndMoves(s: String): String = {
|
||||
val bytes = s.getBytes
|
||||
val res = Array.fill[Byte](bytes.length)(0)
|
||||
|
|
@ -174,6 +218,7 @@ object EscHelpers {
|
|||
leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
|
||||
state = 0
|
||||
b.toChar match {
|
||||
case 'h' => index = math.max(index - 1, 0)
|
||||
case 'D' => index = math.max(index - leftDigit, 0)
|
||||
case 'C' => index = math.min(limit, math.min(index + leftDigit, res.length - 1))
|
||||
case 'K' | 'J' =>
|
||||
|
|
@ -190,6 +235,7 @@ object EscHelpers {
|
|||
index += 1
|
||||
limit = math.max(limit, index)
|
||||
}
|
||||
(res, limit)
|
||||
new String(res, 0, limit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,12 @@ private[sbt] object JLine3 {
|
|||
override val output: OutputStream = new OutputStream {
|
||||
override def write(b: Int): Unit = write(Array[Byte](b.toByte))
|
||||
override def write(b: Array[Byte]): Unit = if (!closed.get) term.withPrintStream { ps =>
|
||||
ps.write(b)
|
||||
val (toWrite, len) = if (b.contains(27.toByte)) {
|
||||
if (!term.isAnsiSupported || !term.isColorEnabled) {
|
||||
EscHelpers.strip(b, !term.isAnsiSupported, !term.isColorEnabled)
|
||||
} else (b, b.length)
|
||||
} else (b, b.length)
|
||||
if (len == toWrite.length) ps.write(toWrite) else ps.write(toWrite, 0, len)
|
||||
term.prompt match {
|
||||
case a: Prompt.AskUser => a.write(b)
|
||||
case _ =>
|
||||
|
|
|
|||
|
|
@ -314,7 +314,10 @@ object Terminal {
|
|||
private[this] def useColorDefault: Boolean = {
|
||||
// This approximates that both stdin and stdio are connected,
|
||||
// so by default color will be turned off for pipes and redirects.
|
||||
props.map(_.color).orElse(isColorEnabledProp).getOrElse((hasConsole && !isDumbTerminal) || isCI)
|
||||
props
|
||||
.map(_.color)
|
||||
.orElse(isColorEnabledProp)
|
||||
.getOrElse((hasConsole && !isDumbTerminal && logFormatEnabled.getOrElse(true)) || isCI)
|
||||
}
|
||||
private[this] lazy val isColorEnabledProp: Option[Boolean] =
|
||||
sys.props.get("sbt.color").orElse(sys.props.get("sbt.colour")).flatMap(parseLogOption)
|
||||
|
|
@ -927,7 +930,14 @@ object Terminal {
|
|||
}
|
||||
override def flush(): Unit = combinedOutputStream.flush()
|
||||
}
|
||||
private def doWrite(bytes: Array[Byte]): Unit = withPrintStream { ps =>
|
||||
private def doWrite(rawBytes: Array[Byte]): Unit = withPrintStream { ps =>
|
||||
val (toWrite, len) =
|
||||
if (rawBytes.contains(27.toByte)) {
|
||||
if (!isAnsiSupported || !isColorEnabled)
|
||||
EscHelpers.strip(rawBytes, stripAnsi = !isAnsiSupported, stripColor = !isColorEnabled)
|
||||
else (rawBytes, rawBytes.length)
|
||||
} else (rawBytes, rawBytes.length)
|
||||
val bytes = if (len < toWrite.length) toWrite.take(len) else toWrite
|
||||
progressState.write(TerminalImpl.this, bytes, ps, hasProgress.get && !rawMode.get)
|
||||
}
|
||||
override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream)
|
||||
|
|
|
|||
|
|
@ -45,23 +45,25 @@ class CleanStringSpec extends FlatSpec {
|
|||
}
|
||||
it should "remove moves in string with only moves" in {
|
||||
val original =
|
||||
new String(Array[Byte](27, 91, 50, 75, 27, 91, 51, 65, 27, 91, 49, 48, 48, 48, 68))
|
||||
assert(EscHelpers.stripMoves(original) == "")
|
||||
Array[Byte](27, 91, 50, 75, 27, 91, 51, 65, 27, 91, 49, 48, 48, 48, 68)
|
||||
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = true)
|
||||
assert(len == 0)
|
||||
}
|
||||
it should "remove moves in string with moves and letters" in {
|
||||
val original = new String(
|
||||
val original =
|
||||
Array[Byte](27, 91, 50, 75, 27, 91, 51, 65) ++ "foo".getBytes ++ Array[Byte](27, 91, 49, 48,
|
||||
48, 48, 68)
|
||||
)
|
||||
assert(EscHelpers.stripMoves(original) == "foo")
|
||||
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = true)
|
||||
assert(new String(bytes, 0, len) == "foo")
|
||||
}
|
||||
it should "preserve colors" in {
|
||||
val original = new String(
|
||||
val original =
|
||||
Array[Byte](27, 91, 49, 48, 48, 48, 68, 27, 91, 48, 74, 102, 111, 111, 27, 91, 51, 54, 109,
|
||||
62, 32, 27, 91, 48, 109)
|
||||
) // this is taken from an sbt prompt that looks like "foo> " with the > rendered blue
|
||||
// this is taken from an sbt prompt that looks like "foo> " with the > rendered blue
|
||||
val colorArrow = new String(Array[Byte](27, 91, 51, 54, 109, 62))
|
||||
assert(EscHelpers.stripMoves(original) == "foo" + colorArrow + " " + scala.Console.RESET)
|
||||
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = false)
|
||||
assert(new String(bytes, 0, len) == "foo" + colorArrow + " " + scala.Console.RESET)
|
||||
}
|
||||
it should "remove unusual escape characters" in {
|
||||
val original = new String(
|
||||
|
|
@ -70,4 +72,28 @@ class CleanStringSpec extends FlatSpec {
|
|||
)
|
||||
assert(EscHelpers.stripColorsAndMoves(original).isEmpty)
|
||||
}
|
||||
it should "remove bracketed paste csi" in {
|
||||
// taken from a test project prompt
|
||||
val original =
|
||||
Array[Byte](27, 91, 63, 50, 48, 48, 52, 104, 115, 98, 116, 58, 114, 101, 112, 114, 111, 62,
|
||||
32)
|
||||
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = false)
|
||||
assert(new String(bytes, 0, len) == "sbt:repro> ")
|
||||
}
|
||||
it should "strip colors" in {
|
||||
// taken from utest output
|
||||
val original =
|
||||
Array[Byte](91, 105, 110, 102, 111, 93, 32, 27, 91, 51, 50, 109, 43, 27, 91, 51, 57, 109, 32,
|
||||
99, 111, 109, 46, 97, 99, 109, 101, 46, 67, 111, 121, 111, 116, 101, 84, 101, 115, 116, 46,
|
||||
109, 97, 107, 101, 84, 114, 97, 112, 32, 27, 91, 50, 109, 57, 109, 115, 27, 91, 48, 109, 32,
|
||||
32, 27, 91, 48, 74, 10)
|
||||
val (bytes, len) = EscHelpers.strip(original, stripAnsi = false, stripColor = true)
|
||||
val expected = "[info] + com.acme.CoyoteTest.makeTrap 9ms " +
|
||||
new String(Array[Byte](27, 91, 48, 74, 10))
|
||||
assert(new String(bytes, 0, len) == expected)
|
||||
|
||||
val (bytes2, len2) = EscHelpers.strip(original, stripAnsi = true, stripColor = true)
|
||||
val expected2 = "[info] + com.acme.CoyoteTest.makeTrap 9ms \n"
|
||||
assert(new String(bytes2, 0, len2) == expected2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package sbt.internal.client
|
|||
|
||||
import java.io.{ File, InputStream, OutputStream }
|
||||
import java.net.Socket
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import sbt.Exit
|
||||
import sbt.io.syntax._
|
||||
|
|
@ -18,18 +19,37 @@ import scala.sys.process.Process
|
|||
import scala.util.control.NonFatal
|
||||
|
||||
class BspClient private (sbtServer: Socket) {
|
||||
private val lock = new AnyRef
|
||||
private var terminated = false
|
||||
private def run(): Exit = Exit(BspClient.bspRun(sbtServer))
|
||||
}
|
||||
|
||||
private def transferTo(input: InputStream, output: OutputStream): Thread = {
|
||||
object BspClient {
|
||||
private[sbt] def bspRun(sbtServer: Socket): Int = {
|
||||
val lock = new AnyRef
|
||||
val terminated = new AtomicBoolean(false)
|
||||
transferTo(terminated, lock, sbtServer.getInputStream, System.out).start()
|
||||
transferTo(terminated, lock, System.in, sbtServer.getOutputStream).start()
|
||||
try {
|
||||
lock.synchronized {
|
||||
while (!terminated.get) lock.wait()
|
||||
}
|
||||
0
|
||||
} catch { case _: Throwable => 1 } finally sbtServer.close()
|
||||
}
|
||||
|
||||
private[sbt] def transferTo(
|
||||
terminated: AtomicBoolean,
|
||||
lock: AnyRef,
|
||||
input: InputStream,
|
||||
output: OutputStream
|
||||
): Thread = {
|
||||
val thread = new Thread {
|
||||
override def run(): Unit = {
|
||||
val buffer = Array.ofDim[Byte](1024)
|
||||
try {
|
||||
while (!terminated) {
|
||||
while (!terminated.get) {
|
||||
val size = input.read(buffer)
|
||||
if (size == -1) {
|
||||
terminated = true
|
||||
terminated.set(true)
|
||||
} else {
|
||||
output.write(buffer, 0, size)
|
||||
output.flush()
|
||||
|
|
@ -38,10 +58,11 @@ class BspClient private (sbtServer: Socket) {
|
|||
input.close()
|
||||
output.close()
|
||||
} catch {
|
||||
case NonFatal(_) => ()
|
||||
case _: InterruptedException => terminated.set(true)
|
||||
case NonFatal(_) => ()
|
||||
} finally {
|
||||
lock.synchronized {
|
||||
terminated = true
|
||||
terminated.set(true)
|
||||
lock.notify()
|
||||
}
|
||||
}
|
||||
|
|
@ -50,24 +71,6 @@ class BspClient private (sbtServer: Socket) {
|
|||
thread.setDaemon(true)
|
||||
thread
|
||||
}
|
||||
|
||||
private def run(): Exit = {
|
||||
try {
|
||||
transferTo(sbtServer.getInputStream, System.out).start()
|
||||
transferTo(System.in, sbtServer.getOutputStream).start()
|
||||
|
||||
lock.synchronized {
|
||||
while (!terminated) lock.wait()
|
||||
}
|
||||
|
||||
Exit(0)
|
||||
} catch {
|
||||
case NonFatal(_) => Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object BspClient {
|
||||
def run(configuration: xsbti.AppConfiguration): Exit = {
|
||||
val baseDirectory = configuration.baseDirectory
|
||||
val portFile = baseDirectory / "project" / "target" / "active.json"
|
||||
|
|
|
|||
|
|
@ -164,8 +164,10 @@ class NetworkClient(
|
|||
case _ =>
|
||||
}
|
||||
|
||||
// Open server connection based on the portfile
|
||||
def init(promptCompleteUsers: Boolean, retry: Boolean): ServerConnection =
|
||||
private[sbt] def connectOrStartServerAndConnect(
|
||||
promptCompleteUsers: Boolean,
|
||||
retry: Boolean
|
||||
): (Socket, Option[String]) =
|
||||
try {
|
||||
if (!portfile.exists) {
|
||||
if (promptCompleteUsers) {
|
||||
|
|
@ -208,88 +210,94 @@ class NetworkClient(
|
|||
connect(attempt + 1)
|
||||
}
|
||||
}
|
||||
val (sk, tkn) = connect(0)
|
||||
val conn = new ServerConnection(sk) {
|
||||
override def onNotification(msg: JsonRpcNotificationMessage): Unit = {
|
||||
msg.method match {
|
||||
case `Shutdown` =>
|
||||
val (log, rebootCommands) = msg.params match {
|
||||
case Some(jvalue) =>
|
||||
Converter
|
||||
.fromJson[(Boolean, Option[(String, String)])](jvalue)
|
||||
.getOrElse((true, None))
|
||||
case _ => (false, None)
|
||||
}
|
||||
if (rebootCommands.nonEmpty) {
|
||||
rebooting.set(true)
|
||||
attached.set(false)
|
||||
connectionHolder.getAndSet(null) match {
|
||||
case null =>
|
||||
case c => c.shutdown()
|
||||
}
|
||||
waitForServer(portfile, true, false)
|
||||
init(promptCompleteUsers = false, retry = false)
|
||||
attachUUID.set(sendJson(attach, s"""{"interactive": ${!batchMode.get}}"""))
|
||||
rebooting.set(false)
|
||||
rebootCommands match {
|
||||
case Some((execId, cmd)) if execId.nonEmpty =>
|
||||
if (batchMode.get && !pendingResults.containsKey(execId) && cmd.nonEmpty) {
|
||||
console.appendLog(
|
||||
Level.Error,
|
||||
s"received request to re-run unknown command '$cmd' after reboot"
|
||||
)
|
||||
} else if (cmd.nonEmpty) {
|
||||
if (batchMode.get) sendCommand(ExecCommand(cmd, execId))
|
||||
else
|
||||
inLock.synchronized {
|
||||
val toSend = cmd.getBytes :+ '\r'.toByte
|
||||
toSend.foreach(b => sendNotification(systemIn, b.toString))
|
||||
}
|
||||
} else completeExec(execId, 0)
|
||||
case _ =>
|
||||
}
|
||||
} else {
|
||||
if (!rebooting.get() && running.compareAndSet(true, false) && log) {
|
||||
if (!arguments.commandArguments.contains(Shutdown)) {
|
||||
console.appendLog(Level.Error, "sbt server disconnected")
|
||||
exitClean.set(false)
|
||||
}
|
||||
} else {
|
||||
console.appendLog(Level.Info, s"${if (log) "sbt server " else ""}disconnected")
|
||||
}
|
||||
stdinBytes.offer(-1)
|
||||
Option(inputThread.get).foreach(_.close())
|
||||
Option(interactiveThread.get).foreach(_.interrupt)
|
||||
}
|
||||
case `readSystemIn` => startInputThread()
|
||||
case `cancelReadSystemIn` =>
|
||||
inputThread.get match {
|
||||
case null =>
|
||||
case t => t.close()
|
||||
}
|
||||
case _ => self.onNotification(msg)
|
||||
}
|
||||
}
|
||||
override def onRequest(msg: JsonRpcRequestMessage): Unit = self.onRequest(msg)
|
||||
override def onResponse(msg: JsonRpcResponseMessage): Unit = self.onResponse(msg)
|
||||
override def onShutdown(): Unit = if (!rebooting.get) {
|
||||
if (exitClean.get != false) exitClean.set(!running.get)
|
||||
running.set(false)
|
||||
Option(interactiveThread.get).foreach(_.interrupt())
|
||||
}
|
||||
}
|
||||
// initiate handshake
|
||||
val execId = UUID.randomUUID.toString
|
||||
val initCommand = InitCommand(tkn, Option(execId), Some(true))
|
||||
conn.sendString(Serialization.serializeCommandAsJsonMessage(initCommand))
|
||||
connectionHolder.set(conn)
|
||||
conn
|
||||
connect(0)
|
||||
} catch {
|
||||
case e: ConnectionRefusedException if retry =>
|
||||
if (Files.deleteIfExists(portfile.toPath)) init(promptCompleteUsers, retry = false)
|
||||
if (Files.deleteIfExists(portfile.toPath))
|
||||
connectOrStartServerAndConnect(promptCompleteUsers, retry = false)
|
||||
else throw e
|
||||
}
|
||||
|
||||
// Open server connection based on the portfile
|
||||
def init(promptCompleteUsers: Boolean, retry: Boolean): ServerConnection = {
|
||||
val (sk, tkn) = connectOrStartServerAndConnect(promptCompleteUsers, retry)
|
||||
val conn = new ServerConnection(sk) {
|
||||
override def onNotification(msg: JsonRpcNotificationMessage): Unit = {
|
||||
msg.method match {
|
||||
case `Shutdown` =>
|
||||
val (log, rebootCommands) = msg.params match {
|
||||
case Some(jvalue) =>
|
||||
Converter
|
||||
.fromJson[(Boolean, Option[(String, String)])](jvalue)
|
||||
.getOrElse((true, None))
|
||||
case _ => (false, None)
|
||||
}
|
||||
if (rebootCommands.nonEmpty) {
|
||||
rebooting.set(true)
|
||||
attached.set(false)
|
||||
connectionHolder.getAndSet(null) match {
|
||||
case null =>
|
||||
case c => c.shutdown()
|
||||
}
|
||||
waitForServer(portfile, true, false)
|
||||
init(promptCompleteUsers = false, retry = false)
|
||||
attachUUID.set(sendJson(attach, s"""{"interactive": ${!batchMode.get}}"""))
|
||||
rebooting.set(false)
|
||||
rebootCommands match {
|
||||
case Some((execId, cmd)) if execId.nonEmpty =>
|
||||
if (batchMode.get && !pendingResults.containsKey(execId) && cmd.nonEmpty) {
|
||||
console.appendLog(
|
||||
Level.Error,
|
||||
s"received request to re-run unknown command '$cmd' after reboot"
|
||||
)
|
||||
} else if (cmd.nonEmpty) {
|
||||
if (batchMode.get) sendCommand(ExecCommand(cmd, execId))
|
||||
else
|
||||
inLock.synchronized {
|
||||
val toSend = cmd.getBytes :+ '\r'.toByte
|
||||
toSend.foreach(b => sendNotification(systemIn, b.toString))
|
||||
}
|
||||
} else completeExec(execId, 0)
|
||||
case _ =>
|
||||
}
|
||||
} else {
|
||||
if (!rebooting.get() && running.compareAndSet(true, false) && log) {
|
||||
if (!arguments.commandArguments.contains(Shutdown)) {
|
||||
console.appendLog(Level.Error, "sbt server disconnected")
|
||||
exitClean.set(false)
|
||||
}
|
||||
} else {
|
||||
console.appendLog(Level.Info, s"${if (log) "sbt server " else ""}disconnected")
|
||||
}
|
||||
stdinBytes.offer(-1)
|
||||
Option(inputThread.get).foreach(_.close())
|
||||
Option(interactiveThread.get).foreach(_.interrupt)
|
||||
}
|
||||
case `readSystemIn` => startInputThread()
|
||||
case `cancelReadSystemIn` =>
|
||||
inputThread.get match {
|
||||
case null =>
|
||||
case t => t.close()
|
||||
}
|
||||
case _ => self.onNotification(msg)
|
||||
}
|
||||
}
|
||||
override def onRequest(msg: JsonRpcRequestMessage): Unit = self.onRequest(msg)
|
||||
override def onResponse(msg: JsonRpcResponseMessage): Unit = self.onResponse(msg)
|
||||
override def onShutdown(): Unit = if (!rebooting.get) {
|
||||
if (exitClean.get != false) exitClean.set(!running.get)
|
||||
running.set(false)
|
||||
Option(interactiveThread.get).foreach(_.interrupt())
|
||||
}
|
||||
}
|
||||
// initiate handshake
|
||||
val execId = UUID.randomUUID.toString
|
||||
val initCommand = InitCommand(tkn, Option(execId), Some(true))
|
||||
conn.sendString(Serialization.serializeCommandAsJsonMessage(initCommand))
|
||||
connectionHolder.set(conn)
|
||||
conn
|
||||
}
|
||||
|
||||
/**
|
||||
* Forks another instance of sbt in the background.
|
||||
* This instance must be shutdown explicitly via `sbt -client shutdown`
|
||||
|
|
@ -1006,9 +1014,10 @@ object NetworkClient {
|
|||
val commandArguments: Seq[String],
|
||||
val completionArguments: Seq[String],
|
||||
val sbtScript: String,
|
||||
val bsp: Boolean,
|
||||
) {
|
||||
def withBaseDirectory(file: File): Arguments =
|
||||
new Arguments(file, sbtArguments, commandArguments, completionArguments, sbtScript)
|
||||
new Arguments(file, sbtArguments, commandArguments, completionArguments, sbtScript, bsp)
|
||||
}
|
||||
private[client] val completions = "--completions"
|
||||
private[client] val noTab = "--no-tab"
|
||||
|
|
@ -1016,6 +1025,7 @@ object NetworkClient {
|
|||
private[client] val sbtBase = "--sbt-base-directory"
|
||||
private[client] def parseArgs(args: Array[String]): Arguments = {
|
||||
var sbtScript = if (Properties.isWin) "sbt.bat" else "sbt"
|
||||
var bsp = false
|
||||
val commandArgs = new mutable.ArrayBuffer[String]
|
||||
val sbtArguments = new mutable.ArrayBuffer[String]
|
||||
val completionArguments = new mutable.ArrayBuffer[String]
|
||||
|
|
@ -1032,10 +1042,15 @@ object NetworkClient {
|
|||
case a if a == noStdErr || a == noTab || a.startsWith(completions) =>
|
||||
completionArguments += a
|
||||
case a if a.startsWith("--sbt-script=") =>
|
||||
sbtScript = a.split("--sbt-script=").lastOption.getOrElse(sbtScript)
|
||||
sbtScript = a
|
||||
.split("--sbt-script=")
|
||||
.lastOption
|
||||
.map(_.replaceAllLiterally("%20", " "))
|
||||
.getOrElse(sbtScript)
|
||||
case "-bsp" | "--bsp" => bsp = true
|
||||
case "--sbt-script" if i + 1 < sanitized.length =>
|
||||
i += 1
|
||||
sbtScript = sanitized(i)
|
||||
sbtScript = sanitized(i).replaceAllLiterally("%20", " ")
|
||||
case a if !a.startsWith("-") => commandArgs += a
|
||||
case a @ SysProp(key, value) =>
|
||||
System.setProperty(key, value)
|
||||
|
|
@ -1046,7 +1061,7 @@ object NetworkClient {
|
|||
}
|
||||
val base = new File("").getCanonicalFile
|
||||
if (!sbtArguments.contains("-Dsbt.io.virtual=true")) sbtArguments += "-Dsbt.io.virtual=true"
|
||||
new Arguments(base, sbtArguments, commandArgs, completionArguments, sbtScript)
|
||||
new Arguments(base, sbtArguments, commandArgs, completionArguments, sbtScript, bsp)
|
||||
}
|
||||
|
||||
def client(
|
||||
|
|
@ -1072,7 +1087,7 @@ object NetworkClient {
|
|||
}
|
||||
def client(
|
||||
baseDirectory: File,
|
||||
args: Array[String],
|
||||
args: Arguments,
|
||||
inputStream: InputStream,
|
||||
errorStream: PrintStream,
|
||||
terminal: Terminal,
|
||||
|
|
@ -1080,17 +1095,32 @@ object NetworkClient {
|
|||
): Int = {
|
||||
val client =
|
||||
simpleClient(
|
||||
NetworkClient.parseArgs(args).withBaseDirectory(baseDirectory),
|
||||
args.withBaseDirectory(baseDirectory),
|
||||
inputStream,
|
||||
errorStream,
|
||||
useJNI,
|
||||
terminal
|
||||
)
|
||||
try {
|
||||
if (client.connect(log = true, promptCompleteUsers = false)) client.run()
|
||||
else 1
|
||||
if (args.bsp) {
|
||||
val (socket, _) =
|
||||
client.connectOrStartServerAndConnect(promptCompleteUsers = false, retry = true)
|
||||
BspClient.bspRun(socket)
|
||||
} else {
|
||||
if (client.connect(log = true, promptCompleteUsers = false)) client.run()
|
||||
else 1
|
||||
}
|
||||
} catch { case _: Exception => 1 } finally client.close()
|
||||
}
|
||||
def client(
|
||||
baseDirectory: File,
|
||||
args: Array[String],
|
||||
inputStream: InputStream,
|
||||
errorStream: PrintStream,
|
||||
terminal: Terminal,
|
||||
useJNI: Boolean
|
||||
): Int = client(baseDirectory, parseArgs(args), inputStream, errorStream, terminal, useJNI)
|
||||
|
||||
private def simpleClient(
|
||||
arguments: Arguments,
|
||||
inputStream: InputStream,
|
||||
|
|
@ -1129,9 +1159,10 @@ object NetworkClient {
|
|||
})
|
||||
Runtime.getRuntime.addShutdownHook(hook)
|
||||
if (Util.isNonCygwinWindows) sbt.internal.util.JLine3.forceWindowsJansi()
|
||||
val parsed = parseArgs(restOfArgs)
|
||||
System.exit(Terminal.withStreams(false) {
|
||||
val term = Terminal.console
|
||||
try client(base, restOfArgs, term.inputStream, System.err, term, useJNI)
|
||||
try client(base, parsed, term.inputStream, System.err, term, useJNI)
|
||||
catch { case _: AccessDeniedException => 1 } finally {
|
||||
Runtime.getRuntime.removeShutdownHook(hook)
|
||||
hook.run()
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ object JoinThread {
|
|||
t.interrupt()
|
||||
t.join(10)
|
||||
} catch { case e: InterruptedException => exception = Some(e) }
|
||||
if (t.isAlive) impl()
|
||||
if (t.isAlive && !deadline.isOverdue) impl()
|
||||
}
|
||||
impl()
|
||||
if (t.isAlive) System.err.println(s"Unable to join thread $t after $duration")
|
||||
exception.foreach(throw _)
|
||||
if (t.isAlive) {
|
||||
System.err.println(s"Unable to join thread $t after $duration")
|
||||
exception.foreach(throw _)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import org.apache.logging.log4j.core.{ Appender => XAppender }
|
|||
import org.scalasbt.ipcsocket.Win32SecurityLevel
|
||||
import sbt.Def.{ Initialize, ScopedKey, Setting, SettingsDefinition }
|
||||
import sbt.Keys._
|
||||
import sbt.OptionSyntax._
|
||||
import sbt.Project.{
|
||||
inConfig,
|
||||
inScope,
|
||||
|
|
@ -91,6 +92,7 @@ import xsbti.{ FileConverter, Position }
|
|||
|
||||
import scala.collection.immutable.ListMap
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
import scala.util.control.NonFatal
|
||||
import scala.xml.NodeSeq
|
||||
|
||||
|
|
@ -389,6 +391,8 @@ object Defaults extends BuildCommon {
|
|||
canonicalInput :== true,
|
||||
echoInput :== true,
|
||||
terminal := state.value.get(terminalKey).getOrElse(Terminal(ITerminal.get)),
|
||||
InstallSbtn.installSbtn := InstallSbtn.installSbtnImpl.evaluated,
|
||||
InstallSbtn.installSbtn / aggregate := false,
|
||||
) ++ LintUnused.lintSettings
|
||||
++ DefaultBackgroundJobService.backgroundJobServiceSettings
|
||||
++ RemoteCache.globalSettings
|
||||
|
|
@ -415,7 +419,7 @@ object Defaults extends BuildCommon {
|
|||
sourcePositionMappers ++= {
|
||||
val fc = fileConverter.value
|
||||
if (reportAbsolutePath.value) {
|
||||
List(toAbsoluteSourceMapper(fc))
|
||||
List(toAbsoluteSourceMapper(fc) _)
|
||||
} else Nil
|
||||
},
|
||||
// The virtual file value cache needs to be global or sbt will run out of direct byte buffer memory.
|
||||
|
|
@ -464,13 +468,21 @@ object Defaults extends BuildCommon {
|
|||
},
|
||||
)
|
||||
|
||||
private[sbt] def toAbsoluteSourceMapper(fc: FileConverter): Position => Option[Position] = {
|
||||
pos =>
|
||||
val newPath: Optional[String] = pos.sourcePath
|
||||
.map { id =>
|
||||
fc.toPath(VirtualFileRef.of(id)).toAbsolutePath.toString
|
||||
}
|
||||
Some(
|
||||
private[sbt] def toAbsoluteSourceMapper(fc: FileConverter)(pos: Position): Option[Position] = {
|
||||
def isValid(path: String): Boolean = {
|
||||
Try(Paths.get(path)).map(_ => true).getOrElse(false)
|
||||
}
|
||||
|
||||
val newPath: Option[String] = pos
|
||||
.sourcePath()
|
||||
.asScala
|
||||
.filter(isValid)
|
||||
.map { path =>
|
||||
fc.toPath(VirtualFileRef.of(path)).toAbsolutePath.toString
|
||||
}
|
||||
|
||||
newPath
|
||||
.map { path =>
|
||||
new Position {
|
||||
override def line(): Optional[Integer] = pos.line()
|
||||
|
||||
|
|
@ -482,11 +494,12 @@ object Defaults extends BuildCommon {
|
|||
|
||||
override def pointerSpace(): Optional[String] = pos.pointerSpace()
|
||||
|
||||
override def sourcePath(): Optional[String] = newPath
|
||||
override def sourcePath(): Optional[String] = Optional.of(path)
|
||||
|
||||
override def sourceFile(): Optional[File] = pos.sourceFile()
|
||||
}
|
||||
)
|
||||
}
|
||||
.orElse(Some(pos))
|
||||
}
|
||||
|
||||
// csrCacheDirectory is scoped to ThisBuild to allow customization.
|
||||
|
|
@ -2519,6 +2532,14 @@ object Classpaths {
|
|||
excludeFilter in unmanagedJars value
|
||||
)
|
||||
).map(exportClasspath) ++ Seq(
|
||||
externalDependencyClasspath / outputFileStamps := {
|
||||
val stamper = timeWrappedStamper.value
|
||||
val converter = fileConverter.value
|
||||
externalDependencyClasspath.value flatMap { file0 =>
|
||||
val p = file0.data.toPath
|
||||
FileStamp(stamper.library(converter.toVirtualFile(p))).map(p -> _)
|
||||
}
|
||||
},
|
||||
dependencyClasspathFiles := data(dependencyClasspath.value).map(_.toPath),
|
||||
dependencyClasspathFiles / outputFileStamps := {
|
||||
val stamper = timeWrappedStamper.value
|
||||
|
|
|
|||
|
|
@ -386,6 +386,7 @@ object Keys {
|
|||
val pushRemoteCacheArtifact = settingKey[Boolean]("Enables publishing an artifact to remote cache.")
|
||||
val pushRemoteCacheConfiguration = taskKey[PublishConfiguration]("")
|
||||
val pushRemoteCacheTo = settingKey[Option[Resolver]]("The resolver to publish remote cache to.")
|
||||
val remoteCacheResolvers = settingKey[Seq[Resolver]]("Resolvers for remote cache.")
|
||||
val remoteCachePom = taskKey[File]("Generates a pom for publishing when publishing Maven-style.")
|
||||
val usePipelining = settingKey[Boolean]("Use subproject pipelining for compilation.").withRank(BSetting)
|
||||
val exportPipelining = settingKey[Boolean]("Product early output so downstream subprojects can do pipelining.").withRank(BSetting)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
|
||||
import Def._
|
||||
import Keys.{ sbtVersion, state, terminal }
|
||||
|
||||
import java.io.{ File, FileInputStream, FileOutputStream, InputStream, IOException }
|
||||
import java.net.URL
|
||||
import java.nio.file.{ Files, Path }
|
||||
import java.util.zip.ZipInputStream
|
||||
import sbt.io.IO
|
||||
import sbt.io.Path.userHome
|
||||
import sbt.io.syntax._
|
||||
import scala.util.{ Properties, Try }
|
||||
|
||||
private[sbt] object InstallSbtn {
|
||||
private[sbt] val installSbtn =
|
||||
Def.inputKey[Unit]("install sbtn and tab completions").withRank(KeyRanks.BTask)
|
||||
private[sbt] def installSbtnImpl: Def.Initialize[InputTask[Unit]] = Def.inputTask {
|
||||
val inputVersion = Def.spaceDelimited("version").parsed.headOption
|
||||
val version = inputVersion.getOrElse(sbtVersion.value.replaceAllLiterally("-SNAPSHOT", ""))
|
||||
val term = terminal.value
|
||||
term.setMode(canonical = false, echo = false)
|
||||
val baseDirectory = BuildPaths.getGlobalBase(state.value).toPath
|
||||
val tmp = Files.createTempFile(s"sbt-$version", "zip")
|
||||
val sbtn = if (Properties.isWin) "sbtn.exe" else "sbtn"
|
||||
try extractSbtn(term, version, tmp, baseDirectory.resolve("bin").resolve(sbtn))
|
||||
finally {
|
||||
Files.deleteIfExists(tmp)
|
||||
()
|
||||
}
|
||||
val shell = if (System.console != null) getShell(term) else "none"
|
||||
shell match {
|
||||
case "none" =>
|
||||
case s =>
|
||||
val completion = shellCompletions(s)
|
||||
val completionLocation = baseDirectory.resolve("completions").resolve(completion)
|
||||
downloadCompletion(completion, version, completionLocation)
|
||||
s match {
|
||||
case "bash" => setupBash(baseDirectory, term)
|
||||
case "fish" => setupFish(baseDirectory, term)
|
||||
case "zsh" => setupZsh(baseDirectory, term)
|
||||
case "powershell" => setupPowershell(baseDirectory, term)
|
||||
case _ => // should be unreachable
|
||||
}
|
||||
val msg = s"Successfully installed sbtn for $s. You may need to restart $s for the " +
|
||||
"changes to take effect."
|
||||
term.printStream.println(msg)
|
||||
}
|
||||
()
|
||||
}
|
||||
|
||||
private[sbt] def extractSbtn(term: Terminal, version: String, sbtZip: Path, sbtn: Path): Unit = {
|
||||
downloadRelease(term, version, sbtZip)
|
||||
Files.createDirectories(sbtn.getParent)
|
||||
val bin =
|
||||
if (Properties.isWin) "pc-win32.exe"
|
||||
else if (Properties.isLinux) "pc-linux"
|
||||
else "apple-darwin"
|
||||
val sbtnName = s"sbt/bin/sbtn-x86_64-$bin"
|
||||
val fis = new FileInputStream(sbtZip.toFile)
|
||||
val zipInputStream = new ZipInputStream(fis)
|
||||
var foundBinary = false
|
||||
try {
|
||||
var entry = zipInputStream.getNextEntry
|
||||
while (entry != null) {
|
||||
if (entry.getName == sbtnName) {
|
||||
foundBinary = true
|
||||
term.printStream.println(s"extracting $sbtZip!$sbtnName to $sbtn")
|
||||
transfer(zipInputStream, sbtn)
|
||||
sbtn.toFile.setExecutable(true)
|
||||
entry = null
|
||||
} else {
|
||||
entry = zipInputStream.getNextEntry
|
||||
}
|
||||
}
|
||||
if (!foundBinary) throw new IllegalStateException(s"couldn't find $sbtnName in $sbtZip")
|
||||
} finally {
|
||||
fis.close()
|
||||
zipInputStream.close()
|
||||
}
|
||||
()
|
||||
}
|
||||
private[this] def downloadRelease(term: Terminal, version: String, location: Path): Unit = {
|
||||
val zip = s"https://github.com/sbt/sbt/releases/download/v$version/sbt-$version.zip"
|
||||
val url = new URL(zip)
|
||||
term.printStream.println(s"downloading $zip to $location")
|
||||
transfer(url.openStream(), location)
|
||||
}
|
||||
private[this] def transfer(inputStream: InputStream, path: Path): Unit =
|
||||
try {
|
||||
val os = new FileOutputStream(path.toFile)
|
||||
try {
|
||||
val result = new Array[Byte](1024 * 1024)
|
||||
var bytesRead = -1
|
||||
do {
|
||||
bytesRead = inputStream.read(result)
|
||||
if (bytesRead > 0) os.write(result, 0, bytesRead)
|
||||
} while (bytesRead > 0)
|
||||
} finally os.close()
|
||||
} finally inputStream.close()
|
||||
private[this] def getShell(term: Terminal): String = {
|
||||
term.printStream.print(s"""Setup sbtn for shell:
|
||||
| [1] bash
|
||||
| [2] fish
|
||||
| [3] powershell
|
||||
| [4] zsh
|
||||
| [5] none
|
||||
|Enter option: """.stripMargin)
|
||||
term.printStream.flush()
|
||||
val key = term.inputStream.read
|
||||
term.printStream.println(key.toChar)
|
||||
key match {
|
||||
case 49 => "bash"
|
||||
case 50 => "fish"
|
||||
case 51 => "powershell"
|
||||
case 52 => "zsh"
|
||||
case _ => "none"
|
||||
}
|
||||
}
|
||||
private[this] def downloadCompletion(completion: String, version: String, target: Path): Unit = {
|
||||
Files.createDirectories(target.getParent)
|
||||
val comp = s"https://raw.githubusercontent.com/sbt/sbt/v$version/client/completions/$completion"
|
||||
transfer(new URL(comp).openStream, target)
|
||||
}
|
||||
private[this] def setupShell(
|
||||
shell: String,
|
||||
baseDirectory: Path,
|
||||
term: Terminal,
|
||||
configFile: File,
|
||||
setPath: Path => String,
|
||||
setCompletions: Path => String,
|
||||
): Unit = {
|
||||
val bin = baseDirectory.resolve("bin")
|
||||
val export = setPath(bin)
|
||||
val completions = baseDirectory.resolve("completions")
|
||||
val sourceCompletions = setCompletions(completions)
|
||||
val contents = try IO.read(configFile)
|
||||
catch { case _: IOException => "" }
|
||||
if (!contents.contains(export)) {
|
||||
term.printStream.print(s"Add $bin to PATH in $configFile? y/n (y default): ")
|
||||
term.printStream.flush()
|
||||
term.inputStream.read() match {
|
||||
case 110 => term.printStream.println()
|
||||
case c =>
|
||||
term.printStream.println(c.toChar)
|
||||
// put the export at the bottom so that the ~/.sbt/1.0/bin/sbtn is least preferred
|
||||
// but still on the path
|
||||
IO.write(configFile, s"$contents\n$export")
|
||||
}
|
||||
}
|
||||
val newContents = try IO.read(configFile)
|
||||
catch { case _: IOException => "" }
|
||||
if (!newContents.contains(sourceCompletions)) {
|
||||
term.printStream.print(s"Add tab completions to $configFile? y/n (y default): ")
|
||||
term.printStream.flush()
|
||||
term.inputStream.read() match {
|
||||
case 110 =>
|
||||
case c =>
|
||||
term.printStream.println(c.toChar)
|
||||
if (shell == "zsh") {
|
||||
// delete the .zcompdump file because it can prevent the new completions from
|
||||
// being recognized
|
||||
Files.deleteIfExists((userHome / ".zcompdump").toPath)
|
||||
// put the completions at the top because it is effectively just a source
|
||||
// so the order in the file doesn't really matter but we want to make sure
|
||||
// that we set fpath before any autoload command in zsh
|
||||
IO.write(configFile, s"$sourceCompletions\n$newContents")
|
||||
} else {
|
||||
IO.write(configFile, s"$newContents\n$sourceCompletions")
|
||||
}
|
||||
}
|
||||
term.printStream.println()
|
||||
}
|
||||
}
|
||||
private[this] def setupBash(baseDirectory: Path, term: Terminal): Unit =
|
||||
setupShell(
|
||||
"bash",
|
||||
baseDirectory,
|
||||
term,
|
||||
userHome / ".bashrc",
|
||||
bin => s"export PATH=$$PATH:$bin",
|
||||
completions => s"source $completions/sbtn.bash"
|
||||
)
|
||||
private[this] def setupZsh(baseDirectory: Path, term: Terminal): Unit = {
|
||||
val comp = (completions: Path) => {
|
||||
"# The following two lines were added by the sbt installSbtn task:\n" +
|
||||
s"fpath=($$fpath $completions)\nautoload -Uz compinit; compinit"
|
||||
}
|
||||
setupShell("zsh", baseDirectory, term, userHome / ".zshrc", bin => s"path=($$path $bin)", comp)
|
||||
}
|
||||
private[this] def setupFish(baseDirectory: Path, term: Terminal): Unit = {
|
||||
val comp = (completions: Path) => s"source $completions/sbtn.fish"
|
||||
val path = (bin: Path) => s"set PATH $$PATH $bin"
|
||||
val config = userHome / ".config" / "fish" / "config.fish"
|
||||
setupShell("fish", baseDirectory, term, config, path, comp)
|
||||
}
|
||||
private[this] def setupPowershell(baseDirectory: Path, term: Terminal): Unit = {
|
||||
val comp = (completions: Path) => s""". "$completions\\sbtn.ps1""""
|
||||
val path = (bin: Path) => s"""$$env:Path += ";$bin""""
|
||||
import scala.sys.process._
|
||||
Try(Seq("pwsh", "-Command", "echo $PROFILE").!!).foreach { output =>
|
||||
output.linesIterator.toSeq.headOption.foreach { l =>
|
||||
setupShell("pwsh", baseDirectory, term, new File(l), path, comp)
|
||||
}
|
||||
}
|
||||
Try(Seq("powershell", "-Command", "echo $PROFILE").!!).foreach { output =>
|
||||
output.linesIterator.toSeq.headOption.foreach { l =>
|
||||
setupShell("pwsh", baseDirectory, term, new File(l), path, comp)
|
||||
}
|
||||
}
|
||||
}
|
||||
private[this] val shellCompletions = Map(
|
||||
"bash" -> "sbtn.bash",
|
||||
"fish" -> "sbtn.fish",
|
||||
"powershell" -> "sbtn.ps1",
|
||||
"zsh" -> "_sbtn",
|
||||
)
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ object LintUnused {
|
|||
onUnload,
|
||||
sbt.nio.Keys.watchTriggers,
|
||||
serverConnectionType,
|
||||
serverIdleTimeout,
|
||||
shellPrompt,
|
||||
),
|
||||
includeLintKeys := Set(
|
||||
|
|
|
|||
|
|
@ -9,19 +9,29 @@ package sbt
|
|||
package internal
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import Keys._
|
||||
import SlashSyntax0._
|
||||
import ScopeFilter.Make._
|
||||
import Project._ // for tag and inTask()
|
||||
|
||||
import org.apache.ivy.core.module.descriptor.{ Artifact => IArtifact, DefaultArtifact }
|
||||
import org.apache.ivy.core.resolve.DownloadOptions
|
||||
import org.apache.ivy.core.report.DownloadStatus
|
||||
import org.apache.ivy.plugins.resolver.DependencyResolver
|
||||
import std.TaskExtra._ // for join
|
||||
import sbt.coursierint.LMCoursier
|
||||
import sbt.librarymanagement._
|
||||
import sbt.librarymanagement.ivy.Credentials
|
||||
import sbt.librarymanagement.ivy.{ Credentials, IvyPaths, UpdateOptions }
|
||||
import sbt.librarymanagement.syntax._
|
||||
import sbt.nio.FileStamp
|
||||
import sbt.nio.Keys.{ inputFileStamps, outputFileStamps }
|
||||
import sbt.internal.librarymanagement._
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
import sbt.internal.remotecache._
|
||||
import sbt.internal.inc.JarUtils
|
||||
import sbt.internal.inc.{ HashUtil, JarUtils }
|
||||
import sbt.util.InterfaceUtil.toOption
|
||||
import sbt.util.Logger
|
||||
|
||||
object RemoteCache {
|
||||
|
|
@ -41,91 +51,49 @@ object RemoteCache {
|
|||
.map(_.take(commitLength))
|
||||
|
||||
lazy val globalSettings: Seq[Def.Setting[_]] = Seq(
|
||||
remoteCacheId := gitCommitId,
|
||||
remoteCacheIdCandidates := gitCommitIds(5),
|
||||
remoteCacheId := "",
|
||||
remoteCacheIdCandidates := Nil,
|
||||
pushRemoteCacheTo :== None
|
||||
)
|
||||
|
||||
lazy val projectSettings: Seq[Def.Setting[_]] = (Seq(
|
||||
remoteCacheProjectId := {
|
||||
val o = organization.value
|
||||
val m = moduleName.value
|
||||
val id = remoteCacheId.value
|
||||
val c = (projectID / crossVersion).value
|
||||
val v = toVersion(id)
|
||||
ModuleID(o, m, v).cross(c)
|
||||
},
|
||||
pushRemoteCacheConfiguration / publishMavenStyle := true,
|
||||
pushRemoteCacheConfiguration / packagedArtifacts := Def.taskDyn {
|
||||
val artifacts = (pushRemoteCacheConfiguration / remoteCacheArtifacts).value
|
||||
|
||||
artifacts
|
||||
.map(a => a.packaged.map(file => (a.artifact, file)))
|
||||
.join
|
||||
.apply(_.join.map(_.toMap))
|
||||
}.value,
|
||||
pushRemoteCache := (Def.taskDyn {
|
||||
val arts = (pushRemoteCacheConfiguration / remoteCacheArtifacts).value
|
||||
val configs = arts flatMap { art =>
|
||||
art.packaged.scopedKey.scope match {
|
||||
case Scope(_, Select(c), _, _) => Some(c)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
val filter = ScopeFilter(configurations = inConfigurationsByKeys(configs: _*))
|
||||
Def.task {
|
||||
val _ = pushRemoteCache.all(filter).value
|
||||
()
|
||||
}
|
||||
}).value,
|
||||
pullRemoteCache := (Def.taskDyn {
|
||||
val arts = (pushRemoteCacheConfiguration / remoteCacheArtifacts).value
|
||||
val configs = arts flatMap { art =>
|
||||
art.packaged.scopedKey.scope match {
|
||||
case Scope(_, Select(c), _, _) => Some(c)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
val filter = ScopeFilter(configurations = inConfigurationsByKeys(configs: _*))
|
||||
Def.task {
|
||||
val _ = pullRemoteCache.all(filter).value
|
||||
()
|
||||
}
|
||||
}).value,
|
||||
pushRemoteCacheConfiguration / remoteCacheArtifacts := {
|
||||
enabledOnly(remoteCacheArtifact.toSettingKey, defaultArtifactTasks).apply(_.join).value
|
||||
},
|
||||
pushRemoteCacheConfiguration / publishMavenStyle := true,
|
||||
Compile / packageCache / pushRemoteCacheArtifact := true,
|
||||
Test / packageCache / pushRemoteCacheArtifact := true,
|
||||
Compile / packageCache / artifact := Artifact(moduleName.value, cachedCompileClassifier),
|
||||
Test / packageCache / artifact := Artifact(moduleName.value, cachedTestClassifier),
|
||||
remoteCachePom / pushRemoteCacheArtifact := true,
|
||||
pushRemoteCacheConfiguration := {
|
||||
Classpaths.publishConfig(
|
||||
(pushRemoteCacheConfiguration / publishMavenStyle).value,
|
||||
Classpaths.deliverPattern(crossTarget.value),
|
||||
if (isSnapshot.value) "integration" else "release",
|
||||
ivyConfigurations.value.map(c => ConfigRef(c.name)).toVector,
|
||||
(pushRemoteCacheConfiguration / packagedArtifacts).value.toVector,
|
||||
(pushRemoteCacheConfiguration / checksums).value.toVector,
|
||||
Classpaths.getPublishTo(pushRemoteCacheTo.value).name,
|
||||
ivyLoggingLevel.value,
|
||||
isSnapshot.value
|
||||
)
|
||||
},
|
||||
pullRemoteCache := {
|
||||
val log = streams.value.log
|
||||
val smi = scalaModuleInfo.value
|
||||
val dr = (pullRemoteCache / dependencyResolution).value
|
||||
val is = (pushRemoteCache / ivySbt).value
|
||||
val t = crossTarget.value / "cache-download"
|
||||
val p = remoteCacheProjectId.value
|
||||
val ids = remoteCacheIdCandidates.value
|
||||
val artifacts = (pushRemoteCacheConfiguration / remoteCacheArtifacts).value
|
||||
val applicable = artifacts.filterNot(isPomArtifact)
|
||||
val classifiers = applicable.flatMap(_.artifact.classifier).toVector
|
||||
|
||||
var found = false
|
||||
ids foreach {
|
||||
id: String =>
|
||||
val v = toVersion(id)
|
||||
val modId = p.withRevision(v)
|
||||
if (found) ()
|
||||
else
|
||||
pullFromMavenRepo0(modId, classifiers, smi, is, dr, t, log) match {
|
||||
case Right(xs0) =>
|
||||
val jars = xs0.distinct
|
||||
|
||||
applicable.foreach { art =>
|
||||
val classifier = art.artifact.classifier
|
||||
|
||||
findJar(classifier, v, jars) match {
|
||||
case Some(jar) =>
|
||||
extractJar(art, jar)
|
||||
log.info(s"remote cache artifact extracted for $p $classifier")
|
||||
|
||||
case None =>
|
||||
log.info(s"remote cache artifact not found for $p $classifier")
|
||||
}
|
||||
}
|
||||
found = true
|
||||
case Left(unresolvedWarning) =>
|
||||
log.info(s"remote cache not found for ${v}")
|
||||
}
|
||||
}
|
||||
},
|
||||
remoteCachePom := {
|
||||
val s = streams.value
|
||||
val config = (remoteCachePom / makePomConfiguration).value
|
||||
|
|
@ -142,50 +110,40 @@ object RemoteCache {
|
|||
},
|
||||
remoteCachePom / remoteCacheArtifact := {
|
||||
PomRemoteCacheArtifact((makePom / artifact).value, remoteCachePom)
|
||||
}
|
||||
},
|
||||
remoteCacheResolvers := pushRemoteCacheTo.value.toVector,
|
||||
) ++ inTask(pushRemoteCache)(
|
||||
Seq(
|
||||
ivyPaths := IvyPaths(baseDirectory.value, crossTarget.value / "remote-cache"),
|
||||
ivyConfiguration := {
|
||||
val other = pushRemoteCacheTo.value.toVector
|
||||
val config0 = Classpaths.mkIvyConfiguration.value
|
||||
config0
|
||||
.withOtherResolvers(other)
|
||||
.withResolvers(remoteCacheResolvers.value.toVector)
|
||||
.withOtherResolvers(pushRemoteCacheTo.value.toVector)
|
||||
.withResolutionCacheDir(crossTarget.value / "alt-resolution")
|
||||
.withPaths(ivyPaths.value)
|
||||
.withUpdateOptions(UpdateOptions().withGigahorse(true))
|
||||
},
|
||||
ivySbt := {
|
||||
val config0 = ivyConfiguration.value
|
||||
Credentials.register(credentials.value, streams.value.log)
|
||||
val config0 = ivyConfiguration.value
|
||||
new IvySbt(config0, CustomHttp.okhttpClient.value)
|
||||
},
|
||||
ivyModule := {
|
||||
val is = ivySbt.value
|
||||
new is.Module(moduleSettings.value)
|
||||
},
|
||||
moduleSettings := {
|
||||
val smi = scalaModuleInfo.value
|
||||
ModuleDescriptorConfiguration(remoteCacheProjectId.value, projectInfo.value)
|
||||
.withScalaModuleInfo(smi)
|
||||
},
|
||||
pushRemoteCache.in(Defaults.TaskZero) := (Def.task {
|
||||
val s = streams.value
|
||||
val config = pushRemoteCacheConfiguration.value
|
||||
IvyActions.publish(ivyModule.value, config, s.log)
|
||||
} tag (Tags.Publish, Tags.Network)).value
|
||||
)
|
||||
) ++ inTask(pullRemoteCache)(
|
||||
Seq(
|
||||
dependencyResolution := Defaults.dependencyResolutionTask.value,
|
||||
csrConfiguration := {
|
||||
val rs = pushRemoteCacheTo.value.toVector
|
||||
val rs = pushRemoteCacheTo.value.toVector ++ remoteCacheResolvers.value.toVector
|
||||
LMCoursier.scalaCompilerBridgeConfigurationTask.value
|
||||
.withResolvers(rs)
|
||||
}
|
||||
)
|
||||
) ++ inConfig(Compile)(packageCacheSettings(compileArtifact(Compile, cachedCompileClassifier)))
|
||||
++ inConfig(Test)(packageCacheSettings(testArtifact(Test, cachedTestClassifier))))
|
||||
) ++ inConfig(Compile)(configCacheSettings(compileArtifact(Compile, cachedCompileClassifier)))
|
||||
++ inConfig(Test)(configCacheSettings(testArtifact(Test, cachedTestClassifier))))
|
||||
|
||||
private def packageCacheSettings[A <: RemoteCacheArtifact](
|
||||
cacheArtifact: Def.Initialize[Task[A]]
|
||||
def configCacheSettings[A <: RemoteCacheArtifact](
|
||||
cacheArtifactTask: Def.Initialize[Task[A]]
|
||||
): Seq[Def.Setting[_]] =
|
||||
inTask(packageCache)(
|
||||
Seq(
|
||||
|
|
@ -206,10 +164,128 @@ object RemoteCache {
|
|||
// }
|
||||
artp
|
||||
},
|
||||
remoteCacheArtifact := cacheArtifact.value,
|
||||
pushRemoteCacheArtifact := true,
|
||||
remoteCacheArtifact := cacheArtifactTask.value,
|
||||
packagedArtifact := (artifact.value -> packageCache.value),
|
||||
artifactPath := Defaults.artifactPathSetting(artifact).value
|
||||
)
|
||||
) ++ inTask(pushRemoteCache)(
|
||||
Seq(
|
||||
moduleSettings := {
|
||||
val smi = scalaModuleInfo.value
|
||||
ModuleDescriptorConfiguration(remoteCacheProjectId.value, projectInfo.value)
|
||||
.withScalaModuleInfo(smi)
|
||||
},
|
||||
pushRemoteCache.in(Defaults.TaskZero) := (Def.task {
|
||||
val s = streams.value
|
||||
val config = pushRemoteCacheConfiguration.value
|
||||
val is = (pushRemoteCache / ivySbt).value
|
||||
val m = new is.Module(moduleSettings.value)
|
||||
IvyActions.publish(m, config, s.log)
|
||||
} tag (Tags.Publish, Tags.Network)).value,
|
||||
)
|
||||
) ++ Seq(
|
||||
remoteCacheIdCandidates := List(remoteCacheId.value),
|
||||
remoteCacheProjectId := {
|
||||
val o = organization.value
|
||||
val m = moduleName.value
|
||||
val id = remoteCacheId.value
|
||||
val c = (projectID / crossVersion).value
|
||||
val v = toVersion(id)
|
||||
ModuleID(o, m, v).cross(c)
|
||||
},
|
||||
remoteCacheId := {
|
||||
val inputs = (unmanagedSources / inputFileStamps).value
|
||||
val cp = (externalDependencyClasspath / outputFileStamps).?.value.getOrElse(Nil)
|
||||
val extraInc = (extraIncOptions.value) flatMap {
|
||||
case (k, v) =>
|
||||
Vector(k, v)
|
||||
}
|
||||
combineHash(extractHash(inputs) ++ extractHash(cp) ++ extraInc)
|
||||
},
|
||||
pushRemoteCacheConfiguration := {
|
||||
Classpaths.publishConfig(
|
||||
(pushRemoteCacheConfiguration / publishMavenStyle).value,
|
||||
Classpaths.deliverPattern(crossTarget.value),
|
||||
if (isSnapshot.value) "integration" else "release",
|
||||
ivyConfigurations.value.map(c => ConfigRef(c.name)).toVector,
|
||||
(pushRemoteCacheConfiguration / packagedArtifacts).value.toVector,
|
||||
(pushRemoteCacheConfiguration / checksums).value.toVector,
|
||||
Classpaths.getPublishTo(pushRemoteCacheTo.value).name,
|
||||
ivyLoggingLevel.value,
|
||||
isSnapshot.value
|
||||
)
|
||||
},
|
||||
pushRemoteCacheConfiguration / packagedArtifacts := Def.taskDyn {
|
||||
val artifacts = (pushRemoteCacheConfiguration / remoteCacheArtifacts).value
|
||||
artifacts
|
||||
.map(a => a.packaged.map(file => (a.artifact, file)))
|
||||
.join
|
||||
.apply(_.join.map(_.toMap))
|
||||
}.value,
|
||||
pushRemoteCacheConfiguration / remoteCacheArtifacts := {
|
||||
List((packageCache / remoteCacheArtifact).value)
|
||||
},
|
||||
pullRemoteCache := {
|
||||
import scala.collection.JavaConverters._
|
||||
val log = streams.value.log
|
||||
val r = remoteCacheResolvers.value.head
|
||||
val p = remoteCacheProjectId.value
|
||||
val ids = remoteCacheIdCandidates.value
|
||||
val is = (pushRemoteCache / ivySbt).value
|
||||
val m = new is.Module((pushRemoteCache / moduleSettings).value)
|
||||
val smi = scalaModuleInfo.value
|
||||
val artifacts = (pushRemoteCacheConfiguration / remoteCacheArtifacts).value
|
||||
val nonPom = artifacts.filterNot(isPomArtifact).toVector
|
||||
m.withModule(log) {
|
||||
case (ivy, md, _) =>
|
||||
val resolver = ivy.getSettings.getResolver(r.name)
|
||||
if (resolver eq null) sys.error(s"undefined resolver '${r.name}'")
|
||||
val cross = CrossVersion(p, smi)
|
||||
val crossf: String => String = cross.getOrElse(identity _)
|
||||
var found = false
|
||||
ids foreach {
|
||||
id: String =>
|
||||
val v = toVersion(id)
|
||||
val modId = p.withRevision(v).withName(crossf(p.name))
|
||||
val ivyId = IvySbt.toID(modId)
|
||||
if (found) ()
|
||||
else {
|
||||
val rawa = nonPom map { _.artifact }
|
||||
val seqa = CrossVersion.substituteCross(rawa, cross)
|
||||
val as = seqa map { a =>
|
||||
val extra = a.classifier match {
|
||||
case Some(c) => Map("e:classifier" -> c)
|
||||
case None => Map.empty
|
||||
}
|
||||
new DefaultArtifact(ivyId, null, a.name, a.`type`, a.extension, extra.asJava)
|
||||
}
|
||||
pullFromMavenRepo0(as, resolver, log) match {
|
||||
case Right(xs0) =>
|
||||
val jars = xs0.distinct
|
||||
|
||||
nonPom.foreach { art =>
|
||||
val classifier = art.artifact.classifier
|
||||
|
||||
findJar(classifier, v, jars) match {
|
||||
case Some(jar) =>
|
||||
extractJar(art, jar)
|
||||
log.info(s"remote cache artifact extracted for $p $classifier")
|
||||
|
||||
case None =>
|
||||
log.info(s"remote cache artifact not found for $p $classifier")
|
||||
}
|
||||
}
|
||||
found = true
|
||||
case Left(e) =>
|
||||
log.info(s"remote cache not found for ${v}")
|
||||
log.debug(e.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def isPomArtifact(artifact: RemoteCacheArtifact): Boolean =
|
||||
|
|
@ -245,26 +321,35 @@ object RemoteCache {
|
|||
|
||||
private def toVersion(v: String): String = s"0.0.0-$v"
|
||||
|
||||
private lazy val doption = new DownloadOptions
|
||||
private def pullFromMavenRepo0(
|
||||
modId: ModuleID,
|
||||
classifiers: Vector[String],
|
||||
smi: Option[ScalaModuleInfo],
|
||||
is: IvySbt,
|
||||
dr: DependencyResolution,
|
||||
cacheDir: File,
|
||||
artifacts: Vector[IArtifact],
|
||||
r: DependencyResolver,
|
||||
log: Logger
|
||||
): Either[UnresolvedWarning, Vector[File]] = {
|
||||
def dummyModule(deps: Vector[ModuleID]): ModuleDescriptorConfiguration = {
|
||||
val module = ModuleID("com.example.temp", "fake", "0.1.0-SNAPSHOT")
|
||||
val info = ModuleInfo("fake", "", None, None, Vector(), "", None, None, Vector())
|
||||
ModuleDescriptorConfiguration(module, info)
|
||||
.withScalaModuleInfo(smi)
|
||||
.withDependencies(deps)
|
||||
): Either[Throwable, Vector[File]] = {
|
||||
try {
|
||||
val files = r.download(artifacts.toArray, doption).getArtifactsReports.toVector map {
|
||||
report =>
|
||||
if (report == null) sys.error(s"failed to download $artifacts: " + r.toString)
|
||||
else
|
||||
report.getDownloadStatus match {
|
||||
case DownloadStatus.NO =>
|
||||
val o = report.getArtifactOrigin
|
||||
if (o.isLocal) {
|
||||
val localFile = new File(o.getLocation)
|
||||
if (!localFile.exists) sys.error(s"$localFile doesn't exist")
|
||||
else localFile
|
||||
} else report.getLocalFile
|
||||
case DownloadStatus.SUCCESSFUL =>
|
||||
report.getLocalFile
|
||||
case DownloadStatus.FAILED =>
|
||||
sys.error(s"failed to download $artifacts: " + r.toString)
|
||||
}
|
||||
}
|
||||
Right(files)
|
||||
} catch {
|
||||
case e: Throwable => Left(e)
|
||||
}
|
||||
val deps = classifiers.map(modId.classifier)
|
||||
val mconfig = dummyModule(deps)
|
||||
val m = new is.Module(mconfig)
|
||||
dr.retrieve(m, cacheDir, log)
|
||||
}
|
||||
|
||||
private def findJar(classifier: Option[String], ver: String, jars: Vector[File]): Option[File] = {
|
||||
|
|
@ -322,7 +407,7 @@ object RemoteCache {
|
|||
}
|
||||
|
||||
private def defaultArtifactTasks: Seq[TaskKey[File]] =
|
||||
Seq(remoteCachePom, Compile / packageCache, Test / packageCache)
|
||||
Seq(Compile / packageCache, Test / packageCache)
|
||||
|
||||
private def enabledOnly[A](
|
||||
key: SettingKey[A],
|
||||
|
|
@ -332,4 +417,14 @@ object RemoteCache {
|
|||
Classpaths.forallIn(pushRemoteCacheArtifact, pkgTasks))(_ zip _ collect {
|
||||
case (a, true) => a
|
||||
})
|
||||
|
||||
private def extractHash(inputs: Seq[(Path, FileStamp)]): Vector[String] =
|
||||
inputs.toVector map {
|
||||
case (_, stamp0) => toOption(stamp0.stamp.getHash).getOrElse("cafe")
|
||||
}
|
||||
|
||||
private def combineHash(vs: Vector[String]): String = {
|
||||
val hashValue = HashUtil.farmHash(vs.sorted.mkString("").getBytes("UTF-8"))
|
||||
java.lang.Long.toHexString(hashValue)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ private[sbt] class TaskProgress(
|
|||
}
|
||||
private[this] val skipReportTasks =
|
||||
Set(
|
||||
"installSbtn",
|
||||
"run",
|
||||
"runMain",
|
||||
"bgRun",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
package sbt.internal.server
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
import sbt.StandardMain
|
||||
import sbt.internal.bsp._
|
||||
import sbt.internal.util.ManagedLogger
|
||||
|
|
@ -69,7 +71,7 @@ final class BuildServerReporterImpl(
|
|||
import sbt.internal.inc.JavaInterfaceUtil._
|
||||
|
||||
private lazy val exchange = StandardMain.exchange
|
||||
private val problemsByFile = mutable.Map[VirtualFileRef, Vector[Diagnostic]]()
|
||||
private val problemsByFile = mutable.Map[Path, Vector[Diagnostic]]()
|
||||
|
||||
override def sendSuccessReport(analysis: CompileAnalysis): Unit = {
|
||||
for {
|
||||
|
|
@ -90,9 +92,8 @@ final class BuildServerReporterImpl(
|
|||
|
||||
override def sendFailureReport(sources: Array[VirtualFile]): Unit = {
|
||||
for (source <- sources) {
|
||||
val ref = VirtualFileRef.of(source.id())
|
||||
val diagnostics = problemsByFile.getOrElse(ref, Vector())
|
||||
val filePath = converter.toPath(source)
|
||||
val diagnostics = problemsByFile.getOrElse(filePath, Vector())
|
||||
val params = PublishDiagnosticsParams(
|
||||
textDocument = TextDocumentIdentifier(filePath.toUri),
|
||||
buildTarget,
|
||||
|
|
@ -106,14 +107,13 @@ final class BuildServerReporterImpl(
|
|||
|
||||
protected override def publishDiagnostic(problem: Problem): Unit = {
|
||||
for {
|
||||
path <- problem.position().sourcePath.toOption
|
||||
source <- problem.position.sourceFile.toOption
|
||||
id <- problem.position.sourcePath.toOption
|
||||
diagnostic <- toDiagnostic(problem)
|
||||
} {
|
||||
val fileId = VirtualFileRef.of(path)
|
||||
problemsByFile(fileId) = problemsByFile.getOrElse(fileId, Vector()) :+ diagnostic
|
||||
val filePath = converter.toPath(VirtualFileRef.of(id))
|
||||
problemsByFile(filePath) = problemsByFile.getOrElse(filePath, Vector()) :+ diagnostic
|
||||
val params = PublishDiagnosticsParams(
|
||||
TextDocumentIdentifier(source.toURI),
|
||||
TextDocumentIdentifier(filePath.toUri),
|
||||
buildTarget,
|
||||
originId = None,
|
||||
Vector(diagnostic),
|
||||
|
|
|
|||
|
|
@ -565,6 +565,7 @@ final class NetworkChannel(
|
|||
logShutdown: Boolean,
|
||||
remainingCommands: Option[(String, String)]
|
||||
): Unit = {
|
||||
doFlush()
|
||||
terminal.close()
|
||||
StandardMain.exchange.removeChannel(this)
|
||||
super.shutdown(logShutdown)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package internal
|
||||
|
||||
import java.io.{ InputStream, OutputStream, PrintStream }
|
||||
import java.lang.ProcessBuilder
|
||||
import java.lang.ProcessBuilder.Redirect
|
||||
import java.nio.file.{ Files, Path }
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.scalatest.FlatSpec
|
||||
import sbt.io.IO
|
||||
|
||||
class InstallSbtnSpec extends FlatSpec {
|
||||
private def withTemp[R](ext: String)(f: Path => R): R = {
|
||||
val tmp = Files.createTempFile("sbt-1.4.1-", ext)
|
||||
try f(tmp)
|
||||
finally {
|
||||
Files.deleteIfExists(tmp)
|
||||
()
|
||||
}
|
||||
}
|
||||
private[this] val term = new Terminal {
|
||||
def getHeight: Int = 0
|
||||
def getWidth: Int = 0
|
||||
def inputStream: InputStream = () => -1
|
||||
def printStream: PrintStream = new PrintStream((_ => {}): OutputStream)
|
||||
def setMode(canonical: Boolean, echo: Boolean): Unit = {}
|
||||
|
||||
}
|
||||
// This test has issues in ci but runs ok locally on all platforms
|
||||
"InstallSbtn" should "extract native sbtn" ignore
|
||||
withTemp(".zip") { tmp =>
|
||||
withTemp(".exe") { sbtn =>
|
||||
InstallSbtn.extractSbtn(term, "1.4.1", tmp, sbtn)
|
||||
val tmpDir = Files.createTempDirectory("sbtn-test").toRealPath()
|
||||
Files.createDirectories(tmpDir.resolve("project"))
|
||||
val foo = tmpDir.resolve("foo")
|
||||
val fooPath = foo.toString.replaceAllLiterally("\\", "\\\\")
|
||||
val build = s"""TaskKey[Unit]("foo") := IO.write(file("$fooPath"), "foo")"""
|
||||
IO.write(tmpDir.resolve("build.sbt").toFile, build)
|
||||
IO.write(
|
||||
tmpDir.resolve("project").resolve("build.properties").toFile,
|
||||
"sbt.version=1.4.1"
|
||||
)
|
||||
try {
|
||||
val proc =
|
||||
new ProcessBuilder(sbtn.toString, "foo;shutdown")
|
||||
.redirectInput(Redirect.INHERIT)
|
||||
.redirectOutput(Redirect.INHERIT)
|
||||
.redirectError(Redirect.INHERIT)
|
||||
.directory(tmpDir.toFile)
|
||||
.start()
|
||||
proc.waitFor(1, TimeUnit.MINUTES)
|
||||
assert(proc.exitValue == 0)
|
||||
assert(IO.read(foo.toFile) == "foo")
|
||||
} finally {
|
||||
sbt.io.IO.delete(tmpDir.toFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ object Dependencies {
|
|||
private val ioVersion = nightlyVersion.getOrElse("1.4.0")
|
||||
private val lmVersion =
|
||||
sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.4.0")
|
||||
val zincVersion = nightlyVersion.getOrElse("1.4.1")
|
||||
val zincVersion = nightlyVersion.getOrElse("1.4.2")
|
||||
|
||||
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
sbt.version=1.4.0
|
||||
|
|
@ -10,8 +10,9 @@ pushRemoteCacheTo := Some(
|
|||
MavenCache("local-cache", (ThisBuild / baseDirectory).value / "remote-cache-semanticdb")
|
||||
)
|
||||
|
||||
remoteCacheId := "fixed-id"
|
||||
|
||||
remoteCacheIdCandidates := Seq(remoteCacheId.value)
|
||||
|
||||
pushRemoteCacheConfiguration := pushRemoteCacheConfiguration.value.withOverwrite(true)
|
||||
Compile / remoteCacheId := "fixed-id"
|
||||
Compile / remoteCacheIdCandidates := Seq((Compile / remoteCacheId).value)
|
||||
Test / remoteCacheId := "fixed-id"
|
||||
Test / remoteCacheIdCandidates := Seq((Test / remoteCacheId).value)
|
||||
Compile / pushRemoteCacheConfiguration := (Compile / pushRemoteCacheConfiguration).value.withOverwrite(true)
|
||||
Test / pushRemoteCacheConfiguration := (Test / pushRemoteCacheConfiguration).value.withOverwrite(true)
|
||||
|
|
|
|||
|
|
@ -10,11 +10,6 @@ $ exists target/scala-2.12/test-classes/MyTest$.class
|
|||
$ exists target/scala-2.12/test-classes/META-INF/semanticdb/src/test/scala/MyTest.scala.semanticdb
|
||||
$ exists target/scala-2.12/test-zinc/inc_compile_2.12.zip
|
||||
|
||||
# Pom file
|
||||
$ exists remote-cache-semanticdb/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id.pom
|
||||
$ exists remote-cache-semanticdb/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id.pom.md5
|
||||
$ exists remote-cache-semanticdb/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id.pom.sha1
|
||||
|
||||
# Compile
|
||||
$ exists remote-cache-semanticdb/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar
|
||||
$ exists remote-cache-semanticdb/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar.md5
|
||||
|
|
|
|||
|
|
@ -1,38 +1,74 @@
|
|||
import sbt.internal.remotecache.CustomRemoteCacheArtifact
|
||||
import sbt.internal.inc.Analysis
|
||||
import complete.DefaultParsers._
|
||||
|
||||
lazy val CustomArtifact = config("custom-artifact")
|
||||
|
||||
// Reset compiler iterations, necessary because tests run in batch mode
|
||||
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||
|
||||
ThisBuild / scalaVersion := "2.12.12"
|
||||
ThisBuild / pushRemoteCacheTo := Some(
|
||||
MavenCache("local-cache", (ThisBuild / baseDirectory).value / "r")
|
||||
)
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.configs(CustomArtifact)
|
||||
.settings(
|
||||
name := "my-project",
|
||||
scalaVersion := "2.12.12",
|
||||
pushRemoteCacheTo := Some(
|
||||
MavenCache("local-cache", (ThisBuild / baseDirectory).value / "remote-cache")
|
||||
),
|
||||
remoteCacheId := "fixed-id",
|
||||
remoteCacheIdCandidates := Seq("fixed-id"),
|
||||
pushRemoteCacheConfiguration := pushRemoteCacheConfiguration.value.withOverwrite(true),
|
||||
pushRemoteCacheConfiguration / remoteCacheArtifacts += {
|
||||
val art = (CustomArtifact / artifact).value
|
||||
val packaged = CustomArtifact / packageCache
|
||||
val extractDirectory = (CustomArtifact / sourceManaged).value
|
||||
|
||||
CustomRemoteCacheArtifact(art, packaged, extractDirectory, preserveLastModified = false)
|
||||
},
|
||||
customArtifactSettings,
|
||||
pushRemoteCacheConfiguration / remoteCacheArtifacts += (CustomArtifact / packageCache / remoteCacheArtifact).value,
|
||||
|
||||
Compile / pushRemoteCacheConfiguration := (Compile / pushRemoteCacheConfiguration).value.withOverwrite(true),
|
||||
Test / pushRemoteCacheConfiguration := (Test / pushRemoteCacheConfiguration).value.withOverwrite(true),
|
||||
|
||||
Compile / sourceGenerators += Def.task {
|
||||
val extractDirectory = (CustomArtifact / sourceManaged).value
|
||||
val output = extractDirectory / "HelloWorld.scala"
|
||||
IO.write(output, "class HelloWorld")
|
||||
Seq(output)
|
||||
}.taskValue
|
||||
)
|
||||
.settings(customArtifactSettings)
|
||||
}.taskValue,
|
||||
// bring back fixed-id because JDK 8/11 would be different intentionally
|
||||
Compile / remoteCacheId := "fixed-id",
|
||||
Compile / remoteCacheIdCandidates := Seq((Compile / remoteCacheId).value),
|
||||
Test / remoteCacheId := "fixed-id",
|
||||
Test / remoteCacheIdCandidates := Seq((Test / remoteCacheId).value),
|
||||
CustomArtifact / remoteCacheId := "fixed-id",
|
||||
CustomArtifact / remoteCacheIdCandidates := Seq((CustomArtifact / remoteCacheId).value),
|
||||
|
||||
lazy val CustomArtifact = config("custom-artifact")
|
||||
// test tasks
|
||||
recordPreviousIterations := {
|
||||
val log = streams.value.log
|
||||
CompileState.previousIterations = {
|
||||
val previousAnalysis = (previousCompile in Compile).value.analysis.asScala
|
||||
previousAnalysis match {
|
||||
case None =>
|
||||
log.info("No previous analysis detected")
|
||||
0
|
||||
case Some(a: Analysis) => a.compilations.allCompilations.size
|
||||
}
|
||||
}
|
||||
},
|
||||
checkIterations := {
|
||||
val expected: Int = (Space ~> NatBasic).parsed
|
||||
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||
}
|
||||
)
|
||||
|
||||
def customArtifactSettings: Seq[Def.Setting[_]] = {
|
||||
val classifier = "custom-artifact"
|
||||
|
||||
def cachedArtifactTask = Def.task {
|
||||
val art = (CustomArtifact / artifact).value
|
||||
val packaged = CustomArtifact / packageCache
|
||||
val extractDirectory = (CustomArtifact / sourceManaged).value
|
||||
CustomRemoteCacheArtifact(art, packaged, extractDirectory, preserveLastModified = false)
|
||||
}
|
||||
inConfig(CustomArtifact)(
|
||||
sbt.internal.RemoteCache.configCacheSettings(cachedArtifactTask) ++
|
||||
Seq(
|
||||
packageOptions := {
|
||||
val n = name.value + "-" + classifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
// This is necessary because tests are run in batch mode
|
||||
object CompileState {
|
||||
@volatile var previousIterations: Int = -1
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
> recordPreviousIterations
|
||||
> compile
|
||||
> pushRemoteCache
|
||||
|
||||
|
|
@ -11,25 +12,20 @@ $ exists target/scala-2.12/test-classes/MyTest.class
|
|||
$ exists target/scala-2.12/test-classes/MyTest$.class
|
||||
$ exists target/scala-2.12/test-zinc/inc_compile_2.12.zip
|
||||
|
||||
# Pom file
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id.pom
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id.pom.md5
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id.pom.sha1
|
||||
|
||||
# Compile
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar.md5
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar.sha1
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar.md5
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-compile.jar.sha1
|
||||
|
||||
# Test
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-test.jar
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-test.jar.md5
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-test.jar.sha1
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-test.jar
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-test.jar.md5
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-cached-test.jar.sha1
|
||||
|
||||
# Custom artifact
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-custom-artifact.jar
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-custom-artifact.jar.md5
|
||||
$ exists remote-cache/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-custom-artifact.jar.sha1
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-custom-artifact.jar
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-custom-artifact.jar.md5
|
||||
$ exists r/my-project/my-project_2.12/0.0.0-fixed-id/my-project_2.12-0.0.0-fixed-id-custom-artifact.jar.sha1
|
||||
|
||||
> clean
|
||||
|
||||
|
|
@ -54,5 +50,8 @@ $ exists target/scala-2.12/test-classes/MyTest.class
|
|||
$ exists target/scala-2.12/test-classes/MyTest$.class
|
||||
$ exists target/scala-2.12/test-zinc/inc_compile_2.12.zip
|
||||
|
||||
> debug
|
||||
> checkIterations 1
|
||||
|
||||
# Artifacts can be pushed twice (enabled overriding)
|
||||
> pushRemoteCache
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import java.util.Optional
|
||||
import xsbti.Position
|
||||
|
||||
val assertAbsolutePathConversion = taskKey[Unit]("checks source mappers convert to absolute path")
|
||||
|
||||
val assertHandleFakePos = taskKey[Unit]("checks source mappers handle fake position")
|
||||
|
||||
assertAbsolutePathConversion := {
|
||||
val converter = fileConverter.value
|
||||
val source = (Compile/sources).value.head
|
||||
val position = newPosition(converter.toVirtualFile(source.toPath).id, source)
|
||||
val mappedPos = sourcePositionMappers.value
|
||||
.foldLeft(Option(position)) {
|
||||
case (pos, mapper) => pos.flatMap(mapper)
|
||||
}
|
||||
assert {
|
||||
mappedPos.get.sourcePath.asScala.contains(source.getAbsolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
assertHandleFakePos := {
|
||||
val position = newPosition("<macro>", new File("<macro>"))
|
||||
val mappedPos = sourcePositionMappers.value
|
||||
.foldLeft(Option(position)) {
|
||||
case (pos, mapper) => pos.flatMap(mapper)
|
||||
}
|
||||
assert {
|
||||
mappedPos.get.sourcePath.asScala.get.contains("<macro>")
|
||||
}
|
||||
}
|
||||
|
||||
def newPosition(path: String, file: File): Position = new Position {
|
||||
override def line(): Optional[Integer] = Optional.empty()
|
||||
|
||||
override def lineContent() = ""
|
||||
|
||||
override def offset(): Optional[Integer] = Optional.empty()
|
||||
|
||||
override def pointer(): Optional[Integer] = Optional.empty()
|
||||
|
||||
override def pointerSpace(): Optional[String] = Optional.empty()
|
||||
|
||||
override def sourcePath(): Optional[String] = Optional.of(path)
|
||||
|
||||
override def sourceFile(): Optional[File] = Optional.of(file)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> assertAbsolutePathConversion
|
||||
> assertHandleFakePos
|
||||
|
|
@ -2,13 +2,17 @@ ThisBuild / scalaVersion := "2.13.1"
|
|||
|
||||
Global / serverLog / logLevel := Level.Debug
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.aggregate(foo, util)
|
||||
|
||||
lazy val foo = project.in(file("foo"))
|
||||
lazy val runAndTest = project.in(file("run-and-test"))
|
||||
.settings(
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test",
|
||||
)
|
||||
.dependsOn(util)
|
||||
|
||||
lazy val reportError = project.in(file("report-error"))
|
||||
|
||||
lazy val reportWarning = project.in(file("report-warning"))
|
||||
.settings(
|
||||
scalacOptions += "-deprecation"
|
||||
)
|
||||
|
||||
lazy val util = project
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
package foo
|
||||
|
||||
import org.scalatest.FreeSpec
|
||||
|
||||
class FooTest extends FreeSpec {
|
||||
"test message" in {
|
||||
assert(FooMain.message == "Hello World!")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package reportertests
|
||||
|
||||
object Error {
|
||||
val version: String = 5
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package reportertests
|
||||
|
||||
object Warning {
|
||||
def print() {
|
||||
prtinln("bar")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package foo
|
||||
package main
|
||||
|
||||
object FooMain extends App {
|
||||
object Main extends App {
|
||||
lazy val message = "Hello World!"
|
||||
|
||||
println(message)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package foo
|
||||
package tests
|
||||
|
||||
import org.scalatest.FreeSpec
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package tests
|
||||
|
||||
import org.scalatest.FreeSpec
|
||||
|
||||
class PassingTest extends FreeSpec {
|
||||
"test message" in {
|
||||
assert(main.Main.message == "Hello World!")
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/scalaMainClasses") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#foo/Compile"
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Compile"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "16", "method": "buildTarget/scalaMainClasses", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|
|
@ -95,17 +95,17 @@ object BuildServerTest extends AbstractServerTest {
|
|||
assert(svr.waitForString(30.seconds) { s =>
|
||||
println(s)
|
||||
(s contains """"id":"16"""") &&
|
||||
(s contains """"class":"foo.FooMain"""")
|
||||
(s contains """"class":"main.Main"""")
|
||||
})
|
||||
}
|
||||
|
||||
test("buildTarget/run") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#foo/Compile"
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Compile"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "17", "method": "buildTarget/run", "params": {
|
||||
| "target": { "uri": "$x" },
|
||||
| "dataKind": "scala-main-class",
|
||||
| "data": { "class": "foo.FooMain" }
|
||||
| "data": { "class": "main.Main" }
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
|
|
@ -121,7 +121,7 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/scalaTestClasses") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#foo/Test"
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "18", "method": "buildTarget/scalaTestClasses", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|
|
@ -130,12 +130,13 @@ object BuildServerTest extends AbstractServerTest {
|
|||
assert(svr.waitForString(10.seconds) { s =>
|
||||
println(s)
|
||||
(s contains """"id":"18"""") &&
|
||||
(s contains """"classes":["foo.FailingTest","foo.FooTest"]""")
|
||||
(s contains """"tests.FailingTest"""") &&
|
||||
(s contains """"tests.PassingTest"""")
|
||||
})
|
||||
}
|
||||
|
||||
test("buildTarget/test: run all tests") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#foo/Test"
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "19", "method": "buildTarget/test", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|
|
@ -149,7 +150,7 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/test: run one test class") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#foo/Test"
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "20", "method": "buildTarget/test", "params": {
|
||||
| "targets": [{ "uri": "$x" }],
|
||||
|
|
@ -158,7 +159,7 @@ object BuildServerTest extends AbstractServerTest {
|
|||
| "testClasses": [
|
||||
| {
|
||||
| "target": { "uri": "$x" },
|
||||
| "classes": ["foo.FooTest"]
|
||||
| "classes": ["tests.PassingTest"]
|
||||
| }
|
||||
| ]
|
||||
| }
|
||||
|
|
@ -171,6 +172,36 @@ object BuildServerTest extends AbstractServerTest {
|
|||
})
|
||||
}
|
||||
|
||||
test("buildTarget/compile: report error") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#reportError/Compile"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "21", "method": "buildTarget/compile", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
println(s)
|
||||
(s contains s""""buildTarget":{"uri":"$x"}""") &&
|
||||
(s contains """"severity":1""") &&
|
||||
(s contains """"reset":true""")
|
||||
})
|
||||
}
|
||||
|
||||
test("buildTarget/compile: report warning") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#reportWarning/Compile"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "22", "method": "buildTarget/compile", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
println(s)
|
||||
(s contains s""""buildTarget":{"uri":"$x"}""") &&
|
||||
(s contains """"severity":2""") &&
|
||||
(s contains """"reset":true""")
|
||||
})
|
||||
}
|
||||
|
||||
def initializeRequest(): Unit = {
|
||||
svr.sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": "10", "method": "build/initialize",
|
||||
|
|
|
|||
Loading…
Reference in New Issue