Merge pull request #9445 from eed3si9n/bport2/mix

[2.0.x] Backports
This commit is contained in:
eugene yokota 2026-07-12 21:50:11 -04:00 committed by GitHub
commit f80e2c38c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 154 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -0,0 +1,5 @@
package example
object Main {
def run(): String = Greeter.greet("world")
}

View File

@ -0,0 +1,5 @@
package example
object Main {
def run(): String = Greeter.greet("world")
}

View File

@ -0,0 +1,5 @@
package example
object Main {
def run(): String = Greeter.greet("world")
}

View File

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

View File

@ -0,0 +1,7 @@
package example;
public class Greeter {
public static String greet(String name) {
return "Hello, " + name;
}
}

View File

@ -0,0 +1,5 @@
package example
object Marker {
val tag = "upstream"
}