mirror of https://github.com/sbt/sbt.git
commit
f80e2c38c8
|
|
@ -2839,7 +2839,7 @@ object Classpaths {
|
|||
case (false, _) =>
|
||||
Def.task { filteredDependencyClasspath.value }
|
||||
case (true, DependencyMode.Transitive) =>
|
||||
Def.task { dependencyClasspath.value }
|
||||
Def.task { internalDependencyPicklePath.value ++ externalDependencyClasspath.value }
|
||||
case (true, DependencyMode.Direct) =>
|
||||
Def.task {
|
||||
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
||||
|
|
@ -2847,7 +2847,7 @@ object Classpaths {
|
|||
thisProjectRef.value,
|
||||
settingsData.value,
|
||||
buildDependencies.value,
|
||||
internalDependencyClasspath.value,
|
||||
internalDependencyPicklePath.value,
|
||||
)
|
||||
val externalFiltered = ClasspathImpl.filterByDirectDeps(
|
||||
allDependencies.value,
|
||||
|
|
@ -2862,7 +2862,7 @@ object Classpaths {
|
|||
thisProjectRef.value,
|
||||
settingsData.value,
|
||||
buildDependencies.value,
|
||||
internalDependencyClasspath.value,
|
||||
internalDependencyPicklePath.value,
|
||||
)
|
||||
val externalFiltered = ClasspathImpl.filterByPlusOne(
|
||||
allDependencies.value,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ final class NetworkChannel(
|
|||
private val pendingWrites = new LinkedBlockingQueue[(Array[Byte], Boolean)]()
|
||||
private val attached = new AtomicBoolean(false)
|
||||
private val alive = new AtomicBoolean(true)
|
||||
private val isCanceled = new AtomicBoolean(false)
|
||||
private[sbt] def isInteractive = interactive.get
|
||||
private val interactive = new AtomicBoolean(false)
|
||||
private[sbt] def setInteractive(id: String, value: Boolean) = {
|
||||
|
|
@ -109,6 +110,10 @@ final class NetworkChannel(
|
|||
interactive.set(true)
|
||||
jsonRpcNotify(promptChannel, "")
|
||||
}
|
||||
override def prompt(e: ConsolePromptEvent): Unit = {
|
||||
isCanceled.set(false)
|
||||
super.prompt(e)
|
||||
}
|
||||
private[sbt] def write(byte: Byte) = inputBuffer.add(byte.toInt)
|
||||
|
||||
private val terminalHolder = new AtomicReference[Terminal](Terminal.NullTerminal)
|
||||
|
|
@ -532,6 +537,8 @@ final class NetworkChannel(
|
|||
StandardMain.exchange.currentExec.exists(_.source.exists(_.channelName == name)))
|
||||
) {
|
||||
runningEngine.cancelAndShutdown()
|
||||
isCanceled.set(true)
|
||||
discardPending()
|
||||
|
||||
respondResult(
|
||||
ExecStatusEvent(
|
||||
|
|
@ -625,16 +632,19 @@ final class NetworkChannel(
|
|||
}
|
||||
|
||||
/** Notify to Language Server's client. */
|
||||
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = {
|
||||
val m =
|
||||
JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get))
|
||||
if (method != Serialization.systemOut) {
|
||||
forceFlush()
|
||||
log.debug(s"jsonRpcNotify: $m")
|
||||
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit =
|
||||
if (isCanceled.get && NetworkChannel.isCanceledOutput(method))
|
||||
()
|
||||
else {
|
||||
val m =
|
||||
JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get))
|
||||
if (method != Serialization.systemOut) {
|
||||
forceFlush()
|
||||
log.debug(s"jsonRpcNotify: $m")
|
||||
}
|
||||
val bytes = Serialization.serializeNotificationMessage(m)
|
||||
publishBytes(bytes)
|
||||
}
|
||||
val bytes = Serialization.serializeNotificationMessage(m)
|
||||
publishBytes(bytes)
|
||||
}
|
||||
|
||||
/** Notify to Language Server's client. */
|
||||
private[sbt] def jsonRpcRequest[A: JsonFormat](id: String, method: String, params: A): Unit = {
|
||||
|
|
@ -675,6 +685,11 @@ final class NetworkChannel(
|
|||
|
||||
import scala.jdk.CollectionConverters.*
|
||||
private val outputBuffer = new LinkedBlockingQueue[Byte]
|
||||
private def discardPending(): Unit = {
|
||||
pendingWrites.clear()
|
||||
outputBuffer.synchronized(outputBuffer.clear())
|
||||
}
|
||||
|
||||
// Batches writes to the client at most once per 20ms to cut terminal flicker (see
|
||||
// CoalescingFlusher). forceFlush drains now while leaving the timer live.
|
||||
private val flusher =
|
||||
|
|
@ -942,6 +957,9 @@ final class NetworkChannel(
|
|||
}
|
||||
|
||||
object NetworkChannel {
|
||||
private[server] def isCanceledOutput(method: String): Boolean =
|
||||
method == Serialization.systemOut || method == Serialization.systemErr
|
||||
|
||||
private[sbt] def cancel(
|
||||
execID: Option[String],
|
||||
id: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.server
|
||||
|
||||
import sbt.protocol.Serialization
|
||||
import verify.BasicTestSuite
|
||||
|
||||
object NetworkChannelSpec extends BasicTestSuite:
|
||||
|
||||
test("only systemOut and systemErr are dropped while canceling") {
|
||||
assert(NetworkChannel.isCanceledOutput(Serialization.systemOut))
|
||||
assert(NetworkChannel.isCanceledOutput(Serialization.systemErr))
|
||||
}
|
||||
|
||||
test("control-plane and flush methods are never dropped while canceling") {
|
||||
val kept = Seq(
|
||||
Serialization.systemOutFlush,
|
||||
Serialization.systemErrFlush,
|
||||
Serialization.readSystemIn,
|
||||
Serialization.promptChannel,
|
||||
"build/logMessage",
|
||||
"sbt/exec",
|
||||
"window/logMessage",
|
||||
sbt.BasicCommandStrings.Shutdown,
|
||||
)
|
||||
kept.foreach(m => assert(!NetworkChannel.isCanceledOutput(m), s"must not drop: $m"))
|
||||
}
|
||||
|
||||
end NetworkChannelSpec
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
### Cancelling a task discards its queued output
|
||||
|
||||
Previously, cancelling a task that was producing a lot of output (Ctrl+C from the
|
||||
thin client) stopped the task, but the server kept draining the already-queued
|
||||
output to the client for several seconds, so the cancel appeared not to take
|
||||
effect. The cancelled task's buffered stdout/stderr is now discarded on cancel,
|
||||
so output stops promptly. Control-plane messages and the next command's output
|
||||
are unaffected.
|
||||
19
sbt
19
sbt
|
|
@ -558,6 +558,14 @@ checkWorkingDirectory() {
|
|||
fi
|
||||
}
|
||||
|
||||
runShutdownAll() {
|
||||
local sbt_processes=( $(jps -v | grep sbt-launch | cut -f1 -d ' ') )
|
||||
for procId in "${sbt_processes[@]}"; do
|
||||
kill -9 $procId
|
||||
done
|
||||
echoerr "shutdown ${#sbt_processes[@]} sbt processes"
|
||||
}
|
||||
|
||||
run() {
|
||||
# Copy preloaded repo to user's preloaded directory
|
||||
syncPreloaded
|
||||
|
|
@ -594,12 +602,6 @@ run() {
|
|||
echoerr ""
|
||||
echoerr "[info] sbt runner (sbt-the-shell-script) is a runner to run any declared version of sbt."
|
||||
echoerr "[info] Actual version of the sbt is declared using project/build.properties for each build."
|
||||
elif [[ $shutdownall ]]; then
|
||||
local sbt_processes=( $(jps -v | grep sbt-launch | cut -f1 -d ' ') )
|
||||
for procId in "${sbt_processes[@]}"; do
|
||||
kill -9 $procId
|
||||
done
|
||||
echoerr "shutdown ${#sbt_processes[@]} sbt processes"
|
||||
else
|
||||
checkWorkingDirectory
|
||||
# run sbt
|
||||
|
|
@ -962,6 +964,11 @@ if [[ $print_sbt_script_version ]]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $shutdownall ]]; then
|
||||
runShutdownAll
|
||||
exit 0
|
||||
fi
|
||||
|
||||
java_version="$(jdk_version)"
|
||||
vlog "[process_args] java_version = '$java_version'"
|
||||
checkJava17ForSbt2
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
ThisBuild / scalaVersion := "2.13.18"
|
||||
ThisBuild / usePipelining := true
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.aggregate(upstream, `downstream-transitive`, `downstream-direct`, `downstream-plusone`)
|
||||
.settings(
|
||||
name := "pipelining Java then Scala",
|
||||
)
|
||||
|
||||
lazy val upstream = project
|
||||
|
||||
lazy val `downstream-transitive` = project
|
||||
.in(file("downstream-transitive"))
|
||||
.dependsOn(upstream)
|
||||
|
||||
lazy val `downstream-direct` = project
|
||||
.in(file("downstream-direct"))
|
||||
.dependsOn(upstream)
|
||||
.settings(
|
||||
dependencyMode := DependencyMode.Direct,
|
||||
)
|
||||
|
||||
lazy val `downstream-plusone` = project
|
||||
.in(file("downstream-plusone"))
|
||||
.dependsOn(upstream)
|
||||
.settings(
|
||||
dependencyMode := DependencyMode.PlusOne,
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package example
|
||||
|
||||
object Main {
|
||||
def run(): String = Greeter.greet("world")
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package example
|
||||
|
||||
object Main {
|
||||
def run(): String = Greeter.greet("world")
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package example
|
||||
|
||||
object Main {
|
||||
def run(): String = Greeter.greet("world")
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Regression test for https://github.com/sbt/sbt/issues/9343
|
||||
# With usePipelining := true, a downstream Scala source that references a
|
||||
# Java class defined in an upstream project must still compile; the early
|
||||
# pickle artifacts from upstream must stay visible on the pipelined classpath.
|
||||
# Three downstream projects exercise Transitive, Direct, and PlusOne modes.
|
||||
# A single aggregate compile fans out from one upstream pickle-producing task
|
||||
# to all three downstream compilations without reusing a pipelining promise.
|
||||
|
||||
> compile
|
||||
|
||||
$ exists target/**/downstream-transitive/classes/example/Main.class
|
||||
$ exists target/**/downstream-direct/classes/example/Main.class
|
||||
$ exists target/**/downstream-plusone/classes/example/Main.class
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package example;
|
||||
|
||||
public class Greeter {
|
||||
public static String greet(String name) {
|
||||
return "Hello, " + name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package example
|
||||
|
||||
object Marker {
|
||||
val tag = "upstream"
|
||||
}
|
||||
Loading…
Reference in New Issue