From f89cef1fd01c67572081981231eb7467ff244636 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 28 Apr 2020 18:03:42 +0200 Subject: [PATCH] add bspCompile task --- main/src/main/scala/sbt/Keys.scala | 3 + .../internal/server/BuildServerProtocol.scala | 184 ++++++++++-------- .../server/LanguageServerProtocol.scala | 32 ++- .../sbt/internal/bsp/BspCompileResult.scala | 45 +++++ .../sbt/internal/bsp/CompileParams.scala | 51 +++++ .../sbt/internal/bsp/TaskFinishParams.scala | 57 ++++++ .../sbt/internal/bsp/TaskId.scala | 41 ++++ .../sbt/internal/bsp/TaskStartParams.scala | 53 +++++ .../bsp/codec/BspCompileResultFormats.scala | 29 +++ .../bsp/codec/CompileParamsFormats.scala | 31 +++ .../sbt/internal/bsp/codec/JsonProtocol.scala | 5 + .../bsp/codec/TaskFinishParamsFormats.scala | 33 ++++ .../internal/bsp/codec/TaskIdFormats.scala | 29 +++ .../bsp/codec/TaskStartParamsFormats.scala | 31 +++ protocol/src/main/contraband/bsp.contra | 78 ++++++++ .../scala/sbt/internal/bsp/StatusCode.scala | 7 + .../test/scala/testpkg/BuildServerTest.scala | 18 +- 17 files changed, 624 insertions(+), 103 deletions(-) create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/BspCompileResult.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/CompileParams.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/TaskFinishParams.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/TaskId.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/TaskStartParams.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BspCompileResultFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileParamsFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskFinishParamsFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskIdFormats.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskStartParamsFormats.scala create mode 100644 protocol/src/main/scala/sbt/internal/bsp/StatusCode.scala diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 49b4066d7..f8f340796 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -339,8 +339,11 @@ object Keys { val closeClassLoaders = settingKey[Boolean]("Close classloaders in run and test when the task completes.").withRank(DSetting) 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 bspBuildTarget = settingKey[BuildTarget]("Description of the BSP build target").withRank(DSetting) val bspBuildTargetSources = inputKey[Unit]("").withRank(DTask) val bspBuildTargetSourcesItem = taskKey[SourcesItem]("").withRank(DTask) + val bspBuildTargetCompile = inputKey[Unit]("").withRank(DTask) + val bspBuildTargetCompileItem = taskKey[Int]("").withRank(DTask) val bspBuildTargetScalacOptions = inputKey[Unit]("").withRank(DTask) val bspBuildTargetScalacOptionsItem = taskKey[ScalacOptionsItem]("").withRank(DTask) diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 223a1f4b7..a9250d36e 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -10,12 +10,14 @@ package internal package server import java.net.URI + import sbt.internal.bsp._ import sbt.internal.util.complete.DefaultParsers import sbt.librarymanagement.{ Configuration, Configurations } import Configurations.{ Compile, Test } import sbt.SlashSyntax0._ import sbt.BuildSyntax._ + import scala.collection.mutable import sjsonnew.support.scalajson.unsafe.Converter import Def._ @@ -44,6 +46,17 @@ object BuildServerProtocol { } }).evaluated, bspBuildTargetSources / aggregate := false, + bspBuildTargetCompile := Def.inputTaskDyn { + val s: State = state.value + val args: Seq[String] = spaceDelimited().parsed + val filter = toScopeFilter(args) + Def.task { + import sbt.internal.bsp.codec.JsonProtocol._ + val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max + s.respondEvent(BspCompileResult(None, statusCode)) + } + }.evaluated, + bspBuildTargetCompile / aggregate := false, bspBuildTargetScalacOptions := (Def.inputTaskDyn { import DefaultParsers._ val s = state.value @@ -57,16 +70,17 @@ object BuildServerProtocol { s.respondEvent(result) } }).evaluated, - bspBuildTargetScalacOptions / aggregate := false, + bspBuildTargetScalacOptions / aggregate := false ) - // This will be coped to Compile, Test, etc + // This will be scoped to Compile, Test, etc lazy val configSettings: Seq[Def.Setting[_]] = Seq( buildTargetIdentifier := { val ref = thisProjectRef.value val c = configuration.value toId(ref, c) }, + bspBuildTarget := bspBuildTargetSetting.value, bspBuildTargetSourcesItem := { val id = buildTargetIdentifier.value val dirs = unmanagedSourceDirectories.value @@ -79,15 +93,70 @@ object BuildServerProtocol { }) SourcesItem(id, items) }, + bspBuildTargetCompileItem := bspCompileTask.value, bspBuildTargetScalacOptionsItem := ScalacOptionsItem( target = buildTargetIdentifier.value, options = scalacOptions.value.toVector, classpath = fullClasspath.value.toVector.map(_.data.toURI), classDirectory = classDirectory.value.toURI - ), + ) ) + private def bspBuildTargetSetting: Def.Initialize[BuildTarget] = Def.settingDyn { + import sbt.internal.bsp.codec.JsonProtocol._ + val buildTargetIdentifier = Keys.buildTargetIdentifier.value + val thisProject = Keys.thisProject.value + val thisProjectRef = Keys.thisProjectRef.value + val compileData = ScalaBuildTarget( + scalaOrganization = scalaOrganization.value, + scalaVersion = scalaVersion.value, + scalaBinaryVersion = scalaBinaryVersion.value, + platform = ScalaPlatform.JVM, + jars = Vector("scala-library") + ) + val configuration = Keys.configuration.value + val displayName = configuration.name match { + case "compile" => thisProject.id + case configName => s"${thisProject.id} $configName" + } + val baseDirectory = Keys.baseDirectory.value.toURI + val internalDependencies = configuration.name match { + case "test" => + thisProject.dependencies :+ + ResolvedClasspathDependency(thisProjectRef, Some("test->compile")) + case _ => thisProject.dependencies + } + val dependencies = Initialize.join { + for { + dependencyRef <- internalDependencies + dependencyId <- dependencyTargetKeys(dependencyRef, configuration) + } yield dependencyId + } + Def.setting { + BuildTarget( + buildTargetIdentifier, + Some(displayName), + Some(baseDirectory), + tags = Vector.empty, + languageIds = Vector("scala"), + dependencies = dependencies.value.toVector, + dataKind = Some("scala"), + data = Some(Converter.toJsonUnsafe(compileData)), + ) + } + } + + private def bspCompileTask: Def.Initialize[Task[Int]] = Def.task { + import sbt.Project._ + Keys.compile.result.value match { + case Value(_) => StatusCode.Success + case Inc(_) => + // Cancellation is not yet implemented + StatusCode.Error + } + } + def toScopeFilter(args: Seq[String]): ScopeFilter = { val filters = args map { arg => val id = BuildTargetIdentifier(new URI(arg)) @@ -103,12 +172,28 @@ 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 + } } // This is mixed into NetworkChannel trait BuildServerImpl { self: LanguageServerProtocol with NetworkChannel => - import BuildServerProtocol._ - protected def structure: BuildStructure protected def getSetting[A](key: SettingKey[A]): Option[A] protected def setting[A](key: SettingKey[A]): A @@ -129,83 +214,18 @@ trait BuildServerImpl { self: LanguageServerProtocol with NetworkChannel => */ protected def onBspBuildTargets(execId: Option[String]): Unit = { import sbt.internal.bsp.codec.JsonProtocol._ - val targets = buildTargets - respondResult(targets, execId) - } - - def buildTargets: WorkspaceBuildTargetsResult = { - import sbt.internal.bsp.codec.JsonProtocol._ - val allProjectPairs = structure.allProjectPairs - val ts = allProjectPairs flatMap { - case (p, ref) => - val baseOpt = getSetting(ref / Keys.baseDirectory).map(_.toURI) - val internalCompileDeps = p.dependencies.flatMap(idForConfig(_, Compile)).toVector - val compileId = getSetting(ref / Compile / Keys.buildTargetIdentifier).get - idMap(compileId) = (ref, Compile) - val compileData = ScalaBuildTarget( - scalaOrganization = getSetting(ref / Compile / Keys.scalaOrganization).get, - scalaVersion = getSetting(ref / Compile / Keys.scalaVersion).get, - scalaBinaryVersion = getSetting(ref / Compile / Keys.scalaBinaryVersion).get, - platform = ScalaPlatform.JVM, - jars = Vector("scala-library") - ) - val t0 = BuildTarget( - compileId, - displayName = Some(p.id), - baseDirectory = baseOpt, - tags = Vector.empty, - languageIds = Vector("scala"), - dependencies = internalCompileDeps, - dataKind = Some("scala"), - data = Some(Converter.toJsonUnsafe(compileData)), - ) - val testId = getSetting(ref / Test / Keys.buildTargetIdentifier).get - idMap(testId) = (ref, Test) - // encode Test extending Compile - val internalTestDeps = - (p.dependencies ++ Seq(ResolvedClasspathDependency(ref, Some("test->compile")))) - .flatMap(idForConfig(_, Test)) - .toVector - val testData = ScalaBuildTarget( - scalaOrganization = getSetting(ref / Test / Keys.scalaOrganization).get, - scalaVersion = getSetting(ref / Test / Keys.scalaVersion).get, - scalaBinaryVersion = getSetting(ref / Test / Keys.scalaBinaryVersion).get, - platform = ScalaPlatform.JVM, - jars = Vector("scala-library") - ) - val t1 = BuildTarget( - testId, - displayName = Some(p.id + " test"), - baseDirectory = baseOpt, - tags = Vector.empty, - languageIds = Vector("scala"), - dependencies = internalTestDeps, - dataKind = Some("scala"), - data = Some(Converter.toJsonUnsafe(testData)), - ) - Seq(t0, t1) - } - WorkspaceBuildTargetsResult(ts.toVector) - } - - def idForConfig( - ref: ClasspathDep[ProjectRef], - from: Configuration - ): Seq[BuildTargetIdentifier] = { - val configStr = ref.configuration.getOrElse("compile") - val configExprs0 = configStr.split(",").toList - val configExprs1 = configExprs0 map { expr => - if (expr.contains("->")) { - val xs = expr.split("->") - (xs(0), xs(1)) - } else ("compile", expr) - } - configExprs1 flatMap { - case (fr, "compile") if fr == from.name => - Some(getSetting(ref.project / Compile / Keys.buildTargetIdentifier).get) - case (fr, "test") if fr == from.name => - Some(getSetting(ref.project / Test / Keys.buildTargetIdentifier).get) - case _ => None + val buildTargets = structure.allProjectRefs.flatMap { ref => + val compileTarget = getSetting(ref / Compile / Keys.bspBuildTarget).get + val testTarget = getSetting(ref / Test / Keys.bspBuildTarget).get + BuildServerProtocol.idMap ++= Seq( + compileTarget.id -> ((ref, Compile)), + testTarget.id -> ((ref, Test)) + ) + Seq(compileTarget, testTarget) } + respondResult( + WorkspaceBuildTargetsResult(buildTargets.toVector), + execId + ) } } diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index e5a83005c..f7aba795e 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -55,24 +55,6 @@ private[sbt] object LanguageServerProtocol { ) { - case r: JsonRpcRequestMessage if r.method == "initialize" => - if (authOptions(ServerAuthentication.Token)) { - val param = Converter.fromJson[InitializeParams](json(r)).get - val optionJson = param.initializationOptions.getOrElse( - throw LangServerError( - ErrorCodes.InvalidParams, - "initializationOptions is expected on 'initialize' param." - ) - ) - val opt = Converter.fromJson[InitializeOption](optionJson).get - val token = opt.token.getOrElse(sys.error("'token' is missing.")) - if (authenticate(token)) () - else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token") - } else () - setInitialized(true) - appendExec(Exec(s"collectAnalyses", None, Some(CommandSource(name)))) - jsonRpcRespond(InitializeResult(serverCapabilities), Option(r.id)) - case r: JsonRpcRequestMessage if r.method == "textDocument/definition" => implicit val executionContext: ExecutionContext = StandardMain.executionContext Definition.lspDefinition(json(r), r.id, CommandSource(name), converter, log) @@ -94,7 +76,6 @@ private[sbt] object LanguageServerProtocol { val param = Converter.fromJson[CP](json(r)).get onCompletionRequest(Option(r.id), param) case r: JsonRpcRequestMessage if r.method == "build/initialize" => - import sbt.protocol.codec.JsonProtocol._ val param = Converter.fromJson[InitializeBuildParams](json(r)).get onBspInitialize(Option(r.id), param) case r: JsonRpcRequestMessage if r.method == "workspace/buildTargets" => @@ -117,6 +98,19 @@ private[sbt] object LanguageServerProtocol { ) ) () + case r if r.method == "buildTarget/compile" => + val param = Converter.fromJson[CompileParams](json(r)).get + log.info(param.toString) + val targetsUris = param.targets.map(_.uri).mkString(" ") + val key = Keys.bspBuildTargetCompile.key.toString + appendExec( + Exec( + s"$key $targetsUris", + Option(r.id), + Some(CommandSource(name)) + ) + ) + () case r: JsonRpcRequestMessage if r.method == "buildTarget/scalacOptions" => import sbt.internal.bsp.codec.JsonProtocol._ val param = Converter.fromJson[ScalacOptionsParams](json(r)).get diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/BspCompileResult.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/BspCompileResult.scala new file mode 100644 index 000000000..49b73a8fc --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/BspCompileResult.scala @@ -0,0 +1,45 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Compile Response + * @param originId An optional request id to know the origin of this report. + * @param statusCode A status code for the execution. + */ +final class BspCompileResult private ( + val originId: Option[String], + val statusCode: Int) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: BspCompileResult => (this.originId == x.originId) && (this.statusCode == x.statusCode) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.BspCompileResult".##) + originId.##) + statusCode.##) + } + override def toString: String = { + "BspCompileResult(" + originId + ", " + statusCode + ")" + } + private[this] def copy(originId: Option[String] = originId, statusCode: Int = statusCode): BspCompileResult = { + new BspCompileResult(originId, statusCode) + } + def withOriginId(originId: Option[String]): BspCompileResult = { + copy(originId = originId) + } + def withOriginId(originId: String): BspCompileResult = { + copy(originId = Option(originId)) + } + def withStatusCode(statusCode: Int): BspCompileResult = { + copy(statusCode = statusCode) + } +} +object BspCompileResult { + + def apply(originId: Option[String], statusCode: Int): BspCompileResult = new BspCompileResult(originId, statusCode) + def apply(originId: String, statusCode: Int): BspCompileResult = new BspCompileResult(Option(originId), statusCode) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileParams.scala new file mode 100644 index 000000000..625f2baaf --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/CompileParams.scala @@ -0,0 +1,51 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Compile Request + * @param targets A sequence of build targets to compile + * @param originId An optional unique identifier generated by the client to identify this request. + The server may include this id in triggered notifications or response. + * @param arguments Optional arguments to the compilation process + */ +final class CompileParams private ( + val targets: Vector[sbt.internal.bsp.BuildTargetIdentifier], + val originId: Option[String], + val arguments: Vector[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: CompileParams => (this.targets == x.targets) && (this.originId == x.originId) && (this.arguments == x.arguments) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.CompileParams".##) + targets.##) + originId.##) + arguments.##) + } + override def toString: String = { + "CompileParams(" + targets + ", " + originId + ", " + arguments + ")" + } + private[this] def copy(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier] = targets, originId: Option[String] = originId, arguments: Vector[String] = arguments): CompileParams = { + new CompileParams(targets, originId, arguments) + } + def withTargets(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): CompileParams = { + copy(targets = targets) + } + def withOriginId(originId: Option[String]): CompileParams = { + copy(originId = originId) + } + def withOriginId(originId: String): CompileParams = { + copy(originId = Option(originId)) + } + def withArguments(arguments: Vector[String]): CompileParams = { + copy(arguments = arguments) + } +} +object CompileParams { + + def apply(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier], originId: Option[String], arguments: Vector[String]): CompileParams = new CompileParams(targets, originId, arguments) + def apply(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier], originId: String, arguments: Vector[String]): CompileParams = new CompileParams(targets, Option(originId), arguments) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskFinishParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskFinishParams.scala new file mode 100644 index 000000000..b628645bc --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskFinishParams.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 taskId Unique id of the task with optional reference to parent task id. + * @param eventTime Optional timestamp of when the event started in milliseconds since Epoch. + * @param message Optional message describing the task. + * @param status Task completion status: 1 -> success, 2 -> error, 3 -> cancelled + */ +final class TaskFinishParams private ( + val taskId: sbt.internal.bsp.TaskId, + val eventTime: Option[Long], + val message: Option[String], + val status: Int) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TaskFinishParams => (this.taskId == x.taskId) && (this.eventTime == x.eventTime) && (this.message == x.message) && (this.status == x.status) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.TaskFinishParams".##) + taskId.##) + eventTime.##) + message.##) + status.##) + } + override def toString: String = { + "TaskFinishParams(" + taskId + ", " + eventTime + ", " + message + ", " + status + ")" + } + private[this] def copy(taskId: sbt.internal.bsp.TaskId = taskId, eventTime: Option[Long] = eventTime, message: Option[String] = message, status: Int = status): TaskFinishParams = { + new TaskFinishParams(taskId, eventTime, message, status) + } + def withTaskId(taskId: sbt.internal.bsp.TaskId): TaskFinishParams = { + copy(taskId = taskId) + } + def withEventTime(eventTime: Option[Long]): TaskFinishParams = { + copy(eventTime = eventTime) + } + def withEventTime(eventTime: Long): TaskFinishParams = { + copy(eventTime = Option(eventTime)) + } + def withMessage(message: Option[String]): TaskFinishParams = { + copy(message = message) + } + def withMessage(message: String): TaskFinishParams = { + copy(message = Option(message)) + } + def withStatus(status: Int): TaskFinishParams = { + copy(status = status) + } +} +object TaskFinishParams { + + def apply(taskId: sbt.internal.bsp.TaskId, eventTime: Option[Long], message: Option[String], status: Int): TaskFinishParams = new TaskFinishParams(taskId, eventTime, message, status) + def apply(taskId: sbt.internal.bsp.TaskId, eventTime: Long, message: String, status: Int): TaskFinishParams = new TaskFinishParams(taskId, Option(eventTime), Option(message), status) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskId.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskId.scala new file mode 100644 index 000000000..e81f221c6 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskId.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 +/** + * @param id A unique identifier + * @param parents The parent task ids, if any. A non-empty parents field means + this task is a sub-task of every parent task. + */ +final class TaskId private ( + val id: String, + val parents: Vector[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TaskId => (this.id == x.id) && (this.parents == x.parents) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.TaskId".##) + id.##) + parents.##) + } + override def toString: String = { + "TaskId(" + id + ", " + parents + ")" + } + private[this] def copy(id: String = id, parents: Vector[String] = parents): TaskId = { + new TaskId(id, parents) + } + def withId(id: String): TaskId = { + copy(id = id) + } + def withParents(parents: Vector[String]): TaskId = { + copy(parents = parents) + } +} +object TaskId { + + def apply(id: String, parents: Vector[String]): TaskId = new TaskId(id, parents) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskStartParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskStartParams.scala new file mode 100644 index 000000000..3981c49cb --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/TaskStartParams.scala @@ -0,0 +1,53 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Task Notifications + * @param taskId Unique id of the task with optional reference to parent task id. + * @param eventTime Optional timestamp of when the event started in milliseconds since Epoch. + * @param message Optional message describing the task. + */ +final class TaskStartParams private ( + val taskId: sbt.internal.bsp.TaskId, + val eventTime: Option[Long], + val message: Option[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TaskStartParams => (this.taskId == x.taskId) && (this.eventTime == x.eventTime) && (this.message == x.message) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.TaskStartParams".##) + taskId.##) + eventTime.##) + message.##) + } + override def toString: String = { + "TaskStartParams(" + taskId + ", " + eventTime + ", " + message + ")" + } + private[this] def copy(taskId: sbt.internal.bsp.TaskId = taskId, eventTime: Option[Long] = eventTime, message: Option[String] = message): TaskStartParams = { + new TaskStartParams(taskId, eventTime, message) + } + def withTaskId(taskId: sbt.internal.bsp.TaskId): TaskStartParams = { + copy(taskId = taskId) + } + def withEventTime(eventTime: Option[Long]): TaskStartParams = { + copy(eventTime = eventTime) + } + def withEventTime(eventTime: Long): TaskStartParams = { + copy(eventTime = Option(eventTime)) + } + def withMessage(message: Option[String]): TaskStartParams = { + copy(message = message) + } + def withMessage(message: String): TaskStartParams = { + copy(message = Option(message)) + } +} +object TaskStartParams { + + def apply(taskId: sbt.internal.bsp.TaskId, eventTime: Option[Long], message: Option[String]): TaskStartParams = new TaskStartParams(taskId, eventTime, message) + def apply(taskId: sbt.internal.bsp.TaskId, eventTime: Long, message: String): TaskStartParams = new TaskStartParams(taskId, Option(eventTime), Option(message)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BspCompileResultFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BspCompileResultFormats.scala new file mode 100644 index 000000000..23f0b7a6e --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BspCompileResultFormats.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 BspCompileResultFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val BspCompileResultFormat: JsonFormat[sbt.internal.bsp.BspCompileResult] = new JsonFormat[sbt.internal.bsp.BspCompileResult] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.BspCompileResult = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val originId = unbuilder.readField[Option[String]]("originId") + val statusCode = unbuilder.readField[Int]("statusCode") + unbuilder.endObject() + sbt.internal.bsp.BspCompileResult(originId, statusCode) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.BspCompileResult, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("originId", obj.originId) + builder.addField("statusCode", obj.statusCode) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileParamsFormats.scala new file mode 100644 index 000000000..f662874df --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/CompileParamsFormats.scala @@ -0,0 +1,31 @@ +/** + * 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 CompileParamsFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val CompileParamsFormat: JsonFormat[sbt.internal.bsp.CompileParams] = new JsonFormat[sbt.internal.bsp.CompileParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.CompileParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val targets = unbuilder.readField[Vector[sbt.internal.bsp.BuildTargetIdentifier]]("targets") + val originId = unbuilder.readField[Option[String]]("originId") + val arguments = unbuilder.readField[Vector[String]]("arguments") + unbuilder.endObject() + sbt.internal.bsp.CompileParams(targets, originId, arguments) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.CompileParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("targets", obj.targets) + builder.addField("originId", obj.originId) + builder.addField("arguments", obj.arguments) + 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 d8e553035..6083f41b2 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 @@ -8,6 +8,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.BuildTargetIdentifierFormats with sbt.internal.util.codec.JValueFormats with sbt.internal.bsp.codec.BuildTargetFormats + with sbt.internal.bsp.codec.TaskIdFormats with sbt.internal.bsp.codec.BuildClientCapabilitiesFormats with sbt.internal.bsp.codec.InitializeBuildParamsFormats with sbt.internal.bsp.codec.InitializeBuildResultFormats @@ -16,6 +17,10 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.SourceItemFormats with sbt.internal.bsp.codec.SourcesItemFormats with sbt.internal.bsp.codec.SourcesResultFormats + with sbt.internal.bsp.codec.TaskStartParamsFormats + with sbt.internal.bsp.codec.TaskFinishParamsFormats + with sbt.internal.bsp.codec.CompileParamsFormats + with sbt.internal.bsp.codec.BspCompileResultFormats with sbt.internal.bsp.codec.ScalaBuildTargetFormats with sbt.internal.bsp.codec.SbtBuildTargetFormats with sbt.internal.bsp.codec.ScalacOptionsParamsFormats diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskFinishParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskFinishParamsFormats.scala new file mode 100644 index 000000000..b821a6171 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskFinishParamsFormats.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 TaskFinishParamsFormats { self: sbt.internal.bsp.codec.TaskIdFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val TaskFinishParamsFormat: JsonFormat[sbt.internal.bsp.TaskFinishParams] = new JsonFormat[sbt.internal.bsp.TaskFinishParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.TaskFinishParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val taskId = unbuilder.readField[sbt.internal.bsp.TaskId]("taskId") + val eventTime = unbuilder.readField[Option[Long]]("eventTime") + val message = unbuilder.readField[Option[String]]("message") + val status = unbuilder.readField[Int]("status") + unbuilder.endObject() + sbt.internal.bsp.TaskFinishParams(taskId, eventTime, message, status) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.TaskFinishParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("taskId", obj.taskId) + builder.addField("eventTime", obj.eventTime) + builder.addField("message", obj.message) + builder.addField("status", obj.status) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskIdFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskIdFormats.scala new file mode 100644 index 000000000..55c52734e --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskIdFormats.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 TaskIdFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val TaskIdFormat: JsonFormat[sbt.internal.bsp.TaskId] = new JsonFormat[sbt.internal.bsp.TaskId] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.TaskId = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val id = unbuilder.readField[String]("id") + val parents = unbuilder.readField[Vector[String]]("parents") + unbuilder.endObject() + sbt.internal.bsp.TaskId(id, parents) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.TaskId, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("id", obj.id) + builder.addField("parents", obj.parents) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskStartParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskStartParamsFormats.scala new file mode 100644 index 000000000..74b144237 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/TaskStartParamsFormats.scala @@ -0,0 +1,31 @@ +/** + * 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 TaskStartParamsFormats { self: sbt.internal.bsp.codec.TaskIdFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val TaskStartParamsFormat: JsonFormat[sbt.internal.bsp.TaskStartParams] = new JsonFormat[sbt.internal.bsp.TaskStartParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.TaskStartParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val taskId = unbuilder.readField[sbt.internal.bsp.TaskId]("taskId") + val eventTime = unbuilder.readField[Option[Long]]("eventTime") + val message = unbuilder.readField[Option[String]]("message") + unbuilder.endObject() + sbt.internal.bsp.TaskStartParams(taskId, eventTime, message) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.TaskStartParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("taskId", obj.taskId) + builder.addField("eventTime", obj.eventTime) + builder.addField("message", obj.message) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index 79ba0c802..f8ad0de4f 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -52,6 +52,15 @@ type BuildTargetIdentifier { uri: java.net.URI! } +type TaskId { + ## A unique identifier + id: String! + + ## The parent task ids, if any. A non-empty parents field means + ## this task is a sub-task of every parent task. + parents: [String] +} + ## Initialize Build Request type InitializeBuildParams { ## Name of the client @@ -136,6 +145,75 @@ type SourceItem { generated: Boolean! } +## Task Notifications + +type TaskStartParams { + ## Unique id of the task with optional reference to parent task id. + taskId: sbt.internal.bsp.TaskId! + + ## Optional timestamp of when the event started in milliseconds since Epoch. + eventTime: Long + + ## Optional message describing the task. + message: String + + ## Kind of data to expect in the `data` field. + # dataKind: String + + ## Optional metadata about the task. + # data: Any +} + +type TaskFinishParams { + ## Unique id of the task with optional reference to parent task id. + taskId: sbt.internal.bsp.TaskId! + + ## Optional timestamp of when the event started in milliseconds since Epoch. + eventTime: Long + + ## Optional message describing the task. + message: String + + ## Task completion status: 1 -> success, 2 -> error, 3 -> cancelled + status: Int! + + # Kind of data to expect in the `data` field. + # dataKind: String + + # Optional metadata about the task. + # data: Any +} + +## Compile Request +type CompileParams { + ## A sequence of build targets to compile + targets: [sbt.internal.bsp.BuildTargetIdentifier] + + ## An optional unique identifier generated by the client to identify this request. + ## The server may include this id in triggered notifications or response. + originId: String + + ## Optional arguments to the compilation process + arguments: [String] +} + +## Compile Response +type BspCompileResult { + ## An optional request id to know the origin of this report. + originId: String + + ## A status code for the execution. + statusCode: Int! + + # Kind of data to expect in the `data` field. + # If this field is not set, the kind of data is not specified. + # dataKind: String + + # A field containing language-specific information, like products + # of compilation or compiler-specific metadata the client needs to know. + # data: any +} + # Scala Extension ## Contains scala-specific metadata for compiling a target containing Scala sources. diff --git a/protocol/src/main/scala/sbt/internal/bsp/StatusCode.scala b/protocol/src/main/scala/sbt/internal/bsp/StatusCode.scala new file mode 100644 index 000000000..5614d4364 --- /dev/null +++ b/protocol/src/main/scala/sbt/internal/bsp/StatusCode.scala @@ -0,0 +1,7 @@ +package sbt.internal.bsp + +object StatusCode { + final val Success: Int = 1 + final val Error: Int = 2 + final val Cancelled: Int = 3 +} diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index c6f1e0b0d..9dbfbd0f0 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -46,16 +46,30 @@ object BuildServerTest extends AbstractServerTest { }) } + test("buildTarget/compile") { _ => + val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile" + svr.sendJsonRpc( + s"""{ "jsonrpc": "2.0", "id": "13", "method": "buildTarget/compile", "params": { + | "targets": [{ "uri": "$x" }] + |} }""".stripMargin + ) + assert(svr.waitForString(10.seconds) { s => + println(s) + (s contains """"id":"13"""") && + (s contains """"statusCode":1""") + }) + } + test("buildTarget/scalacOptions") { _ => val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile" svr.sendJsonRpc( - s"""{ "jsonrpc": "2.0", "id": "13", "method": "buildTarget/scalacOptions", "params": { + s"""{ "jsonrpc": "2.0", "id": "14", "method": "buildTarget/scalacOptions", "params": { | "targets": [{ "uri": "$x" }] |} }""".stripMargin ) assert(svr.waitForString(10.seconds) { s => println(s) - (s contains """"id":"13"""") && + (s contains """"id":"14"""") && (s contains "scala-library-2.13.1.jar") }) }