From 95c6d42f8ed1ea855f9697933073994eae7cfa2a Mon Sep 17 00:00:00 2001 From: PandaMan Date: Sat, 7 Feb 2026 11:01:49 -0500 Subject: [PATCH] [2.x] fix : BSP compile returns StatusCode.Error on failure (#8104) (#8709) - BuildServerProtocol: for Result.Inc(cause), return StatusCode.Error for any non-InterruptedException (was throwing for non-CompileFailed, causing JSON-RPC error instead of BspCompileResult with statusCode Error) - BuildServerTest: add test 'buildTarget/compile - returns StatusCode.Error when compilation fails' (introduce compile error, compile via BSP, assert statusCode == Error) --- .gitignore | 1 + .../internal/server/BuildServerProtocol.scala | 12 ++++--- .../test/scala/testpkg/BuildServerTest.scala | 36 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c924d2a62..42f4ebf74 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ launcher-package/citest/freshly-baked .vscode sbt-launch.jar local-temp +.jdk diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 3f9286e78..e515dd9d4 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -23,7 +23,7 @@ import sbt.StandardMain.exchange import sbt.internal.bsp.* import sbt.internal.langserver.ErrorCodes import sbt.internal.protocol.JsonRpcRequestMessage -import sbt.internal.util.{ Attributed, ErrorHandling } +import sbt.internal.util.{ Attributed, ErrorHandling, MessageOnlyException } import sbt.internal.util.complete.{ Parser, Parsers } import sbt.librarymanagement.CrossVersion.binaryScalaVersion import sbt.librarymanagement.{ Configuration, ScalaArtifacts, UpdateReport } @@ -31,7 +31,6 @@ import sbt.std.TaskExtra import sbt.util.Logger import sjsonnew.shaded.scalajson.ast.unsafe.{ JNull, JValue } import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser as JsonParser } -import xsbti.CompileFailed import java.io.File import java.nio.file.Paths @@ -879,9 +878,14 @@ object BuildServerProtocol { case Result.Value(_) => StatusCode.Success case Result.Inc(cause) => cause.getCause match { - case _: CompileFailed => StatusCode.Error case _: InterruptedException => StatusCode.Cancelled - case err => throw cause + case _: MessageOnlyException => + // Rethrow so task failure path sends JSON-RPC error (e.g. respondError project) + throw cause + case _ => + // Return Error for any compile failure (CompileFailed or other Incomplete) + // so BSP returns a proper BspCompileResult instead of a JSON-RPC error (#8104) + StatusCode.Error } } } diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 7466bf68b..2e653b299 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -240,6 +240,42 @@ class BuildServerTest extends AbstractServerTest { ) } + test("buildTarget/compile - returns StatusCode.Error when compilation fails") { + // Reproduces #8104: failed BSP compile must return BspCompileResult with statusCode Error, + // not a JSON-RPC error. Placed after diagnostics tests so shared message queue is not polluted. + val buildTarget = buildTargetUri("diagnostics", "Compile") + val mainFile = new File(svr.baseDirectory, "diagnostics/src/main/scala/Diagnostics.scala") + val original = IO.read(mainFile) + try { + IO.write( + mainFile, + """|object Diagnostics { + | private val a: Int = "" + |}""".stripMargin + ) + compile(buildTarget) + val res = svr.waitFor[BspCompileResult](30.seconds) + assert( + res.statusCode == StatusCode.Error, + s"expected StatusCode.Error, got ${res.statusCode}" + ) + // Drain notifications from this failing compile so the next test does not consume them + def ourFailureNotification(s: String): Boolean = + (s.contains("build/taskStart") && s.contains("diagnostics")) || + (s.contains("build/taskFinish") && s.contains("Compiled diagnostics") && s.contains( + "\"status\":2" + )) || + (s.contains("build/publishDiagnostics") && s.contains("Diagnostics.scala") && s.contains( + "type mismatch" + )) + svr.waitForString(30.seconds)(ourFailureNotification) + svr.waitForString(30.seconds)(ourFailureNotification) + svr.waitForString(30.seconds)(ourFailureNotification) + } finally { + IO.write(mainFile, original) + } + } + test("buildTarget/compile: Java diagnostics") { val buildTarget = buildTargetUri("javaProj", "Compile")