From 159171bba5976798e171976e7bb4b6421ea14543 Mon Sep 17 00:00:00 2001 From: adpi2 Date: Mon, 15 Jun 2020 16:57:49 +0200 Subject: [PATCH] Add suggestion about semanticdb when Metals connect to sbt Try parse the required semanticdbVersion in the initialization request metadata Issue a warning if the semanticdb plugin is not enabled Issue a warning if the semanticdb version is lower than the required --- main/src/main/scala/sbt/Defaults.scala | 3 +- .../internal/server/BuildServerProtocol.scala | 64 ++++++++++++++++++- .../internal/bsp/InitializeBuildParams.scala | 23 +++++-- .../sbt/internal/bsp/MetalsMetadata.scala | 41 ++++++++++++ .../codec/InitializeBuildParamsFormats.scala | 6 +- .../sbt/internal/bsp/codec/JsonProtocol.scala | 1 + .../bsp/codec/MetalsMetadataFormats.scala | 29 +++++++++ protocol/src/main/contraband/bsp.contra | 13 +++- 8 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/MetalsMetadata.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/MetalsMetadataFormats.scala diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 30d66cb40..0d1b4d64c 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -208,7 +208,8 @@ object Defaults extends BuildCommon { fullServerHandlers := { Seq( LanguageServerProtocol.handler(fileConverter.value), - BuildServerProtocol.handler(sbtVersion.value) + BuildServerProtocol + .handler(sbtVersion.value, semanticdbEnabled.value, semanticdbVersion.value) ) ++ serverHandlers.value :+ ServerHandler.fallback }, uncachedStamper := Stamps.uncachedStamps(fileConverter.value), diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 35263c562..eadfec89f 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -20,9 +20,12 @@ import sbt.internal.bsp._ import sbt.internal.langserver.ErrorCodes import sbt.internal.protocol.JsonRpcRequestMessage import sbt.librarymanagement.Configuration +import sbt.util.Logger import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.support.scalajson.unsafe.Converter +import scala.util.control.NonFatal + object BuildServerProtocol { import sbt.internal.bsp.codec.JsonProtocol._ private val bspTargetConfigs = Set("compile", "test") @@ -139,11 +142,17 @@ object BuildServerProtocol { bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value ) - def handler(sbtVersion: String): ServerHandler = ServerHandler { callback => + def handler( + sbtVersion: String, + semanticdbEnabled: Boolean, + semanticdbVersion: String + ): ServerHandler = ServerHandler { callback => ServerIntent( { case r: JsonRpcRequestMessage if r.method == "build/initialize" => - val _ = Converter.fromJson[InitializeBuildParams](json(r)).get + val params = Converter.fromJson[InitializeBuildParams](json(r)).get + checkMetalsCompatibility(semanticdbEnabled, semanticdbVersion, params, callback.log) + val response = InitializeBuildResult( "sbt", sbtVersion, @@ -191,6 +200,39 @@ object BuildServerProtocol { ) } + private def checkMetalsCompatibility( + semanticdbEnabled: Boolean, + semanticdbVersion: String, + params: InitializeBuildParams, + log: Logger + ): Unit = { + for { + data <- params.data + // try parse metadata as MetalsMetadata + metalsMetadata <- Converter.fromJson[MetalsMetadata](data).toOption + } { + if (!semanticdbEnabled) { + log.warn(s"${params.displayName} requires the semanticdb compiler plugin") + log.warn( + s"Please consider setting 'Global / semanticdbEnabled := true' in your global sbt settings ($$HOME/.sbt/1.0)" + ) + } + + for { + requiredVersion <- SemanticVersion.tryParse(metalsMetadata.semanticdbVersion) + currentVersion <- SemanticVersion.tryParse(semanticdbVersion) + if requiredVersion > currentVersion + } { + log.warn( + s"${params.displayName} requires semanticdb version ${metalsMetadata.semanticdbVersion}, current version is $semanticdbVersion" + ) + log.warn( + s"""Please consider setting 'Global / semanticdbVersion := "${metalsMetadata.semanticdbVersion}"' in your global sbt settings ($$HOME/.sbt/1.0)""" + ) + } + } + } + private def json(r: JsonRpcRequestMessage): JValue = r.params.getOrElse( throw LangServerError( @@ -307,4 +349,22 @@ object BuildServerProtocol { BuildTargetIdentifier(new URI(s"$build#$project/${config.id}")) case _ => sys.error(s"unexpected $ref") } + + private case class SemanticVersion(major: Int, minor: Int) extends Ordered[SemanticVersion] { + override def compare(that: SemanticVersion): Int = { + if (that.major != major) major.compare(that.major) + else minor.compare(minor) + } + } + + private object SemanticVersion { + def tryParse(versionStr: String): Option[SemanticVersion] = { + try { + val parts = versionStr.split('.') + Some(SemanticVersion(parts(0).toInt, parts(1).toInt)) + } catch { + case NonFatal(_) => None + } + } + } } diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/InitializeBuildParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/InitializeBuildParams.scala index e81805c2a..915d8a35d 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/InitializeBuildParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/InitializeBuildParams.scala @@ -11,28 +11,30 @@ package sbt.internal.bsp * @param bspVersion The BSP version that the client speaks * @param rootUri The rootUri of the workspace * @param capabilities The capabilities of the client + * @param data Additional metadata about the client */ final class InitializeBuildParams private ( val displayName: String, val version: String, val bspVersion: String, val rootUri: java.net.URI, - val capabilities: sbt.internal.bsp.BuildClientCapabilities) extends Serializable { + val capabilities: sbt.internal.bsp.BuildClientCapabilities, + val data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]) extends Serializable { override def equals(o: Any): Boolean = o match { - case x: InitializeBuildParams => (this.displayName == x.displayName) && (this.version == x.version) && (this.bspVersion == x.bspVersion) && (this.rootUri == x.rootUri) && (this.capabilities == x.capabilities) + case x: InitializeBuildParams => (this.displayName == x.displayName) && (this.version == x.version) && (this.bspVersion == x.bspVersion) && (this.rootUri == x.rootUri) && (this.capabilities == x.capabilities) && (this.data == x.data) case _ => false } override def hashCode: Int = { - 37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.InitializeBuildParams".##) + displayName.##) + version.##) + bspVersion.##) + rootUri.##) + capabilities.##) + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.InitializeBuildParams".##) + displayName.##) + version.##) + bspVersion.##) + rootUri.##) + capabilities.##) + data.##) } override def toString: String = { - "InitializeBuildParams(" + displayName + ", " + version + ", " + bspVersion + ", " + rootUri + ", " + capabilities + ")" + "InitializeBuildParams(" + displayName + ", " + version + ", " + bspVersion + ", " + rootUri + ", " + capabilities + ", " + data + ")" } - private[this] def copy(displayName: String = displayName, version: String = version, bspVersion: String = bspVersion, rootUri: java.net.URI = rootUri, capabilities: sbt.internal.bsp.BuildClientCapabilities = capabilities): InitializeBuildParams = { - new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities) + private[this] def copy(displayName: String = displayName, version: String = version, bspVersion: String = bspVersion, rootUri: java.net.URI = rootUri, capabilities: sbt.internal.bsp.BuildClientCapabilities = capabilities, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = data): InitializeBuildParams = { + new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, data) } def withDisplayName(displayName: String): InitializeBuildParams = { copy(displayName = displayName) @@ -49,8 +51,15 @@ final class InitializeBuildParams private ( def withCapabilities(capabilities: sbt.internal.bsp.BuildClientCapabilities): InitializeBuildParams = { copy(capabilities = capabilities) } + def withData(data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): InitializeBuildParams = { + copy(data = data) + } + def withData(data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): InitializeBuildParams = { + copy(data = Option(data)) + } } object InitializeBuildParams { - def apply(displayName: String, version: String, bspVersion: String, rootUri: java.net.URI, capabilities: sbt.internal.bsp.BuildClientCapabilities): InitializeBuildParams = new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities) + def apply(displayName: String, version: String, bspVersion: String, rootUri: java.net.URI, capabilities: sbt.internal.bsp.BuildClientCapabilities, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): InitializeBuildParams = new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, data) + def apply(displayName: String, version: String, bspVersion: String, rootUri: java.net.URI, capabilities: sbt.internal.bsp.BuildClientCapabilities, data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): InitializeBuildParams = new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, Option(data)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/MetalsMetadata.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/MetalsMetadata.scala new file mode 100644 index 000000000..dd4da0d4e --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/MetalsMetadata.scala @@ -0,0 +1,41 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Metals metadata in the initialization request + * @param semanticdbVersion The semanticdb plugin version that should be enabled for Metals code navigation + * @param supportedScalaVersions The list of scala versions that are supported by Metals + */ +final class MetalsMetadata private ( + val semanticdbVersion: String, + val supportedScalaVersions: Vector[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: MetalsMetadata => (this.semanticdbVersion == x.semanticdbVersion) && (this.supportedScalaVersions == x.supportedScalaVersions) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.MetalsMetadata".##) + semanticdbVersion.##) + supportedScalaVersions.##) + } + override def toString: String = { + "MetalsMetadata(" + semanticdbVersion + ", " + supportedScalaVersions + ")" + } + private[this] def copy(semanticdbVersion: String = semanticdbVersion, supportedScalaVersions: Vector[String] = supportedScalaVersions): MetalsMetadata = { + new MetalsMetadata(semanticdbVersion, supportedScalaVersions) + } + def withSemanticdbVersion(semanticdbVersion: String): MetalsMetadata = { + copy(semanticdbVersion = semanticdbVersion) + } + def withSupportedScalaVersions(supportedScalaVersions: Vector[String]): MetalsMetadata = { + copy(supportedScalaVersions = supportedScalaVersions) + } +} +object MetalsMetadata { + + def apply(semanticdbVersion: String, supportedScalaVersions: Vector[String]): MetalsMetadata = new MetalsMetadata(semanticdbVersion, supportedScalaVersions) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/InitializeBuildParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/InitializeBuildParamsFormats.scala index 60e7bb731..943feb664 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/InitializeBuildParamsFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/InitializeBuildParamsFormats.scala @@ -5,7 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.internal.bsp.codec import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -trait InitializeBuildParamsFormats { self: sbt.internal.bsp.codec.BuildClientCapabilitiesFormats with sjsonnew.BasicJsonProtocol => +trait InitializeBuildParamsFormats { self: sbt.internal.bsp.codec.BuildClientCapabilitiesFormats with sbt.internal.util.codec.JValueFormats with sjsonnew.BasicJsonProtocol => implicit lazy val InitializeBuildParamsFormat: JsonFormat[sbt.internal.bsp.InitializeBuildParams] = new JsonFormat[sbt.internal.bsp.InitializeBuildParams] { override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.InitializeBuildParams = { __jsOpt match { @@ -16,8 +16,9 @@ implicit lazy val InitializeBuildParamsFormat: JsonFormat[sbt.internal.bsp.Initi val bspVersion = unbuilder.readField[String]("bspVersion") val rootUri = unbuilder.readField[java.net.URI]("rootUri") val capabilities = unbuilder.readField[sbt.internal.bsp.BuildClientCapabilities]("capabilities") + val data = unbuilder.readField[Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]]("data") unbuilder.endObject() - sbt.internal.bsp.InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities) + sbt.internal.bsp.InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, data) case None => deserializationError("Expected JsObject but found None") } @@ -29,6 +30,7 @@ implicit lazy val InitializeBuildParamsFormat: JsonFormat[sbt.internal.bsp.Initi builder.addField("bspVersion", obj.bspVersion) builder.addField("rootUri", obj.rootUri) builder.addField("capabilities", obj.capabilities) + builder.addField("data", obj.data) builder.endObject() } } 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 eae54770c..855a93bba 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 @@ -40,4 +40,5 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.ScalacOptionsItemFormats with sbt.internal.bsp.codec.ScalacOptionsResultFormats with sbt.internal.bsp.codec.BspConnectionDetailsFormats + with sbt.internal.bsp.codec.MetalsMetadataFormats object JsonProtocol extends JsonProtocol \ No newline at end of file diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/MetalsMetadataFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/MetalsMetadataFormats.scala new file mode 100644 index 000000000..2e6a249fd --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/MetalsMetadataFormats.scala @@ -0,0 +1,29 @@ +/** + * 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 MetalsMetadataFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val MetalsMetadataFormat: JsonFormat[sbt.internal.bsp.MetalsMetadata] = new JsonFormat[sbt.internal.bsp.MetalsMetadata] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.MetalsMetadata = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val semanticdbVersion = unbuilder.readField[String]("semanticdbVersion") + val supportedScalaVersions = unbuilder.readField[Vector[String]]("supportedScalaVersions") + unbuilder.endObject() + sbt.internal.bsp.MetalsMetadata(semanticdbVersion, supportedScalaVersions) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.MetalsMetadata, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("semanticdbVersion", obj.semanticdbVersion) + builder.addField("supportedScalaVersions", obj.supportedScalaVersions) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index 7405cedda..1732de2d9 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -135,8 +135,8 @@ type InitializeBuildParams { ## The capabilities of the client capabilities: sbt.internal.bsp.BuildClientCapabilities! - # Additional metadata about the client - # data: any + ## Additional metadata about the client + data: sjsonnew.shaded.scalajson.ast.unsafe.JValue } type BuildClientCapabilities { @@ -457,3 +457,12 @@ type BspConnectionDetails { ## Command arguments runnable via system processes to start a BSP server argv: [String] } + +## Metals metadata in the initialization request +type MetalsMetadata { + ## The semanticdb plugin version that should be enabled for Metals code navigation + semanticdbVersion: String! + + ## The list of scala versions that are supported by Metals + supportedScalaVersions: [String] +} \ No newline at end of file