mirror of https://github.com/sbt/sbt.git
Refine and test SBT BSP target
- Pass source dirs and current list of files - Align display name and URI
This commit is contained in:
parent
0bd736be2a
commit
fe046476b1
|
|
@ -144,7 +144,9 @@ final case class PluginData(
|
|||
resolvers: Option[Vector[Resolver]],
|
||||
report: Option[UpdateReport],
|
||||
scalacOptions: Seq[String],
|
||||
unmanagedSourceDirectories: Seq[File],
|
||||
unmanagedSources: Seq[File],
|
||||
managedSourceDirectories: Seq[File],
|
||||
managedSources: Seq[File]
|
||||
) {
|
||||
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
|
||||
|
|
@ -152,7 +154,7 @@ final case class PluginData(
|
|||
|
||||
object PluginData {
|
||||
private[sbt] def apply(dependencyClasspath: Def.Classpath): PluginData =
|
||||
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil)
|
||||
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil)
|
||||
}
|
||||
|
||||
object EvaluateTask {
|
||||
|
|
|
|||
|
|
@ -1164,16 +1164,20 @@ private[sbt] object Load {
|
|||
val prod = (Configurations.Runtime / exportedProducts).value
|
||||
val cp = (Configurations.Runtime / fullClasspath).value
|
||||
val opts = (Configurations.Compile / scalacOptions).value
|
||||
val managedSrcs = (Configurations.Compile / managedSources).value
|
||||
val unmanagedSrcDirs = (Configurations.Compile / unmanagedSourceDirectories).value
|
||||
val unmanagedSrcs = (Configurations.Compile / unmanagedSources).value
|
||||
val managedSrcDirs = (Configurations.Compile / managedSourceDirectories).value
|
||||
val managedSrcs = (Configurations.Compile / managedSources).value
|
||||
PluginData(
|
||||
removeEntries(cp, prod),
|
||||
prod,
|
||||
Some(fullResolvers.value.toVector),
|
||||
Some(update.value),
|
||||
opts,
|
||||
unmanagedSrcDirs,
|
||||
unmanagedSrcs,
|
||||
managedSrcs
|
||||
managedSrcDirs,
|
||||
managedSrcs,
|
||||
)
|
||||
},
|
||||
scalacOptions += "-Wconf:cat=unused-nowarn:s",
|
||||
|
|
@ -1229,7 +1233,7 @@ private[sbt] object Load {
|
|||
loadPluginDefinition(
|
||||
dir,
|
||||
config,
|
||||
PluginData(config.globalPluginClasspath, Nil, None, None, Nil, Nil, Nil)
|
||||
PluginData(config.globalPluginClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil)
|
||||
)
|
||||
|
||||
def buildPlugins(dir: File, s: State, config: LoadBuildConfiguration): LoadedPlugins =
|
||||
|
|
@ -1423,6 +1427,8 @@ final case class LoadBuildConfiguration(
|
|||
Some(data.updateReport),
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil
|
||||
)
|
||||
case None => PluginData(globalPluginClasspath)
|
||||
|
|
|
|||
|
|
@ -132,15 +132,21 @@ object BuildServerProtocol {
|
|||
val base = loadedBuildUnit.localBase
|
||||
val sbtFiles = configurationSources(base)
|
||||
val pluginData = loadedBuildUnit.unit.plugins.pluginData
|
||||
val unmanagedSources = pluginData.unmanagedSources.map(
|
||||
f => SourceItem(f.toURI, SourceItemKind.File, generated = false)
|
||||
)
|
||||
val managedSources = pluginData.managedSources.map(
|
||||
f => SourceItem(f.toURI, SourceItemKind.File, generated = true)
|
||||
)
|
||||
val sbtFilesItems =
|
||||
sbtFiles.map(f => SourceItem(f.toURI, SourceItemKind.File, generated = false))
|
||||
SourcesItem(id, (unmanagedSources ++ managedSources ++ sbtFilesItems).toVector)
|
||||
val all = Vector.newBuilder[SourceItem]
|
||||
def add(fs: Seq[File], sourceItemKind: Int, generated: Boolean): Unit = {
|
||||
fs.foreach(f => all += (SourceItem(f.toURI, sourceItemKind, generated = generated)))
|
||||
}
|
||||
all += (SourceItem(
|
||||
loadedBuildUnit.unit.plugins.base.toURI,
|
||||
SourceItemKind.Directory,
|
||||
generated = false
|
||||
))
|
||||
add(pluginData.unmanagedSourceDirectories, SourceItemKind.Directory, generated = false)
|
||||
add(pluginData.unmanagedSources, SourceItemKind.File, generated = false)
|
||||
add(pluginData.managedSourceDirectories, SourceItemKind.Directory, generated = true)
|
||||
add(pluginData.managedSources, SourceItemKind.File, generated = true)
|
||||
add(sbtFiles, SourceItemKind.File, generated = false)
|
||||
SourcesItem(id, all.result())
|
||||
}
|
||||
val result = SourcesResult((items ++ buildItems).toVector)
|
||||
s.respondEvent(result)
|
||||
|
|
@ -499,13 +505,17 @@ object BuildServerProtocol {
|
|||
scope.project.toOption match {
|
||||
case Some(ProjectRef(buildUri, _)) =>
|
||||
val loadedBuildUnit = loadedBuild.units(buildUri)
|
||||
buildsMap.getOrElseUpdate(toId(loadedBuildUnit), new mutable.ListBuffer) += targetId
|
||||
buildsMap.getOrElseUpdate(
|
||||
toSbtTargetId(loadedBuildUnit),
|
||||
new mutable.ListBuffer
|
||||
) += targetId
|
||||
}
|
||||
targetId -> scope
|
||||
}
|
||||
val buildMap = if (bspSbtEnabled.value) {
|
||||
for (loadedBuildUnit <- loadedBuild.units.values) yield {
|
||||
toId(loadedBuildUnit) -> loadedBuildUnit
|
||||
val rootProjectId = loadedBuildUnit.root
|
||||
toSbtTargetId(loadedBuildUnit) -> loadedBuildUnit
|
||||
}
|
||||
} else {
|
||||
Nil
|
||||
|
|
@ -557,7 +567,6 @@ object BuildServerProtocol {
|
|||
buildTargetIdentifier: BuildTargetIdentifier,
|
||||
buildFor: Seq[BuildTargetIdentifier]
|
||||
): Def.Initialize[Task[BuildTarget]] = Def.task {
|
||||
val structure = buildStructure.value
|
||||
val scalaProvider = appConfiguration.value.provider().scalaProvider()
|
||||
appConfiguration.value.provider().mainClasspath()
|
||||
val scalaJars = scalaProvider.jars()
|
||||
|
|
@ -579,9 +588,7 @@ object BuildServerProtocol {
|
|||
|
||||
BuildTarget(
|
||||
buildTargetIdentifier,
|
||||
// naming convention still seems like the only way to get IntelliJ to import this correctly
|
||||
// https://github.com/JetBrains/intellij-scala/blob/a54c2a7c157236f35957049cbfd8c10587c9e60c/scala/scala-impl/src/org/jetbrains/sbt/language/SbtFileImpl.scala#L82-L84
|
||||
structure.rootProject(loadedUnit.unit.uri) + "-build",
|
||||
toSbtTargetIdName(loadedUnit),
|
||||
projectStandard(loadedUnit.unit.localBase).toURI,
|
||||
Vector(),
|
||||
BuildTargetCapabilities(canCompile = false, canTest = false, canRun = false),
|
||||
|
|
@ -828,14 +835,19 @@ object BuildServerProtocol {
|
|||
)
|
||||
}
|
||||
|
||||
private val SbtBuildSuffix = "#sbt-build"
|
||||
private def toId(ref: LoadedBuildUnit): BuildTargetIdentifier = {
|
||||
// naming convention still seems like the only reliable way to get IntelliJ to import this correctly
|
||||
// https://github.com/JetBrains/intellij-scala/blob/a54c2a7c157236f35957049cbfd8c10587c9e60c/scala/scala-impl/src/org/jetbrains/sbt/language/SbtFileImpl.scala#L82-L84
|
||||
private def toSbtTargetIdName(ref: LoadedBuildUnit): String = {
|
||||
ref.root + "-build"
|
||||
}
|
||||
private def toSbtTargetId(ref: LoadedBuildUnit): BuildTargetIdentifier = {
|
||||
val name = toSbtTargetIdName(ref)
|
||||
val build = ref.unit.uri
|
||||
val sanitized = build.toString.indexOf("#") match {
|
||||
case i if i > 0 => build.toString.take(i)
|
||||
case _ => build.toString
|
||||
}
|
||||
BuildTargetIdentifier(new URI(sanitized + SbtBuildSuffix))
|
||||
BuildTargetIdentifier(new URI(sanitized + "#" + name))
|
||||
}
|
||||
private def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
||||
ref match {
|
||||
|
|
@ -879,7 +891,9 @@ object BuildServerProtocol {
|
|||
}
|
||||
def warnIfBuildsNonEmpty(method: String, log: Logger): Unit = {
|
||||
if (builds.nonEmpty)
|
||||
log.warn(s"$method is a no-op for build.sbt targets: ${builds.keys.mkString("[", ",", "]")}")
|
||||
log.warn(
|
||||
s"$method is a no-op for build.sbt targets: ${builds.keys.mkString("[", ",", "]")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,18 @@
|
|||
|
||||
package testpkg
|
||||
|
||||
import sbt.internal.bsp.SourcesResult
|
||||
|
||||
import java.io.File
|
||||
import sbt.internal.bsp.WorkspaceBuildTargetsResult
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
// starts svr using server-test/buildserver and perform custom server tests
|
||||
object BuildServerTest extends AbstractServerTest {
|
||||
|
||||
import sbt.internal.bsp.codec.JsonProtocol._
|
||||
|
||||
override val testDirectory: String = "buildserver"
|
||||
|
||||
test("build/initialize") { _ =>
|
||||
|
|
@ -26,12 +34,12 @@ object BuildServerTest extends AbstractServerTest {
|
|||
"""{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }"""
|
||||
)
|
||||
assert(processing("workspace/buildTargets"))
|
||||
assert {
|
||||
svr.waitForString(10.seconds) { s =>
|
||||
(s contains """"id":"16"""") &&
|
||||
(s contains """"displayName":"util"""")
|
||||
}
|
||||
}
|
||||
val result = svr.waitFor[WorkspaceBuildTargetsResult](10.seconds)
|
||||
val utilTarget = result.targets.find(_.displayName.contains("util")).get
|
||||
assert(utilTarget.id.uri.toString.endsWith("#util/Compile"))
|
||||
val buildServerBuildTarget =
|
||||
result.targets.find(_.displayName.contains("buildserver-build")).get
|
||||
assert(buildServerBuildTarget.id.uri.toString.endsWith("#buildserver-build"))
|
||||
}
|
||||
|
||||
test("buildTarget/sources") { _ =>
|
||||
|
|
@ -42,10 +50,33 @@ object BuildServerTest extends AbstractServerTest {
|
|||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/sources"))
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
(s contains """"id":"24"""") &&
|
||||
(s contains "util/src/main/scala")
|
||||
})
|
||||
val s = svr.waitFor[SourcesResult](10.seconds)
|
||||
val sources = s.items.head.sources.map(_.uri)
|
||||
assert(sources.contains(new File(svr.baseDirectory, "util/src/main/scala").toURI))
|
||||
}
|
||||
test("buildTarget/sources SBT") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#buildserver-build"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "25", "method": "buildTarget/sources", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/sources"))
|
||||
val s = svr.waitFor[SourcesResult](10.seconds)
|
||||
val sources = s.items.head.sources.map(_.uri).sorted
|
||||
val expectedSources = Vector(
|
||||
"build.sbt",
|
||||
"project/",
|
||||
"project/A.scala",
|
||||
"project/src/main/java",
|
||||
"project/src/main/scala-2",
|
||||
"project/src/main/scala-2.12",
|
||||
"project/src/main/scala-sbt-1.0",
|
||||
"project/src/main/scala/",
|
||||
"project/src/main/scala/B.scala",
|
||||
"project/target/scala-2.12/sbt-1.0/src_managed/main"
|
||||
).map(rel => new File(svr.baseDirectory.getAbsoluteFile, rel).toURI).sorted
|
||||
assert(sources == expectedSources)
|
||||
}
|
||||
|
||||
test("buildTarget/compile") { _ =>
|
||||
|
|
|
|||
|
|
@ -12,17 +12,18 @@ import java.net.Socket
|
|||
import java.nio.file.{ Files, Path }
|
||||
import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import verify._
|
||||
import sbt.{ ForkOptions, OutputStrategy, RunFromSourceMain }
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
import sbt.protocol.ClientSocket
|
||||
import sjsonnew.JsonReader
|
||||
import sjsonnew.support.scalajson.unsafe.{ Converter, Parser }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.{ Success, Try }
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
||||
trait AbstractServerTest extends TestSuite[Unit] {
|
||||
private var temp: File = _
|
||||
|
|
@ -293,6 +294,57 @@ case class TestServer(
|
|||
}
|
||||
impl()
|
||||
}
|
||||
final def waitFor[T: JsonReader](duration: FiniteDuration): T = {
|
||||
val deadline = duration.fromNow
|
||||
var lastEx: Throwable = null
|
||||
@tailrec def impl(): T =
|
||||
lines.poll(deadline.timeLeft.toMillis, TimeUnit.MILLISECONDS) match {
|
||||
case null =>
|
||||
if (lastEx != null) throw lastEx
|
||||
else throw new TimeoutException
|
||||
case s =>
|
||||
Parser
|
||||
.parseFromString(s)
|
||||
.flatMap(
|
||||
jvalue =>
|
||||
Converter.fromJson[T](
|
||||
jvalue.toStandard
|
||||
.asInstanceOf[sjsonnew.shaded.scalajson.ast.JObject]
|
||||
.value("result")
|
||||
.toUnsafe
|
||||
)
|
||||
) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(exception) =>
|
||||
if (deadline.isOverdue) {
|
||||
val ex = new TimeoutException()
|
||||
ex.initCause(exception)
|
||||
throw ex
|
||||
} else {
|
||||
lastEx = exception
|
||||
impl()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl()
|
||||
}
|
||||
final def waitForResponse(duration: FiniteDuration, id: Int): String = {
|
||||
val deadline = duration.fromNow
|
||||
@tailrec def impl(): String =
|
||||
lines.poll(deadline.timeLeft.toMillis, TimeUnit.MILLISECONDS) match {
|
||||
case null =>
|
||||
throw new TimeoutException()
|
||||
case s =>
|
||||
val s1 = s
|
||||
val correctId = s1.contains("\"id\":\"" + id + "\"")
|
||||
if (!correctId && !deadline.isOverdue) impl()
|
||||
else if (deadline.isOverdue)
|
||||
throw new TimeoutException()
|
||||
else s
|
||||
}
|
||||
impl()
|
||||
}
|
||||
|
||||
final def neverReceive(duration: FiniteDuration)(f: String => Boolean): Boolean = {
|
||||
val deadline = duration.fromNow
|
||||
|
|
|
|||
Loading…
Reference in New Issue