mirror of https://github.com/sbt/sbt.git
commit
f80e2c38c8
|
|
@ -2839,7 +2839,7 @@ object Classpaths {
|
||||||
case (false, _) =>
|
case (false, _) =>
|
||||||
Def.task { filteredDependencyClasspath.value }
|
Def.task { filteredDependencyClasspath.value }
|
||||||
case (true, DependencyMode.Transitive) =>
|
case (true, DependencyMode.Transitive) =>
|
||||||
Def.task { dependencyClasspath.value }
|
Def.task { internalDependencyPicklePath.value ++ externalDependencyClasspath.value }
|
||||||
case (true, DependencyMode.Direct) =>
|
case (true, DependencyMode.Direct) =>
|
||||||
Def.task {
|
Def.task {
|
||||||
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
val internalFiltered = ClasspathImpl.filterInternalByMode(
|
||||||
|
|
@ -2847,7 +2847,7 @@ object Classpaths {
|
||||||
thisProjectRef.value,
|
thisProjectRef.value,
|
||||||
settingsData.value,
|
settingsData.value,
|
||||||
buildDependencies.value,
|
buildDependencies.value,
|
||||||
internalDependencyClasspath.value,
|
internalDependencyPicklePath.value,
|
||||||
)
|
)
|
||||||
val externalFiltered = ClasspathImpl.filterByDirectDeps(
|
val externalFiltered = ClasspathImpl.filterByDirectDeps(
|
||||||
allDependencies.value,
|
allDependencies.value,
|
||||||
|
|
@ -2862,7 +2862,7 @@ object Classpaths {
|
||||||
thisProjectRef.value,
|
thisProjectRef.value,
|
||||||
settingsData.value,
|
settingsData.value,
|
||||||
buildDependencies.value,
|
buildDependencies.value,
|
||||||
internalDependencyClasspath.value,
|
internalDependencyPicklePath.value,
|
||||||
)
|
)
|
||||||
val externalFiltered = ClasspathImpl.filterByPlusOne(
|
val externalFiltered = ClasspathImpl.filterByPlusOne(
|
||||||
allDependencies.value,
|
allDependencies.value,
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ final class NetworkChannel(
|
||||||
private val pendingWrites = new LinkedBlockingQueue[(Array[Byte], Boolean)]()
|
private val pendingWrites = new LinkedBlockingQueue[(Array[Byte], Boolean)]()
|
||||||
private val attached = new AtomicBoolean(false)
|
private val attached = new AtomicBoolean(false)
|
||||||
private val alive = new AtomicBoolean(true)
|
private val alive = new AtomicBoolean(true)
|
||||||
|
private val isCanceled = new AtomicBoolean(false)
|
||||||
private[sbt] def isInteractive = interactive.get
|
private[sbt] def isInteractive = interactive.get
|
||||||
private val interactive = new AtomicBoolean(false)
|
private val interactive = new AtomicBoolean(false)
|
||||||
private[sbt] def setInteractive(id: String, value: Boolean) = {
|
private[sbt] def setInteractive(id: String, value: Boolean) = {
|
||||||
|
|
@ -109,6 +110,10 @@ final class NetworkChannel(
|
||||||
interactive.set(true)
|
interactive.set(true)
|
||||||
jsonRpcNotify(promptChannel, "")
|
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[sbt] def write(byte: Byte) = inputBuffer.add(byte.toInt)
|
||||||
|
|
||||||
private val terminalHolder = new AtomicReference[Terminal](Terminal.NullTerminal)
|
private val terminalHolder = new AtomicReference[Terminal](Terminal.NullTerminal)
|
||||||
|
|
@ -532,6 +537,8 @@ final class NetworkChannel(
|
||||||
StandardMain.exchange.currentExec.exists(_.source.exists(_.channelName == name)))
|
StandardMain.exchange.currentExec.exists(_.source.exists(_.channelName == name)))
|
||||||
) {
|
) {
|
||||||
runningEngine.cancelAndShutdown()
|
runningEngine.cancelAndShutdown()
|
||||||
|
isCanceled.set(true)
|
||||||
|
discardPending()
|
||||||
|
|
||||||
respondResult(
|
respondResult(
|
||||||
ExecStatusEvent(
|
ExecStatusEvent(
|
||||||
|
|
@ -625,16 +632,19 @@ final class NetworkChannel(
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Notify to Language Server's client. */
|
/** Notify to Language Server's client. */
|
||||||
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = {
|
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit =
|
||||||
val m =
|
if (isCanceled.get && NetworkChannel.isCanceledOutput(method))
|
||||||
JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get))
|
()
|
||||||
if (method != Serialization.systemOut) {
|
else {
|
||||||
forceFlush()
|
val m =
|
||||||
log.debug(s"jsonRpcNotify: $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. */
|
/** Notify to Language Server's client. */
|
||||||
private[sbt] def jsonRpcRequest[A: JsonFormat](id: String, method: String, params: A): Unit = {
|
private[sbt] def jsonRpcRequest[A: JsonFormat](id: String, method: String, params: A): Unit = {
|
||||||
|
|
@ -675,6 +685,11 @@ final class NetworkChannel(
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters.*
|
import scala.jdk.CollectionConverters.*
|
||||||
private val outputBuffer = new LinkedBlockingQueue[Byte]
|
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
|
// Batches writes to the client at most once per 20ms to cut terminal flicker (see
|
||||||
// CoalescingFlusher). forceFlush drains now while leaving the timer live.
|
// CoalescingFlusher). forceFlush drains now while leaving the timer live.
|
||||||
private val flusher =
|
private val flusher =
|
||||||
|
|
@ -942,6 +957,9 @@ final class NetworkChannel(
|
||||||
}
|
}
|
||||||
|
|
||||||
object NetworkChannel {
|
object NetworkChannel {
|
||||||
|
private[server] def isCanceledOutput(method: String): Boolean =
|
||||||
|
method == Serialization.systemOut || method == Serialization.systemErr
|
||||||
|
|
||||||
private[sbt] def cancel(
|
private[sbt] def cancel(
|
||||||
execID: Option[String],
|
execID: Option[String],
|
||||||
id: 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
|
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() {
|
run() {
|
||||||
# Copy preloaded repo to user's preloaded directory
|
# Copy preloaded repo to user's preloaded directory
|
||||||
syncPreloaded
|
syncPreloaded
|
||||||
|
|
@ -594,12 +602,6 @@ run() {
|
||||||
echoerr ""
|
echoerr ""
|
||||||
echoerr "[info] sbt runner (sbt-the-shell-script) is a runner to run any declared version of sbt."
|
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."
|
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
|
else
|
||||||
checkWorkingDirectory
|
checkWorkingDirectory
|
||||||
# run sbt
|
# run sbt
|
||||||
|
|
@ -962,6 +964,11 @@ if [[ $print_sbt_script_version ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $shutdownall ]]; then
|
||||||
|
runShutdownAll
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
java_version="$(jdk_version)"
|
java_version="$(jdk_version)"
|
||||||
vlog "[process_args] java_version = '$java_version'"
|
vlog "[process_args] java_version = '$java_version'"
|
||||||
checkJava17ForSbt2
|
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