fix internal transitive mgmt in BSP

This commit is contained in:
Adrien Piquerez 2020-05-06 18:27:33 +02:00
parent 068fe2ad0a
commit 98750817c6
2 changed files with 61 additions and 52 deletions

View File

@ -349,6 +349,7 @@ object Keys {
val bspBuildTargetCompileItem = taskKey[Int]("").withRank(DTask)
val bspBuildTargetScalacOptions = inputKey[Unit]("").withRank(DTask)
val bspBuildTargetScalacOptionsItem = taskKey[ScalacOptionsItem]("").withRank(DTask)
val bspInternalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[String])]]("The project configurations that this configuration depends on, possibly transitivly")
val useCoursier = settingKey[Boolean]("Use Coursier for dependency resolution.").withRank(BSetting)
val csrCacheDirectory = settingKey[File]("Coursier cache directory. Uses -Dsbt.coursier.home or Coursier's default.").withRank(CSetting)

View File

@ -20,7 +20,6 @@ import sbt.internal.bsp._
import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.JsonRpcRequestMessage
import sbt.librarymanagement.Configuration
import sbt.librarymanagement.Configurations.{ Compile, Test }
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
import sjsonnew.support.scalajson.unsafe.Converter
@ -28,19 +27,19 @@ object BuildServerProtocol {
import sbt.internal.bsp.codec.JsonProtocol._
private val bspVersion = "2.0.0-M5"
private val languageIds = Vector("scala")
private val bspTargetConfigs = Set("compile", "test")
private val capabilities = BuildServerCapabilities(CompileProvider(languageIds))
lazy val globalSettings: Seq[Def.Setting[_]] = Seq(
bspWorkspace := Def.taskDyn {
val structure = Keys.buildStructure.value
val scopes: Seq[Scope] = structure.allProjectRefs.flatMap { ref =>
Seq(Scope.Global.in(ref, Compile), Scope.Global.in(ref, Test))
bspTargetConfigs.toSeq.map(name => Scope.Global.in(ref, ConfigKey(name)))
}
Def.task {
val targetIds = scopes.map(_ / Keys.buildTargetIdentifier).join.value
targetIds.zip(scopes).toMap
}
}.value,
bspWorkspaceBuildTargets := Def.taskDyn {
val workspace = Keys.bspWorkspace.value
@ -100,7 +99,7 @@ object BuildServerProtocol {
val c = configuration.value
toId(ref, c)
},
bspBuildTarget := bspBuildTargetTask.value,
bspBuildTarget := buildTargetTask.value,
bspBuildTargetSourcesItem := {
val id = buildTargetIdentifier.value
val dirs = unmanagedSourceDirectories.value
@ -114,14 +113,8 @@ object BuildServerProtocol {
SourcesItem(id, items)
},
bspBuildTargetCompileItem := bspCompileTask.value,
bspBuildTargetScalacOptionsItem := {
ScalacOptionsItem(
target = buildTargetIdentifier.value,
options = scalacOptions.value.toVector,
classpath = dependencyClasspath.value.map(_.data.toURI).toVector,
classDirectory = classDirectory.value.toURI
)
}
bspBuildTargetScalacOptionsItem := scalacOptionsTask.value,
bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value
)
def handler(sbtVersion: String): ServerHandler = ServerHandler { callback =>
@ -133,34 +126,32 @@ object BuildServerProtocol {
callback.jsonRpcRespond(response, Some(r.id)); ()
case r: JsonRpcRequestMessage if r.method == "workspace/buildTargets" =>
val _ = callback.appendExec(Keys.bspWorkspaceBuildTargets.key.toString, None)
val _ = callback.appendExec(Keys.bspWorkspaceBuildTargets.key.toString, Some(r.id))
case r: JsonRpcRequestMessage if r.method == "build/shutdown" =>
()
case r: JsonRpcRequestMessage if r.method == "build/exit" =>
val _ = callback.appendExec("shutdown", None)
val _ = callback.appendExec("shutdown", Some(r.id))
case r: JsonRpcRequestMessage if r.method == "buildTarget/sources" =>
import sbt.internal.bsp.codec.JsonProtocol._
val param = Converter.fromJson[SourcesParams](json(r)).get
val targets = param.targets.map(_.uri).mkString(" ")
val command = Keys.bspBuildTargetSources.key
val _ = callback.appendExec(s"$command $targets", None)
val _ = callback.appendExec(s"$command $targets", Some(r.id))
case r if r.method == "buildTarget/compile" =>
val param = Converter.fromJson[CompileParams](json(r)).get
callback.log.info(param.toString)
val targets = param.targets.map(_.uri).mkString(" ")
val command = Keys.bspBuildTargetCompile.key
val _ = callback.appendExec(s"$command $targets", None)
val _ = callback.appendExec(s"$command $targets", Some(r.id))
case r: JsonRpcRequestMessage if r.method == "buildTarget/scalacOptions" =>
import sbt.internal.bsp.codec.JsonProtocol._
val param = Converter.fromJson[ScalacOptionsParams](json(r)).get
val targets = param.targets.map(_.uri).mkString(" ")
val command = Keys.bspBuildTargetScalacOptions.key
val _ = callback.appendExec(s"$command $targets", None)
val _ = callback.appendExec(s"$command $targets", Some(r.id))
},
PartialFunction.empty
)
@ -174,11 +165,11 @@ object BuildServerProtocol {
)
)
private def bspBuildTargetTask: Def.Initialize[Task[BuildTarget]] = Def.taskDyn {
import sbt.internal.bsp.codec.JsonProtocol._
private def buildTargetTask: Def.Initialize[Task[BuildTarget]] = Def.taskDyn {
val buildTargetIdentifier = Keys.buildTargetIdentifier.value
val thisProject = Keys.thisProject.value
val thisProjectRef = Keys.thisProjectRef.value
val thisConfig = Keys.configuration.value
val scalaJars = Keys.scalaInstance.value.allJars.map(_.toURI.toString)
val compileData = ScalaBuildTarget(
scalaOrganization = scalaOrganization.value,
@ -193,34 +184,50 @@ object BuildServerProtocol {
case configName => s"${thisProject.id}-$configName"
}
val baseDirectory = Keys.baseDirectory.value.toURI
val allDependencies = configuration.name match {
case "test" =>
thisProject.dependencies :+
ResolvedClasspathDependency(thisProjectRef, Some("test->compile"))
case _ => thisProject.dependencies
}
val projectDependencies = for {
(dep, configs) <- Keys.bspInternalDependencyConfigurations.value
config <- configs if (dep != thisProjectRef || config != thisConfig.name) && bspTargetConfigs.contains(config)
} yield Keys.buildTargetIdentifier.in(dep, ConfigKey(config))
val capabilities = BuildTargetCapabilities(canCompile = true, canTest = false, canRun = false)
val tags = BuildTargetTag.fromConfig(configuration.name)
Def.task {
val allDepIds = allDependencies
.flatMap(dependencyTargetKeys(_, configuration))
.join
.value
BuildTarget(
buildTargetIdentifier,
Some(displayName),
Some(baseDirectory),
tags,
capabilities,
languageIds = Vector("scala"),
dependencies = allDepIds.toVector,
languageIds,
projectDependencies.join.value.toVector,
dataKind = Some("scala"),
data = Some(Converter.toJsonUnsafe(compileData)),
)
}
}
private def scalacOptionsTask: Def.Initialize[Task[ScalacOptionsItem]] = Def.taskDyn {
val target = Keys.buildTargetIdentifier.value
val scalacOptions = Keys.scalacOptions.value
val classDirectory = Keys.classDirectory.value
val externalDependencyClasspath = Keys.externalDependencyClasspath.value
val internalDependencyClasspath = for {
(ref, configs) <- bspInternalDependencyConfigurations.value
config <- configs
} yield Keys.classDirectory.in(ref, ConfigKey(config))
Def.task {
val classpath = internalDependencyClasspath.join.value.distinct ++
externalDependencyClasspath.map(_.data)
ScalacOptionsItem(
target,
scalacOptions.toVector,
classpath.map(_.toURI).toVector,
classDirectory.toURI
)
}
}
private def bspCompileTask: Def.Initialize[Task[Int]] = Def.task {
import sbt.Project._
Keys.compile.result.value match {
@ -231,6 +238,25 @@ object BuildServerProtocol {
}
}
private def internalDependencyConfigurationsSetting = Def.settingDyn {
val directDependencies = Keys.internalDependencyConfigurations.value
val ref = Keys.thisProjectRef.value
val thisConfig = Keys.configuration.value
val transitiveDependencies = for {
(dep, configs) <- directDependencies
config <- configs if dep != ref || config != thisConfig.name
} yield Keys.bspInternalDependencyConfigurations.in(dep, ConfigKey(config))
Def.setting {
val allDependencies = directDependencies ++ transitiveDependencies.join.value.flatten
allDependencies
.groupBy(_._1)
.mapValues { deps =>
deps.flatMap { case (_, configs) => configs }.toSet
}
.toSeq
}
}
def scopeFilter(
targets: Seq[BuildTargetIdentifier],
workspace: Map[BuildTargetIdentifier, Scope]
@ -244,22 +270,4 @@ object BuildServerProtocol {
BuildTargetIdentifier(new URI(s"$build#$project/${config.id}"))
case _ => sys.error(s"unexpected $ref")
}
private def dependencyTargetKeys(
ref: ClasspathDep[ProjectRef],
fromConfig: Configuration
): Seq[SettingKey[BuildTargetIdentifier]] = {
val from = fromConfig.name
val depConfig = ref.configuration.getOrElse("compile")
for {
configExpr <- depConfig.split(",").toSeq
depId <- configExpr.split("->").toList match {
case "compile" :: Nil | `from` :: "compile" :: Nil =>
Some(ref.project / Compile / Keys.buildTargetIdentifier)
case "test" :: Nil | `from` :: "test" :: Nil =>
Some(ref.project / Test / Keys.buildTargetIdentifier)
case _ => None
}
} yield depId
}
}