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]],
|
resolvers: Option[Vector[Resolver]],
|
||||||
report: Option[UpdateReport],
|
report: Option[UpdateReport],
|
||||||
scalacOptions: Seq[String],
|
scalacOptions: Seq[String],
|
||||||
|
unmanagedSourceDirectories: Seq[File],
|
||||||
unmanagedSources: Seq[File],
|
unmanagedSources: Seq[File],
|
||||||
|
managedSourceDirectories: Seq[File],
|
||||||
managedSources: Seq[File]
|
managedSources: Seq[File]
|
||||||
) {
|
) {
|
||||||
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
|
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
|
||||||
|
|
@ -152,7 +154,7 @@ final case class PluginData(
|
||||||
|
|
||||||
object PluginData {
|
object PluginData {
|
||||||
private[sbt] def apply(dependencyClasspath: Def.Classpath): 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 {
|
object EvaluateTask {
|
||||||
|
|
|
||||||
|
|
@ -1164,16 +1164,20 @@ private[sbt] object Load {
|
||||||
val prod = (Configurations.Runtime / exportedProducts).value
|
val prod = (Configurations.Runtime / exportedProducts).value
|
||||||
val cp = (Configurations.Runtime / fullClasspath).value
|
val cp = (Configurations.Runtime / fullClasspath).value
|
||||||
val opts = (Configurations.Compile / scalacOptions).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 unmanagedSrcs = (Configurations.Compile / unmanagedSources).value
|
||||||
|
val managedSrcDirs = (Configurations.Compile / managedSourceDirectories).value
|
||||||
|
val managedSrcs = (Configurations.Compile / managedSources).value
|
||||||
PluginData(
|
PluginData(
|
||||||
removeEntries(cp, prod),
|
removeEntries(cp, prod),
|
||||||
prod,
|
prod,
|
||||||
Some(fullResolvers.value.toVector),
|
Some(fullResolvers.value.toVector),
|
||||||
Some(update.value),
|
Some(update.value),
|
||||||
opts,
|
opts,
|
||||||
|
unmanagedSrcDirs,
|
||||||
unmanagedSrcs,
|
unmanagedSrcs,
|
||||||
managedSrcs
|
managedSrcDirs,
|
||||||
|
managedSrcs,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scalacOptions += "-Wconf:cat=unused-nowarn:s",
|
scalacOptions += "-Wconf:cat=unused-nowarn:s",
|
||||||
|
|
@ -1229,7 +1233,7 @@ private[sbt] object Load {
|
||||||
loadPluginDefinition(
|
loadPluginDefinition(
|
||||||
dir,
|
dir,
|
||||||
config,
|
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 =
|
def buildPlugins(dir: File, s: State, config: LoadBuildConfiguration): LoadedPlugins =
|
||||||
|
|
@ -1423,6 +1427,8 @@ final case class LoadBuildConfiguration(
|
||||||
Some(data.updateReport),
|
Some(data.updateReport),
|
||||||
Nil,
|
Nil,
|
||||||
Nil,
|
Nil,
|
||||||
|
Nil,
|
||||||
|
Nil,
|
||||||
Nil
|
Nil
|
||||||
)
|
)
|
||||||
case None => PluginData(globalPluginClasspath)
|
case None => PluginData(globalPluginClasspath)
|
||||||
|
|
|
||||||
|
|
@ -132,15 +132,21 @@ object BuildServerProtocol {
|
||||||
val base = loadedBuildUnit.localBase
|
val base = loadedBuildUnit.localBase
|
||||||
val sbtFiles = configurationSources(base)
|
val sbtFiles = configurationSources(base)
|
||||||
val pluginData = loadedBuildUnit.unit.plugins.pluginData
|
val pluginData = loadedBuildUnit.unit.plugins.pluginData
|
||||||
val unmanagedSources = pluginData.unmanagedSources.map(
|
val all = Vector.newBuilder[SourceItem]
|
||||||
f => SourceItem(f.toURI, SourceItemKind.File, generated = false)
|
def add(fs: Seq[File], sourceItemKind: Int, generated: Boolean): Unit = {
|
||||||
)
|
fs.foreach(f => all += (SourceItem(f.toURI, sourceItemKind, generated = generated)))
|
||||||
val managedSources = pluginData.managedSources.map(
|
}
|
||||||
f => SourceItem(f.toURI, SourceItemKind.File, generated = true)
|
all += (SourceItem(
|
||||||
)
|
loadedBuildUnit.unit.plugins.base.toURI,
|
||||||
val sbtFilesItems =
|
SourceItemKind.Directory,
|
||||||
sbtFiles.map(f => SourceItem(f.toURI, SourceItemKind.File, generated = false))
|
generated = false
|
||||||
SourcesItem(id, (unmanagedSources ++ managedSources ++ sbtFilesItems).toVector)
|
))
|
||||||
|
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)
|
val result = SourcesResult((items ++ buildItems).toVector)
|
||||||
s.respondEvent(result)
|
s.respondEvent(result)
|
||||||
|
|
@ -499,13 +505,17 @@ object BuildServerProtocol {
|
||||||
scope.project.toOption match {
|
scope.project.toOption match {
|
||||||
case Some(ProjectRef(buildUri, _)) =>
|
case Some(ProjectRef(buildUri, _)) =>
|
||||||
val loadedBuildUnit = loadedBuild.units(buildUri)
|
val loadedBuildUnit = loadedBuild.units(buildUri)
|
||||||
buildsMap.getOrElseUpdate(toId(loadedBuildUnit), new mutable.ListBuffer) += targetId
|
buildsMap.getOrElseUpdate(
|
||||||
|
toSbtTargetId(loadedBuildUnit),
|
||||||
|
new mutable.ListBuffer
|
||||||
|
) += targetId
|
||||||
}
|
}
|
||||||
targetId -> scope
|
targetId -> scope
|
||||||
}
|
}
|
||||||
val buildMap = if (bspSbtEnabled.value) {
|
val buildMap = if (bspSbtEnabled.value) {
|
||||||
for (loadedBuildUnit <- loadedBuild.units.values) yield {
|
for (loadedBuildUnit <- loadedBuild.units.values) yield {
|
||||||
toId(loadedBuildUnit) -> loadedBuildUnit
|
val rootProjectId = loadedBuildUnit.root
|
||||||
|
toSbtTargetId(loadedBuildUnit) -> loadedBuildUnit
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Nil
|
Nil
|
||||||
|
|
@ -557,7 +567,6 @@ object BuildServerProtocol {
|
||||||
buildTargetIdentifier: BuildTargetIdentifier,
|
buildTargetIdentifier: BuildTargetIdentifier,
|
||||||
buildFor: Seq[BuildTargetIdentifier]
|
buildFor: Seq[BuildTargetIdentifier]
|
||||||
): Def.Initialize[Task[BuildTarget]] = Def.task {
|
): Def.Initialize[Task[BuildTarget]] = Def.task {
|
||||||
val structure = buildStructure.value
|
|
||||||
val scalaProvider = appConfiguration.value.provider().scalaProvider()
|
val scalaProvider = appConfiguration.value.provider().scalaProvider()
|
||||||
appConfiguration.value.provider().mainClasspath()
|
appConfiguration.value.provider().mainClasspath()
|
||||||
val scalaJars = scalaProvider.jars()
|
val scalaJars = scalaProvider.jars()
|
||||||
|
|
@ -579,9 +588,7 @@ object BuildServerProtocol {
|
||||||
|
|
||||||
BuildTarget(
|
BuildTarget(
|
||||||
buildTargetIdentifier,
|
buildTargetIdentifier,
|
||||||
// naming convention still seems like the only way to get IntelliJ to import this correctly
|
toSbtTargetIdName(loadedUnit),
|
||||||
// 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",
|
|
||||||
projectStandard(loadedUnit.unit.localBase).toURI,
|
projectStandard(loadedUnit.unit.localBase).toURI,
|
||||||
Vector(),
|
Vector(),
|
||||||
BuildTargetCapabilities(canCompile = false, canTest = false, canRun = false),
|
BuildTargetCapabilities(canCompile = false, canTest = false, canRun = false),
|
||||||
|
|
@ -828,14 +835,19 @@ object BuildServerProtocol {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val SbtBuildSuffix = "#sbt-build"
|
// naming convention still seems like the only reliable way to get IntelliJ to import this correctly
|
||||||
private def toId(ref: LoadedBuildUnit): BuildTargetIdentifier = {
|
// 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 build = ref.unit.uri
|
||||||
val sanitized = build.toString.indexOf("#") match {
|
val sanitized = build.toString.indexOf("#") match {
|
||||||
case i if i > 0 => build.toString.take(i)
|
case i if i > 0 => build.toString.take(i)
|
||||||
case _ => build.toString
|
case _ => build.toString
|
||||||
}
|
}
|
||||||
BuildTargetIdentifier(new URI(sanitized + SbtBuildSuffix))
|
BuildTargetIdentifier(new URI(sanitized + "#" + name))
|
||||||
}
|
}
|
||||||
private def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
private def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
||||||
ref match {
|
ref match {
|
||||||
|
|
@ -879,7 +891,9 @@ object BuildServerProtocol {
|
||||||
}
|
}
|
||||||
def warnIfBuildsNonEmpty(method: String, log: Logger): Unit = {
|
def warnIfBuildsNonEmpty(method: String, log: Logger): Unit = {
|
||||||
if (builds.nonEmpty)
|
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
|
package testpkg
|
||||||
|
|
||||||
|
import sbt.internal.bsp.SourcesResult
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import sbt.internal.bsp.WorkspaceBuildTargetsResult
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
// starts svr using server-test/buildserver and perform custom server tests
|
// starts svr using server-test/buildserver and perform custom server tests
|
||||||
object BuildServerTest extends AbstractServerTest {
|
object BuildServerTest extends AbstractServerTest {
|
||||||
|
|
||||||
|
import sbt.internal.bsp.codec.JsonProtocol._
|
||||||
|
|
||||||
override val testDirectory: String = "buildserver"
|
override val testDirectory: String = "buildserver"
|
||||||
|
|
||||||
test("build/initialize") { _ =>
|
test("build/initialize") { _ =>
|
||||||
|
|
@ -26,12 +34,12 @@ object BuildServerTest extends AbstractServerTest {
|
||||||
"""{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }"""
|
"""{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }"""
|
||||||
)
|
)
|
||||||
assert(processing("workspace/buildTargets"))
|
assert(processing("workspace/buildTargets"))
|
||||||
assert {
|
val result = svr.waitFor[WorkspaceBuildTargetsResult](10.seconds)
|
||||||
svr.waitForString(10.seconds) { s =>
|
val utilTarget = result.targets.find(_.displayName.contains("util")).get
|
||||||
(s contains """"id":"16"""") &&
|
assert(utilTarget.id.uri.toString.endsWith("#util/Compile"))
|
||||||
(s contains """"displayName":"util"""")
|
val buildServerBuildTarget =
|
||||||
}
|
result.targets.find(_.displayName.contains("buildserver-build")).get
|
||||||
}
|
assert(buildServerBuildTarget.id.uri.toString.endsWith("#buildserver-build"))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("buildTarget/sources") { _ =>
|
test("buildTarget/sources") { _ =>
|
||||||
|
|
@ -42,10 +50,33 @@ object BuildServerTest extends AbstractServerTest {
|
||||||
|} }""".stripMargin
|
|} }""".stripMargin
|
||||||
)
|
)
|
||||||
assert(processing("buildTarget/sources"))
|
assert(processing("buildTarget/sources"))
|
||||||
assert(svr.waitForString(10.seconds) { s =>
|
val s = svr.waitFor[SourcesResult](10.seconds)
|
||||||
(s contains """"id":"24"""") &&
|
val sources = s.items.head.sources.map(_.uri)
|
||||||
(s contains "util/src/main/scala")
|
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") { _ =>
|
test("buildTarget/compile") { _ =>
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,18 @@ import java.net.Socket
|
||||||
import java.nio.file.{ Files, Path }
|
import java.nio.file.{ Files, Path }
|
||||||
import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
|
import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
import verify._
|
import verify._
|
||||||
import sbt.{ ForkOptions, OutputStrategy, RunFromSourceMain }
|
import sbt.{ ForkOptions, OutputStrategy, RunFromSourceMain }
|
||||||
import sbt.io.IO
|
import sbt.io.IO
|
||||||
import sbt.io.syntax._
|
import sbt.io.syntax._
|
||||||
import sbt.protocol.ClientSocket
|
import sbt.protocol.ClientSocket
|
||||||
|
import sjsonnew.JsonReader
|
||||||
|
import sjsonnew.support.scalajson.unsafe.{ Converter, Parser }
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent._
|
import scala.concurrent._
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.{ Success, Try }
|
import scala.util.{ Failure, Success, Try }
|
||||||
|
|
||||||
trait AbstractServerTest extends TestSuite[Unit] {
|
trait AbstractServerTest extends TestSuite[Unit] {
|
||||||
private var temp: File = _
|
private var temp: File = _
|
||||||
|
|
@ -293,6 +294,57 @@ case class TestServer(
|
||||||
}
|
}
|
||||||
impl()
|
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 = {
|
final def neverReceive(duration: FiniteDuration)(f: String => Boolean): Boolean = {
|
||||||
val deadline = duration.fromNow
|
val deadline = duration.fromNow
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue