Merge pull request #6609 from adpi2/bsp-resilience

Make BSP requests robust to some target failures
This commit is contained in:
eugene yokota 2021-07-30 09:35:51 -04:00 committed by GitHub
commit 4f18f7473f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 34 deletions

View File

@ -90,16 +90,16 @@ object BuildServerProtocol {
val workspace = Keys.bspFullWorkspace.value
val state = Keys.state.value
val allTargets = ScopeFilter.in(workspace.scopes.values.toSeq)
val sbtTargets: List[Def.Initialize[Task[BuildTarget]]] = workspace.builds.map {
val sbtTargets = workspace.builds.map {
case (buildTargetIdentifier, loadedBuildUnit) =>
val buildFor = workspace.buildToScope.getOrElse(buildTargetIdentifier, Nil)
sbtBuildTarget(loadedBuildUnit, buildTargetIdentifier, buildFor)
sbtBuildTarget(loadedBuildUnit, buildTargetIdentifier, buildFor).result
}.toList
Def.task {
val buildTargets = Keys.bspBuildTarget.all(allTargets).value.toVector
val allBuildTargets = buildTargets ++ sbtTargets.join.value
state.respondEvent(WorkspaceBuildTargetsResult(allBuildTargets))
allBuildTargets
val buildTargets = Keys.bspBuildTarget.result.all(allTargets).value
val successfulBuildTargets = anyOrThrow(buildTargets ++ sbtTargets.join.value)
state.respondEvent(WorkspaceBuildTargetsResult(successfulBuildTargets.toVector))
successfulBuildTargets
}
}.value,
// https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request
@ -110,8 +110,8 @@ object BuildServerProtocol {
val filter = ScopeFilter.in(workspace.scopes.values.toList)
// run the worker task concurrently
Def.task {
val items = bspBuildTargetSourcesItem.all(filter).value
val buildItems = workspace.builds.toVector.map {
val items = bspBuildTargetSourcesItem.result.all(filter).value
val buildItems = workspace.builds.map {
case (id, loadedBuildUnit) =>
val base = loadedBuildUnit.localBase
val sbtFiles = configurationSources(base)
@ -130,9 +130,10 @@ object BuildServerProtocol {
add(pluginData.managedSourceDirectories, SourceItemKind.Directory, generated = true)
add(pluginData.managedSources, SourceItemKind.File, generated = true)
add(sbtFiles, SourceItemKind.File, generated = false)
SourcesItem(id, all.result())
Value(SourcesItem(id, all.result()))
}
val result = SourcesResult((items ++ buildItems).toVector)
val successfulItems = anyOrThrow(items ++ buildItems)
val result = SourcesResult(successfulItems.toVector)
s.respondEvent(result)
}
}.evaluated,
@ -145,8 +146,9 @@ object BuildServerProtocol {
val filter = ScopeFilter.in(workspace.scopes.values.toList)
// run the worker task concurrently
Def.task {
val items = bspBuildTargetResourcesItem.all(filter).value
val result = ResourcesResult(items.toVector)
val items = bspBuildTargetResourcesItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = ResourcesResult(successfulItems.toVector)
s.respondEvent(result)
}
}.evaluated,
@ -159,8 +161,9 @@ object BuildServerProtocol {
// run the worker task concurrently
Def.task {
import sbt.internal.bsp.codec.JsonProtocol._
val items = bspBuildTargetDependencySourcesItem.all(filter).value
val result = DependencySourcesResult(items.toVector)
val items = bspBuildTargetDependencySourcesItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = DependencySourcesResult(successfulItems.toVector)
s.respondEvent(result)
}
}.evaluated,
@ -172,8 +175,12 @@ object BuildServerProtocol {
workspace.warnIfBuildsNonEmpty(Method.Compile, s.log)
val filter = ScopeFilter.in(workspace.scopes.values.toList)
Def.task {
val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max
s.respondEvent(BspCompileResult(None, statusCode))
val statusCodes = Keys.bspBuildTargetCompileItem.result.all(filter).value
val aggregatedStatusCode = allOrThrow(statusCodes) match {
case Seq() => StatusCode.Success
case codes => codes.max
}
s.respondEvent(BspCompileResult(None, aggregatedStatusCode))
}
}.evaluated,
bspBuildTargetCompile / aggregate := false,
@ -188,7 +195,7 @@ object BuildServerProtocol {
val filter = ScopeFilter.in(workspace.scopes.values.toList)
Def.task {
val items = bspBuildTargetScalacOptionsItem.all(filter).value
val items = bspBuildTargetScalacOptionsItem.result.all(filter).value
val appProvider = appConfiguration.value.provider()
val sbtJars = appProvider.mainClasspath()
val buildItems = builds.map {
@ -197,14 +204,16 @@ object BuildServerProtocol {
val scalacOptions = plugins.pluginData.scalacOptions
val pluginClassPath = plugins.classpath
val classpath = (pluginClassPath ++ sbtJars).map(_.toURI).toVector
ScalacOptionsItem(
val item = ScalacOptionsItem(
build._1,
scalacOptions.toVector,
classpath,
new File(build._2.localBase, "project/target").toURI
)
Value(item)
}
val result = ScalacOptionsResult((items ++ buildItems).toVector)
val successfulItems = anyOrThrow(items ++ buildItems)
val result = ScalacOptionsResult(successfulItems.toVector)
s.respondEvent(result)
}
}.evaluated,
@ -216,8 +225,9 @@ object BuildServerProtocol {
workspace.warnIfBuildsNonEmpty(Method.ScalaTestClasses, s.log)
val filter = ScopeFilter.in(workspace.scopes.values.toList)
Def.task {
val items = bspScalaTestClassesItem.all(filter).value
val result = ScalaTestClassesResult(items.toVector, None)
val items = bspScalaTestClassesItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = ScalaTestClassesResult(successfulItems.toVector, None)
s.respondEvent(result)
}
}.evaluated,
@ -228,8 +238,9 @@ object BuildServerProtocol {
workspace.warnIfBuildsNonEmpty(Method.ScalaMainClasses, s.log)
val filter = ScopeFilter.in(workspace.scopes.values.toList)
Def.task {
val items = bspScalaMainClassesItem.all(filter).value
val result = ScalaMainClassesResult(items.toVector, None)
val items = bspScalaMainClassesItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = ScalaMainClassesResult(successfulItems.toVector, None)
s.respondEvent(result)
}
}.evaluated,
@ -846,6 +857,20 @@ object BuildServerProtocol {
case _ => sys.error(s"unexpected $ref")
}
private def anyOrThrow[T](results: Seq[Result[T]]): Seq[T] = {
val successes = results.collect { case Value(v) => v }
val errors = results.collect { case Inc(cause) => cause }
if (successes.nonEmpty || errors.isEmpty) successes
else throw Incomplete(None, causes = errors)
}
private def allOrThrow[T](results: Seq[Result[T]]): Seq[T] = {
val successes = results.collect { case Value(v) => v }
val errors = results.collect { case Inc(cause) => cause }
if (errors.isEmpty) successes
else throw Incomplete(None, causes = errors)
}
private case class SemanticVersion(major: Int, minor: Int) extends Ordered[SemanticVersion] {
override def compare(that: SemanticVersion): Int = {
if (that.major != major) major.compare(that.major)

View File

@ -25,3 +25,18 @@ lazy val respondError = project.in(file("respond-error"))
)
lazy val util = project
def somethingBad = throw new MessageOnlyException("I am a bad build target")
// other build targets should not be affected by this bad build target
lazy val badBuildTarget = project.in(file("bad-build-target"))
.settings(
Compile / bspBuildTarget := somethingBad,
Compile / bspBuildTargetSourcesItem := somethingBad,
Compile / bspBuildTargetResourcesItem := somethingBad,
Compile / bspBuildTargetDependencySourcesItem := somethingBad,
Compile / bspBuildTargetScalacOptionsItem := somethingBad,
Compile / bspBuildTargetCompileItem := somethingBad,
Compile / bspScalaMainClasses := somethingBad,
Test / bspBuildTarget := somethingBad,
Test / bspScalaTestClasses := somethingBad,
)

View File

@ -7,8 +7,7 @@
package testpkg
import sbt.internal.bsp.SourcesResult
import sbt.internal.bsp.WorkspaceBuildTargetsResult
import sbt.internal.bsp.{ BspCompileResult, SourcesResult, StatusCode, WorkspaceBuildTargetsResult }
import sbt.internal.langserver.ErrorCodes
import sbt.IO
@ -41,13 +40,15 @@ object BuildServerTest extends AbstractServerTest {
val buildServerBuildTarget =
result.targets.find(_.displayName.contains("buildserver-build")).get
assert(buildServerBuildTarget.id.uri.toString.endsWith("#buildserver-build"))
assert(!result.targets.exists(_.displayName.contains("badBuildTarget")))
}
test("buildTarget/sources") { _ =>
val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "24", "method": "buildTarget/sources", "params": {
| "targets": [{ "uri": "$buildTarget" }]
| "targets": [{ "uri": "$buildTarget" }, { "uri": "$badBuildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/sources"))
@ -88,17 +89,16 @@ object BuildServerTest extends AbstractServerTest {
|} }""".stripMargin
)
assert(processing("buildTarget/compile"))
assert(svr.waitForString(10.seconds) { s =>
(s contains """"id":"32"""") &&
(s contains """"statusCode":1""")
})
val res = svr.waitFor[BspCompileResult](10.seconds)
assert(res.statusCode == StatusCode.Success)
}
test("buildTarget/scalacOptions") { _ =>
val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "40", "method": "buildTarget/scalacOptions", "params": {
| "targets": [{ "uri": "$buildTarget" }]
| "targets": [{ "uri": "$buildTarget" }, { "uri": "$badBuildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/scalacOptions"))
@ -176,9 +176,10 @@ object BuildServerTest extends AbstractServerTest {
test("buildTarget/scalaMainClasses") { _ =>
val buildTarget = buildTargetUri("runAndTest", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "56", "method": "buildTarget/scalaMainClasses", "params": {
| "targets": [{ "uri": "$buildTarget" }]
| "targets": [{ "uri": "$buildTarget" }, { "uri": "$badBuildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/scalaMainClasses"))
@ -210,9 +211,10 @@ object BuildServerTest extends AbstractServerTest {
test("buildTarget/scalaTestClasses") { _ =>
val buildTarget = buildTargetUri("runAndTest", "Test")
val badBuildTarget = buildTargetUri("badBuildTarget", "Test")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "72", "method": "buildTarget/scalaTestClasses", "params": {
| "targets": [{ "uri": "$buildTarget" }]
| "targets": [{ "uri": "$buildTarget" }, { "uri": "$badBuildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/scalaTestClasses"))
@ -305,9 +307,10 @@ object BuildServerTest extends AbstractServerTest {
test("buildTarget/resources") { _ =>
val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "96", "method": "buildTarget/resources", "params": {
| "targets": [{ "uri": "$buildTarget" }]
| "targets": [{ "uri": "$buildTarget" }, { "uri": "$badBuildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/resources"))