diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index ab1f89a56..49b4066d7 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -340,7 +340,9 @@ object Keys { val allowZombieClassLoaders = settingKey[Boolean]("Allow a classloader that has previously been closed by `run` or `test` to continue loading classes.") val buildTargetIdentifier = settingKey[BuildTargetIdentifier]("Id for BSP build target.").withRank(DSetting) val bspBuildTargetSources = inputKey[Unit]("").withRank(DTask) - val bspBuildTargetSourceItem = taskKey[SourcesItem]("").withRank(DTask) + val bspBuildTargetSourcesItem = taskKey[SourcesItem]("").withRank(DTask) + val bspBuildTargetScalacOptions = inputKey[Unit]("").withRank(DTask) + val bspBuildTargetScalacOptionsItem = taskKey[ScalacOptionsItem]("").withRank(DTask) 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) diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 167fac11c..836fc3524 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -35,14 +35,27 @@ object BuildServerProtocol { val s = state.value val args: Seq[String] = spaceDelimited("").parsed val filter = toScopeFilter(args) - // run bspBuildTargetSourceItem concurrently + // run the worker task concurrently Def.task { import sbt.internal.bsp.codec.JsonProtocol._ - val items = bspBuildTargetSourceItem.all(filter).value + val items = bspBuildTargetSourcesItem.all(filter).value val result = SourcesResult(items.toVector) s.respondEvent(result) } - }).evaluated + }).evaluated, + bspBuildTargetScalacOptions := (Def.inputTaskDyn { + import DefaultParsers._ + val s = state.value + val args: Seq[String] = spaceDelimited("").parsed + val filter = toScopeFilter(args) + // run the worker task concurrently + Def.task { + import sbt.internal.bsp.codec.JsonProtocol._ + val items = bspBuildTargetScalacOptionsItem.all(filter).value + val result = ScalacOptionsResult(items.toVector) + s.respondEvent(result) + } + }).evaluated, ) // This will be coped to Compile, Test, etc @@ -52,7 +65,7 @@ object BuildServerProtocol { val c = configuration.value toId(ref, c) }, - bspBuildTargetSourceItem := { + bspBuildTargetSourcesItem := { val id = buildTargetIdentifier.value val dirs = unmanagedSourceDirectories.value val managed = managedSources.value @@ -63,7 +76,14 @@ object BuildServerProtocol { SourceItem(x.toURI, SourceItemKind.File, true) }) SourcesItem(id, items) - } + }, + bspBuildTargetScalacOptionsItem := + ScalacOptionsItem( + target = buildTargetIdentifier.value, + options = scalacOptions.value.toVector, + classpath = fullClasspath.value.toVector.map(_.data.toURI), + classDirectory = classDirectory.value.toURI + ), ) def toScopeFilter(args: Seq[String]): ScopeFilter = { diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 3cf496ae5..e5a83005c 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -117,6 +117,19 @@ private[sbt] object LanguageServerProtocol { ) ) () + case r: JsonRpcRequestMessage if r.method == "buildTarget/scalacOptions" => + import sbt.internal.bsp.codec.JsonProtocol._ + val param = Converter.fromJson[ScalacOptionsParams](json(r)).get + appendExec( + Exec( + s"""${Keys.bspBuildTargetScalacOptions.key} ${param.targets + .map(_.uri) + .mkString(" ")}""", + Option(r.id), + Some(CommandSource(name)) + ) + ) + () } }, { case n: JsonRpcNotificationMessage if n.method == "textDocument/didSave" => diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsItem.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsItem.scala new file mode 100644 index 000000000..da9cc5e6f --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsItem.scala @@ -0,0 +1,57 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * @param options Additional arguments to the compiler. + For example, -deprecation. + * @param classpath The dependency classpath for this target, must be + identical to what is passed as arguments to + the -classpath flag in the command line interface + of scalac. + * @param classDirectory The output directory for classfiles produced by this target + */ +final class ScalacOptionsItem private ( + val target: sbt.internal.bsp.BuildTargetIdentifier, + val options: Vector[String], + val classpath: Vector[java.net.URI], + val classDirectory: Option[java.net.URI]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ScalacOptionsItem => (this.target == x.target) && (this.options == x.options) && (this.classpath == x.classpath) && (this.classDirectory == x.classDirectory) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalacOptionsItem".##) + target.##) + options.##) + classpath.##) + classDirectory.##) + } + override def toString: String = { + "ScalacOptionsItem(" + target + ", " + options + ", " + classpath + ", " + classDirectory + ")" + } + private[this] def copy(target: sbt.internal.bsp.BuildTargetIdentifier = target, options: Vector[String] = options, classpath: Vector[java.net.URI] = classpath, classDirectory: Option[java.net.URI] = classDirectory): ScalacOptionsItem = { + new ScalacOptionsItem(target, options, classpath, classDirectory) + } + def withTarget(target: sbt.internal.bsp.BuildTargetIdentifier): ScalacOptionsItem = { + copy(target = target) + } + def withOptions(options: Vector[String]): ScalacOptionsItem = { + copy(options = options) + } + def withClasspath(classpath: Vector[java.net.URI]): ScalacOptionsItem = { + copy(classpath = classpath) + } + def withClassDirectory(classDirectory: Option[java.net.URI]): ScalacOptionsItem = { + copy(classDirectory = classDirectory) + } + def withClassDirectory(classDirectory: java.net.URI): ScalacOptionsItem = { + copy(classDirectory = Option(classDirectory)) + } +} +object ScalacOptionsItem { + + def apply(target: sbt.internal.bsp.BuildTargetIdentifier, options: Vector[String], classpath: Vector[java.net.URI], classDirectory: Option[java.net.URI]): ScalacOptionsItem = new ScalacOptionsItem(target, options, classpath, classDirectory) + def apply(target: sbt.internal.bsp.BuildTargetIdentifier, options: Vector[String], classpath: Vector[java.net.URI], classDirectory: java.net.URI): ScalacOptionsItem = new ScalacOptionsItem(target, options, classpath, Option(classDirectory)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsParams.scala new file mode 100644 index 000000000..42a317b64 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsParams.scala @@ -0,0 +1,37 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Scalac options + * The build target scalac options request is sent from the client to the server + * to query for the list of compiler options necessary to compile in a given list of targets. + */ +final class ScalacOptionsParams private ( + val targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ScalacOptionsParams => (this.targets == x.targets) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + "sbt.internal.bsp.ScalacOptionsParams".##) + targets.##) + } + override def toString: String = { + "ScalacOptionsParams(" + targets + ")" + } + private[this] def copy(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier] = targets): ScalacOptionsParams = { + new ScalacOptionsParams(targets) + } + def withTargets(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): ScalacOptionsParams = { + copy(targets = targets) + } +} +object ScalacOptionsParams { + + def apply(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): ScalacOptionsParams = new ScalacOptionsParams(targets) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsResult.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsResult.scala new file mode 100644 index 000000000..c35e96861 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalacOptionsResult.scala @@ -0,0 +1,32 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +final class ScalacOptionsResult private ( + val items: Vector[sbt.internal.bsp.ScalacOptionsItem]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ScalacOptionsResult => (this.items == x.items) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + "sbt.internal.bsp.ScalacOptionsResult".##) + items.##) + } + override def toString: String = { + "ScalacOptionsResult(" + items + ")" + } + private[this] def copy(items: Vector[sbt.internal.bsp.ScalacOptionsItem] = items): ScalacOptionsResult = { + new ScalacOptionsResult(items) + } + def withItems(items: Vector[sbt.internal.bsp.ScalacOptionsItem]): ScalacOptionsResult = { + copy(items = items) + } +} +object ScalacOptionsResult { + + def apply(items: Vector[sbt.internal.bsp.ScalacOptionsItem]): ScalacOptionsResult = new ScalacOptionsResult(items) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala index ac039c0b8..d8e553035 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala @@ -18,4 +18,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.SourcesResultFormats with sbt.internal.bsp.codec.ScalaBuildTargetFormats with sbt.internal.bsp.codec.SbtBuildTargetFormats + with sbt.internal.bsp.codec.ScalacOptionsParamsFormats + with sbt.internal.bsp.codec.ScalacOptionsItemFormats + with sbt.internal.bsp.codec.ScalacOptionsResultFormats object JsonProtocol extends JsonProtocol \ No newline at end of file diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsItemFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsItemFormats.scala new file mode 100644 index 000000000..29d7d0b68 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsItemFormats.scala @@ -0,0 +1,33 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ScalacOptionsItemFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val ScalacOptionsItemFormat: JsonFormat[sbt.internal.bsp.ScalacOptionsItem] = new JsonFormat[sbt.internal.bsp.ScalacOptionsItem] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalacOptionsItem = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val target = unbuilder.readField[sbt.internal.bsp.BuildTargetIdentifier]("target") + val options = unbuilder.readField[Vector[String]]("options") + val classpath = unbuilder.readField[Vector[java.net.URI]]("classpath") + val classDirectory = unbuilder.readField[Option[java.net.URI]]("classDirectory") + unbuilder.endObject() + sbt.internal.bsp.ScalacOptionsItem(target, options, classpath, classDirectory) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.ScalacOptionsItem, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("target", obj.target) + builder.addField("options", obj.options) + builder.addField("classpath", obj.classpath) + builder.addField("classDirectory", obj.classDirectory) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsParamsFormats.scala new file mode 100644 index 000000000..7d80d1118 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsParamsFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ScalacOptionsParamsFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val ScalacOptionsParamsFormat: JsonFormat[sbt.internal.bsp.ScalacOptionsParams] = new JsonFormat[sbt.internal.bsp.ScalacOptionsParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalacOptionsParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val targets = unbuilder.readField[Vector[sbt.internal.bsp.BuildTargetIdentifier]]("targets") + unbuilder.endObject() + sbt.internal.bsp.ScalacOptionsParams(targets) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.ScalacOptionsParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("targets", obj.targets) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsResultFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsResultFormats.scala new file mode 100644 index 000000000..ac7a2863b --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalacOptionsResultFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ScalacOptionsResultFormats { self: sbt.internal.bsp.codec.ScalacOptionsItemFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val ScalacOptionsResultFormat: JsonFormat[sbt.internal.bsp.ScalacOptionsResult] = new JsonFormat[sbt.internal.bsp.ScalacOptionsResult] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalacOptionsResult = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val items = unbuilder.readField[Vector[sbt.internal.bsp.ScalacOptionsItem]]("items") + unbuilder.endObject() + sbt.internal.bsp.ScalacOptionsResult(items) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.ScalacOptionsResult, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("items", obj.items) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index 944254683..79ba0c802 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -183,3 +183,31 @@ type SbtBuildTarget { ## sbt build targets if this target represents an sbt meta-meta build. children: [sbt.internal.bsp.BuildTargetIdentifier]! } + +## Scalac options +## The build target scalac options request is sent from the client to the server +## to query for the list of compiler options necessary to compile in a given list of targets. +type ScalacOptionsParams { + targets: [sbt.internal.bsp.BuildTargetIdentifier] +} + +type ScalacOptionsResult { + items: [sbt.internal.bsp.ScalacOptionsItem] +} + +type ScalacOptionsItem { + target: sbt.internal.bsp.BuildTargetIdentifier! + + ## Additional arguments to the compiler. + ## For example, -deprecation. + options: [String] + + ## The dependency classpath for this target, must be + ## identical to what is passed as arguments to + ## the -classpath flag in the command line interface + ## of scalac. + classpath: [java.net.URI] + + ## The output directory for classfiles produced by this target + classDirectory: java.net.URI +} diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index be41f7800..c6f1e0b0d 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -41,7 +41,22 @@ object BuildServerTest extends AbstractServerTest { ) assert(svr.waitForString(10.seconds) { s => println(s) - (s contains """"id":"12"""") + (s contains """"id":"12"""") && + (s contains "util/src/main/scala") + }) + } + + test("buildTarget/scalacOptions") { _ => + val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile" + svr.sendJsonRpc( + s"""{ "jsonrpc": "2.0", "id": "13", "method": "buildTarget/scalacOptions", "params": { + | "targets": [{ "uri": "$x" }] + |} }""".stripMargin + ) + assert(svr.waitForString(10.seconds) { s => + println(s) + (s contains """"id":"13"""") && + (s contains "scala-library-2.13.1.jar") }) }