Merge branch 'develop' into coursier_2.0.2

This commit is contained in:
João Ferreira 2020-11-03 07:52:16 +00:00 committed by GitHub
commit bc150efd55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1023 additions and 353 deletions

View File

@ -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"

View File

@ -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/*"

View File

@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/sbt/sbt.svg?branch=develop)](https://travis-ci.org/sbt/sbt)
[![Build Status](https://travis-ci.com/sbt/sbt.svg?branch=develop)](https://travis-ci.com/github/sbt/sbt)
[![Latest version](https://img.shields.io/github/tag/sbt/sbt.svg)](https://index.scala-lang.org/sbt/sbt)
[![Gitter Chat](https://badges.gitter.im/sbt/sbt.svg)](https://gitter.im/sbt/sbt)

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 _ =>

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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()

View File

@ -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 _)
}
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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",
)
}

View File

@ -38,6 +38,7 @@ object LintUnused {
onUnload,
sbt.nio.Keys.watchTriggers,
serverConnectionType,
serverIdleTimeout,
shellPrompt,
),
includeLintKeys := Set(

View File

@ -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)
}
}

View File

@ -139,6 +139,7 @@ private[sbt] class TaskProgress(
}
private[this] val skipReportTasks =
Set(
"installSbtn",
"run",
"runMain",
"bgRun",

View File

@ -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),

View File

@ -565,6 +565,7 @@ final class NetworkChannel(
logShutdown: Boolean,
remainingCommands: Option[(String, String)]
): Unit = {
doFlush()
terminal.close()
StandardMain.exchange.removeChannel(this)
super.shutdown(logShutdown)

View File

@ -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)
}
}
}
}

View File

@ -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

View File

@ -0,0 +1 @@
sbt.version=1.4.0

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
// This is necessary because tests are run in batch mode
object CompileState {
@volatile var previousIterations: Int = -1
}

View File

@ -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

View File

@ -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)
}

View File

@ -0,0 +1,2 @@
> assertAbsolutePathConversion
> assertHandleFakePos

View File

@ -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

View File

@ -1,9 +0,0 @@
package foo
import org.scalatest.FreeSpec
class FooTest extends FreeSpec {
"test message" in {
assert(FooMain.message == "Hello World!")
}
}

View File

@ -0,0 +1,5 @@
package reportertests
object Error {
val version: String = 5
}

View File

@ -0,0 +1,7 @@
package reportertests
object Warning {
def print() {
prtinln("bar")
}
}

View File

@ -1,6 +1,6 @@
package foo
package main
object FooMain extends App {
object Main extends App {
lazy val message = "Hello World!"
println(message)

View File

@ -0,0 +1,9 @@
package tests
import org.scalatest.FreeSpec
class PassingTest extends FreeSpec {
"test message" in {
assert(main.Main.message == "Hello World!")
}
}

View File

@ -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",