mirror of https://github.com/sbt/sbt.git
Merge pull request #6609 from adpi2/bsp-resilience
Make BSP requests robust to some target failures
This commit is contained in:
commit
4f18f7473f
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
Loading…
Reference in New Issue