mirror of https://github.com/sbt/sbt.git
Support the SBT extension in BSP import
This enables code assist in the .sbt and project/*.scala files in IntelliJ and any other IDEs that implement this extension.
This commit is contained in:
parent
e4231ac039
commit
45e8e2f90d
|
|
@ -10,7 +10,6 @@ package sbt
|
|||
import java.nio.file.{ Path => NioPath }
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
import lmcoursier.definitions.{ CacheLogger, ModuleMatchers, Reconciliation }
|
||||
import lmcoursier.{ CoursierConfiguration, FallbackDependency }
|
||||
import org.apache.ivy.core.module.descriptor.ModuleDescriptor
|
||||
|
|
@ -26,6 +25,7 @@ import sbt.internal.inc.ScalaInstance
|
|||
import sbt.internal.io.WatchState
|
||||
import sbt.internal.librarymanagement.{ CompatibilityWarningOptions, IvySbt }
|
||||
import sbt.internal.remotecache.RemoteCacheArtifact
|
||||
import sbt.internal.server.BuildServerProtocol.BspWorkspace
|
||||
import sbt.internal.server.{ BuildServerReporter, ServerHandler }
|
||||
import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition }
|
||||
import sbt.io._
|
||||
|
|
@ -399,10 +399,11 @@ object Keys {
|
|||
val bspConfig = taskKey[Unit]("Create or update the BSP connection files").withRank(DSetting)
|
||||
val bspEnabled = SettingKey[Boolean](BasicKeys.bspEnabled)
|
||||
val bspTargetIdentifier = settingKey[BuildTargetIdentifier]("Build target identifier of a project and configuration.").withRank(DSetting)
|
||||
val bspWorkspace = settingKey[Map[BuildTargetIdentifier, Scope]]("Mapping of BSP build targets to sbt scopes").withRank(DSetting)
|
||||
val bspWorkspace = settingKey[BspWorkspace]("Mapping of BSP build targets to sbt scopes").withRank(DSetting)
|
||||
val bspInternalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[ConfigKey])]]("The project configurations that this configuration depends on, possibly transitivly").withRank(DSetting)
|
||||
val bspWorkspaceBuildTargets = taskKey[Seq[BuildTarget]]("List all the BSP build targets").withRank(DTask)
|
||||
val bspBuildTarget = taskKey[BuildTarget]("Description of the BSP build targets").withRank(DTask)
|
||||
val bspSbtBuildTarget = taskKey[List[BuildTarget]]("Description of the BSP SBT build targets").withRank(DTask)
|
||||
val bspBuildTargetSources = inputKey[Unit]("").withRank(DTask)
|
||||
val bspBuildTargetSourcesItem = taskKey[SourcesItem]("").withRank(DTask)
|
||||
val bspBuildTargetResources = inputKey[Unit]("").withRank(DTask)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ package internal
|
|||
package server
|
||||
|
||||
import java.net.URI
|
||||
import sbt.BasicCommandStrings.Shutdown
|
||||
import sbt.BuildPaths.{ configurationSources, projectStandard }
|
||||
import sbt.BuildSyntax._
|
||||
import sbt.Def._
|
||||
import sbt.Keys._
|
||||
|
|
@ -22,13 +24,17 @@ import sbt.internal.langserver.ErrorCodes
|
|||
import sbt.internal.protocol.JsonRpcRequestMessage
|
||||
import sbt.internal.util.Attributed
|
||||
import sbt.internal.util.complete.{ Parser, Parsers }
|
||||
import sbt.librarymanagement.Configuration
|
||||
import sbt.librarymanagement.CrossVersion.binaryScalaVersion
|
||||
import sbt.librarymanagement.{ Configuration, ScalaArtifacts }
|
||||
import sbt.std.TaskExtra
|
||||
import sbt.util.Logger
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.{ JNull, JValue }
|
||||
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser => JsonParser }
|
||||
import xsbti.CompileFailed
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.mutable
|
||||
|
||||
// import scala.annotation.nowarn
|
||||
import scala.util.control.NonFatal
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
|
@ -94,26 +100,39 @@ object BuildServerProtocol {
|
|||
},
|
||||
bspEnabled := true,
|
||||
bspWorkspace := bspWorkspaceSetting.value,
|
||||
bspSbtBuildTarget := sbtBuildTargetTask.value,
|
||||
bspWorkspaceBuildTargets := Def.taskDyn {
|
||||
val workspace = Keys.bspWorkspace.value
|
||||
val state = Keys.state.value
|
||||
val allTargets = ScopeFilter.in(workspace.values.toSeq)
|
||||
val allTargets = ScopeFilter.in(workspace.scopes.values.toSeq)
|
||||
Def.task {
|
||||
val sbtTargets = bspSbtBuildTarget.value
|
||||
val buildTargets = Keys.bspBuildTarget.all(allTargets).value.toVector
|
||||
state.respondEvent(WorkspaceBuildTargetsResult(buildTargets))
|
||||
buildTargets
|
||||
val allBuildTargets = buildTargets ++ sbtTargets
|
||||
state.respondEvent(WorkspaceBuildTargetsResult(allBuildTargets))
|
||||
allBuildTargets
|
||||
}
|
||||
}.value,
|
||||
// https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request
|
||||
bspBuildTargetSources := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
// run the worker task concurrently
|
||||
Def.task {
|
||||
val items = bspBuildTargetSourcesItem.all(filter).value
|
||||
val result = SourcesResult(items.toVector)
|
||||
val buildItems = workspace.builds.toVector.map {
|
||||
case (id, loadedBuildUnit) =>
|
||||
val base = loadedBuildUnit.localBase
|
||||
val sbtFiles = configurationSources(base)
|
||||
val defDir = projectStandard(base)
|
||||
val sbtFilesItems =
|
||||
sbtFiles.map(f => SourceItem(f.toURI, SourceItemKind.File, generated = false))
|
||||
val defDirItem = SourceItem(defDir.toURI, SourceItemKind.Directory, generated = false)
|
||||
SourcesItem(id, (defDirItem +: sbtFilesItems).toVector)
|
||||
}
|
||||
val result = SourcesResult((items ++ buildItems).toVector)
|
||||
s.respondEvent(result)
|
||||
}
|
||||
}.evaluated,
|
||||
|
|
@ -133,9 +152,9 @@ object BuildServerProtocol {
|
|||
bspBuildTargetResources / aggregate := false,
|
||||
bspBuildTargetDependencySources := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
// run the worker task concurrently
|
||||
Def.task {
|
||||
import sbt.internal.bsp.codec.JsonProtocol._
|
||||
|
|
@ -147,9 +166,9 @@ object BuildServerProtocol {
|
|||
bspBuildTargetDependencySources / aggregate := false,
|
||||
bspBuildTargetCompile := Def.inputTaskDyn {
|
||||
val s: State = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max
|
||||
s.respondEvent(BspCompileResult(None, statusCode))
|
||||
|
|
@ -160,21 +179,36 @@ object BuildServerProtocol {
|
|||
bspBuildTargetTest / aggregate := false,
|
||||
bspBuildTargetScalacOptions := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspWorkspace.value.filter(targets)
|
||||
val builds = workspace.builds
|
||||
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val items = bspBuildTargetScalacOptionsItem.all(filter).value
|
||||
val result = ScalacOptionsResult(items.toVector)
|
||||
val appProvider = appConfiguration.value.provider()
|
||||
val sbtJars = appProvider.mainClasspath()
|
||||
val buildItems = builds.map { build =>
|
||||
val pluginClassPath = build._2.unit.plugins.classpath
|
||||
val classpath = (pluginClassPath ++ sbtJars).map(_.toURI).toVector
|
||||
ScalacOptionsItem(
|
||||
build._1,
|
||||
Vector(),
|
||||
classpath,
|
||||
new File(build._2.localBase, "project/target").toURI
|
||||
)
|
||||
}
|
||||
val result = ScalacOptionsResult((items ++ buildItems).toVector)
|
||||
s.respondEvent(result)
|
||||
}
|
||||
}.evaluated,
|
||||
bspBuildTargetScalacOptions / aggregate := false,
|
||||
bspScalaTestClasses := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val items = bspScalaTestClassesItem.all(filter).value
|
||||
val result = ScalaTestClassesResult(items.toVector, None)
|
||||
|
|
@ -183,9 +217,9 @@ object BuildServerProtocol {
|
|||
}.evaluated,
|
||||
bspScalaMainClasses := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val items = bspScalaMainClassesItem.all(filter).value
|
||||
val result = ScalaMainClassesResult(items.toVector, None)
|
||||
|
|
@ -250,7 +284,7 @@ object BuildServerProtocol {
|
|||
|
||||
def handler(
|
||||
loadedBuild: LoadedBuild,
|
||||
workspace: Map[BuildTargetIdentifier, Scope],
|
||||
workspace: BspWorkspace,
|
||||
sbtVersion: String,
|
||||
semanticdbEnabled: Boolean,
|
||||
semanticdbVersion: String
|
||||
|
|
@ -312,7 +346,7 @@ object BuildServerProtocol {
|
|||
case r if r.method == "buildTarget/run" =>
|
||||
val paramJson = json(r)
|
||||
val param = Converter.fromJson[RunParams](json(r)).get
|
||||
val scope = workspace.getOrElse(
|
||||
val scope = workspace.scopes.getOrElse(
|
||||
param.target,
|
||||
throw LangServerError(
|
||||
ErrorCodes.InvalidParams,
|
||||
|
|
@ -403,7 +437,7 @@ object BuildServerProtocol {
|
|||
)
|
||||
|
||||
@nowarn
|
||||
private def bspWorkspaceSetting: Def.Initialize[Map[BuildTargetIdentifier, Scope]] =
|
||||
private def bspWorkspaceSetting: Def.Initialize[BspWorkspace] =
|
||||
Def.settingDyn {
|
||||
val loadedBuild = Keys.loadedBuild.value
|
||||
|
||||
|
|
@ -423,11 +457,24 @@ object BuildServerProtocol {
|
|||
.map(_ / Keys.bspEnabled)
|
||||
.join
|
||||
.value
|
||||
val result = for {
|
||||
val buildsMap =
|
||||
mutable.HashMap[BuildTargetIdentifier, mutable.ListBuffer[BuildTargetIdentifier]]()
|
||||
|
||||
val scopeMap = for {
|
||||
(targetId, scope, bspEnabled) <- (targetIds, scopes, bspEnabled).zipped
|
||||
if bspEnabled
|
||||
} yield targetId -> scope
|
||||
result.toMap
|
||||
} yield {
|
||||
scope.project.toOption match {
|
||||
case Some(ProjectRef(buildUri, _)) =>
|
||||
val loadedBuildUnit = loadedBuild.units(buildUri)
|
||||
buildsMap.getOrElseUpdate(toId(loadedBuildUnit), new mutable.ListBuffer) += targetId
|
||||
}
|
||||
targetId -> scope
|
||||
}
|
||||
val buildMap = for (loadedBuildUnit <- loadedBuild.units.values) yield {
|
||||
toId(loadedBuildUnit) -> loadedBuildUnit
|
||||
}
|
||||
BspWorkspace(scopeMap.toMap, buildMap.toMap, buildsMap.mapValues(_.result()).toMap)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,6 +516,50 @@ object BuildServerProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
private def sbtBuildTargetTask: Def.Initialize[Task[List[BuildTarget]]] = Def.taskDyn {
|
||||
val structure = buildStructure.value
|
||||
val loadedUnits = structure.units.values.toSeq.distinct
|
||||
|
||||
val scalaProvider = appConfiguration.value.provider().scalaProvider()
|
||||
appConfiguration.value.provider().mainClasspath()
|
||||
val scalaJars = scalaProvider.jars()
|
||||
val compileData = ScalaBuildTarget(
|
||||
scalaOrganization = ScalaArtifacts.Organization,
|
||||
scalaVersion = scalaProvider.version(),
|
||||
scalaBinaryVersion = binaryScalaVersion(scalaProvider.version()),
|
||||
platform = ScalaPlatform.JVM,
|
||||
jars = scalaJars.toVector.map(_.toURI.toString)
|
||||
)
|
||||
val workspace = bspWorkspaceSetting.value
|
||||
Def.task {
|
||||
val sbtVersionValue = sbtVersion.value
|
||||
loadedUnits.toList.map { loadedUnit =>
|
||||
val buildTargetIdentifier = toId(loadedUnit)
|
||||
val sbtData = SbtBuildTarget(
|
||||
sbtVersionValue,
|
||||
loadedUnit.imports.toVector,
|
||||
compileData,
|
||||
None,
|
||||
workspace.buildToScope.getOrElse(buildTargetIdentifier, Nil).toVector
|
||||
)
|
||||
|
||||
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",
|
||||
projectStandard(loadedUnit.unit.localBase).toURI,
|
||||
Vector(),
|
||||
BuildTargetCapabilities(canCompile = false, canTest = false, canRun = false),
|
||||
BuildServerConnection.languages,
|
||||
Vector(),
|
||||
"sbt",
|
||||
data = Converter.toJsonUnsafe(sbtData),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def scalacOptionsTask: Def.Initialize[Task[ScalacOptionsItem]] = Def.taskDyn {
|
||||
val target = Keys.bspTargetIdentifier.value
|
||||
val scalacOptions = Keys.scalacOptions.value
|
||||
|
|
@ -582,7 +673,7 @@ object BuildServerProtocol {
|
|||
case Success(value) => value.testClasses
|
||||
}
|
||||
val testTasks: Seq[Def.Initialize[Task[Unit]]] = items.map { item =>
|
||||
val scope = workspace(item.target)
|
||||
val scope = workspace.scopes(item.target)
|
||||
item.classes.toList match {
|
||||
case Nil => Def.task(())
|
||||
case classes =>
|
||||
|
|
@ -599,7 +690,7 @@ object BuildServerProtocol {
|
|||
|
||||
case None =>
|
||||
// run allTests in testParams.targets
|
||||
val filter = ScopeFilter.in(testParams.targets.map(workspace))
|
||||
val filter = ScopeFilter.in(testParams.targets.map(workspace.scopes))
|
||||
test.all(filter).result
|
||||
}
|
||||
|
||||
|
|
@ -644,7 +735,7 @@ object BuildServerProtocol {
|
|||
|
||||
@nowarn
|
||||
private def internalDependencyConfigurationsSetting = Def.settingDyn {
|
||||
val allScopes = bspWorkspace.value.map { case (_, scope) => scope }.toSet
|
||||
val allScopes = bspWorkspace.value.scopes.map { case (_, scope) => scope }.toSet
|
||||
val directDependencies = Keys.internalDependencyConfigurations.value
|
||||
.map {
|
||||
case (project, rawConfigs) =>
|
||||
|
|
@ -705,6 +796,15 @@ object BuildServerProtocol {
|
|||
)
|
||||
}
|
||||
|
||||
private val SbtBuildSuffix = "#sbt-build"
|
||||
private def toId(ref: LoadedBuildUnit): BuildTargetIdentifier = {
|
||||
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))
|
||||
}
|
||||
private def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
||||
ref match {
|
||||
case ProjectRef(build, project) =>
|
||||
|
|
@ -733,4 +833,16 @@ object BuildServerProtocol {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
final case class BspWorkspace(
|
||||
scopes: Map[BuildTargetIdentifier, Scope],
|
||||
builds: Map[BuildTargetIdentifier, LoadedBuildUnit],
|
||||
buildToScope: Map[BuildTargetIdentifier, Seq[BuildTargetIdentifier]]
|
||||
) {
|
||||
def filter(targets: Seq[BuildTargetIdentifier]): BspWorkspace = {
|
||||
val set = targets.toSet
|
||||
def filterMap[T](map: Map[BuildTargetIdentifier, T]) = map.filter(x => set.contains(x._1))
|
||||
BspWorkspace(filterMap(scopes), filterMap(builds), buildToScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue