summaryrefslogtreecommitdiff
path: root/contrib/bsp/src
diff options
context:
space:
mode:
authorAlexandra Dima <alexandra.dima@jetbrains.com>2019-07-10 17:12:12 +0200
committerSamvel Abrahamyan <samvel1024@gmail.com>2019-10-12 14:32:58 +0200
commit0d53a6e057ae24ecf0bfd5bf0929310723c31282 (patch)
tree1503d4f04ff8c4bfb54f3ae841293ae20076eb8b /contrib/bsp/src
parentb71748b1b58da3b70ceb9290257ed688b71fbe21 (diff)
downloadmill-0d53a6e057ae24ecf0bfd5bf0929310723c31282.tar.gz
mill-0d53a6e057ae24ecf0bfd5bf0929310723c31282.tar.bz2
mill-0d53a6e057ae24ecf0bfd5bf0929310723c31282.zip
Implemented support for publishing compilation diagnostics through the custom BspLoggedReporter reporter. Patched the mill.api.Ctx data structure as well as the evaluate() method on mill's Evaluator in order to accept a potential reporter from the outside, or use a default value if none is given.
Diffstat (limited to 'contrib/bsp/src')
-rw-r--r--contrib/bsp/src/mill/contrib/MainMillBuildServer.scala30
-rw-r--r--contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala82
-rw-r--r--contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala152
-rw-r--r--contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala3
4 files changed, 254 insertions, 13 deletions
diff --git a/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala b/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala
index a8508ab6..7caaf5d3 100644
--- a/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala
+++ b/contrib/bsp/src/mill/contrib/MainMillBuildServer.scala
@@ -7,10 +7,10 @@ import java.nio.file.FileAlreadyExistsException
import java.util.concurrent.{CancellationException, CompletableFuture, ExecutorService, Executors, Future}
import upickle.default._
-import ch.epfl.scala.bsp4j.{BspConnectionDetails, BuildClient, ScalaTestClassesParams}
+import ch.epfl.scala.bsp4j.{BspConnectionDetails, BuildClient, DidChangeBuildTarget, LogMessageParams, PublishDiagnosticsParams, ScalaTestClassesParams, ShowMessageParams, TaskFinishParams, TaskProgressParams, TaskStartParams, WorkspaceBuildTargetsResult}
import mill._
import mill.api.Strict
-import mill.contrib.bsp.{MillBuildServer, ModuleUtils}
+import mill.contrib.bsp.{BspLoggedReporter, MillBuildServer, ModuleUtils}
import mill.define.{Command, Discover, ExternalModule, Target, Task}
import mill.eval.Evaluator
import mill.scalalib._
@@ -20,6 +20,7 @@ import requests.Compress.None
import upickle.default
import scala.collection.JavaConverters._
+import scala.io.Source
object MainMillBuildServer extends ExternalModule {
@@ -152,11 +153,28 @@ object MainMillBuildServer extends ExternalModule {
def experiment(ev: Evaluator): Command[Unit] = T.command {
val millServer = new mill.contrib.bsp.MillBuildServer(ev, bspVersion, version, languages)
- val mods: Seq[JavaModule] = modules(ev)()
- for (module <- mods) {
- println(module.millModuleSegments.parts.mkString("."))
+ val client = new BuildClient {
+ var diagnostics = List.empty[PublishDiagnosticsParams]
+ override def onBuildShowMessage(params: ShowMessageParams): Unit = ???
+ override def onBuildLogMessage(params: LogMessageParams): Unit = ???
+ override def onBuildTaskStart(params: TaskStartParams): Unit = ???
+ override def onBuildTaskProgress(params: TaskProgressParams): Unit = ???
+ override def onBuildTaskFinish(params: TaskFinishParams): Unit = ???
+ override def onBuildPublishDiagnostics(
+ params: PublishDiagnosticsParams
+ ): Unit = {
+ diagnostics ++= List(params)
}
-
+ override def onBuildTargetDidChange(params: DidChangeBuildTarget): Unit =
+ ???
+ }
+ for (module <- millServer.millModules) {
+ ev.evaluate(Strict.Agg(module.compile), Option(new BspLoggedReporter(client,
+ millServer.moduleToTargetId(module),
+ Option.empty[String],
+ 10, millServer.getCompilationLogger)))
+ //println("Diagnostics: " + client.diagnostics)
+ }
}
/**
diff --git a/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala b/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala
new file mode 100644
index 00000000..31900211
--- /dev/null
+++ b/contrib/bsp/src/mill/contrib/bsp/BspLoggedReporter.scala
@@ -0,0 +1,82 @@
+package mill.contrib.bsp
+
+import java.io.File
+
+import ch.epfl.scala.{bsp4j => bsp}
+import sbt.internal.inc.ManagedLoggedReporter
+import sbt.internal.inc.schema.Position
+import sbt.internal.util.ManagedLogger
+import xsbti.{Problem, Severity}
+
+import scala.collection.JavaConverters._
+import scala.compat.java8.OptionConverters._
+import scala.io.Source
+
+class BspLoggedReporter(client: bsp.BuildClient,
+ targetId: bsp.BuildTargetIdentifier,
+ compilationOriginId: Option[String],
+ maxErrors: Int,
+ logger: ManagedLogger) extends ManagedLoggedReporter(maxErrors, logger) {
+
+ override def logError(problem: Problem): Unit = {
+ client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
+ println("Sent diagnostics to the client")
+ super.logError(problem)
+ println("Logged the error")
+ }
+
+ override def logInfo(problem: Problem): Unit = {
+ client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
+ super.logInfo(problem)
+ }
+
+ override def logWarning(problem: Problem): Unit = {
+ client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
+ super.logWarning(problem)
+ }
+
+ def getDiagnostics(problem: Problem, targetId: bsp.BuildTargetIdentifier, originId: Option[String]):
+ bsp.PublishDiagnosticsParams = {
+ println("Problem: " + problem)
+ val sourceFile = problem.position().sourceFile().asScala
+ val start = new bsp.Position(
+ problem.position.startLine.asScala.getOrElse(0),
+ problem.position.startOffset.asScala.getOrElse(0))
+ val end = new bsp.Position(
+ problem.position.endLine.asScala.getOrElse(0),
+ problem.position.endOffset.asScala.getOrElse(0))
+ val diagnostic = new bsp.Diagnostic(new bsp.Range(start, end), problem.message)
+ diagnostic.setCode(problem.position.lineContent)
+ diagnostic.setSource("compiler from mill")
+ diagnostic.setSeverity( problem.severity match {
+ case Severity.Info => bsp.DiagnosticSeverity.INFORMATION
+ case Severity.Error => bsp.DiagnosticSeverity.ERROR
+ case Severity.Warn => bsp.DiagnosticSeverity.WARNING
+ }
+ )
+
+ val params = new bsp.PublishDiagnosticsParams(
+ new bsp.TextDocumentIdentifier(sourceFile.getOrElse(new File(targetId.getUri)).toPath.toAbsolutePath.toUri.toString),
+ targetId, List(diagnostic).asJava, true)
+
+ if (originId.nonEmpty) { params.setOriginId(originId.get) }
+ println("Diagnostics: " + params)
+ params
+ }
+
+ private[this] def getErrorCode(file: Option[File], start: bsp.Position, end: bsp.Position, position: xsbti.Position): String = {
+ file match {
+ case None => position.lineContent
+ case f: Option[File] =>
+ val source = Source.fromFile(f.get)
+ source.close()
+ val lines = source.getLines.toSeq
+ val code = lines(start.getLine).substring(start.getCharacter) +
+ lines.take(start.getLine - 1).takeRight(lines.length - end.getLine - 1).mkString("\n") +
+ lines(end.getLine).substring(0, end.getCharacter + 1)
+ code
+ }
+
+ }
+
+}
diff --git a/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala b/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala
index 91d9c4fd..5d79ee76 100644
--- a/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala
+++ b/contrib/bsp/src/mill/contrib/bsp/MillBuildServer.scala
@@ -1,25 +1,34 @@
package mill.contrib.bsp
+import java.io.File
+
import sbt.testing._
import java.util.{Calendar, Collections}
import java.util.concurrent.CompletableFuture
+import scala.compat.java8.OptionConverters._
import mill.scalalib.Lib.discoverTests
import ch.epfl.scala.bsp4j._
+import ch.epfl.scala.{bsp4j => bsp}
import mill.{scalalib, _}
import mill.api.{Loose, Result, Strict}
import mill.contrib.bsp.ModuleUtils._
import mill.eval.Evaluator
import mill.scalalib._
-import mill.scalalib.api.CompilationResult
-import mill.scalalib.api.ZincWorkerApi
+import mill.scalalib.api.{CompilationResult, ZincWorkerApi}
+import sbt.internal.inc._
+import xsbti.{Position, Problem, Severity}
+import xsbti.compile.{AnalysisContents, AnalysisStore, FileAnalysisStore}
+import xsbti.compile.analysis.SourceInfo
import scala.collection.mutable.Map
-import mill.api.Result.{Failing, Success}
+import mill.api.Result.{Failing, Failure, Success}
import scala.collection.JavaConverters._
import mill.modules.Jvm
import mill.util.{Ctx, PrintLogger}
import mill.define.{Discover, ExternalModule, Target, Task}
+import sbt.internal.util.{ConsoleOut, MainAppender, ManagedLogger}
+import sbt.util.LogExchange
import scala.io.Source
@@ -31,7 +40,6 @@ class MillBuildServer(evaluator: Evaluator,
implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()
lazy val millDiscover: Discover[MillBuildServer.this.type] = Discover[this.type]
-
val bspVersion: String = _bspVersion
val supportedLanguages: List[String] = languages
val millServerVersion: String = serverVersion
@@ -44,7 +52,6 @@ class MillBuildServer(evaluator: Evaluator,
var moduleToTarget: Predef.Map[JavaModule, BuildTarget] =
ModuleUtils.millModulesToBspTargets(millModules, evaluator, List("scala", "java"))
-
var clientInitialized = false
val ctx: Ctx.Log with Ctx.Home = new Ctx.Log with Ctx.Home {
@@ -217,6 +224,131 @@ class MillBuildServer(evaluator: Evaluator,
future
}
+ private[this] def getErrorCode(file: File, start: bsp.Position, end: bsp.Position): String = {
+
+ val source = Source.fromFile(file)
+ source.close()
+ val lines = source.getLines.toSeq
+ val code = lines(start.getLine).substring(start.getCharacter) +
+ lines.take(start.getLine - 1).takeRight(lines.length - end.getLine - 1).mkString("\n") +
+ lines(end.getLine).substring(0, end.getCharacter + 1)
+ code
+ }
+
+ def getDiagnosticsFromFile(analysisFile: os.Path, targetId: BuildTargetIdentifier, originId: Option[String]):
+ Seq[PublishDiagnosticsParams] = {
+ val analysisStore: AnalysisStore = FileAnalysisStore.getDefault(analysisFile.toIO)
+ analysisStore.get.asScala match {
+ case contents: AnalysisContents =>
+ val sourceInfoMap: Map[File, SourceInfo] = contents.getAnalysis.readSourceInfos.getAllSourceInfos.asScala
+ var diagnosticsParams = Seq.empty[PublishDiagnosticsParams]
+ for ( (file, sourceInfo) <- sourceInfoMap) {
+ var diagnostics = List.empty[Diagnostic]
+ for (problem <- sourceInfo.getReportedProblems) {
+ val start = new bsp.Position(
+ problem.position.startLine.asScala.getOrElse(0),
+ problem.position.startOffset.asScala.getOrElse(0))
+ val end = new bsp.Position(
+ problem.position.endLine.asScala.getOrElse(0),
+ problem.position.endOffset.asScala.getOrElse(0))
+ val diagnostic = new Diagnostic(new Range(start, end), problem.message)
+ diagnostic.setCode(getErrorCode(file, start, end))
+ diagnostic.setSource("compiler from mill")
+
+ diagnostic.setSeverity( problem.severity match {
+ case Severity.Info => DiagnosticSeverity.INFORMATION
+ case Severity.Error => DiagnosticSeverity.ERROR
+ case Severity.Warn => DiagnosticSeverity.WARNING
+ }
+ )
+ diagnostics ++= List(diagnostic)
+ }
+ val params = new PublishDiagnosticsParams(new TextDocumentIdentifier(file.toURI.toString),
+ targetId, diagnostics.asJava, true)
+ if (originId.nonEmpty) { params.setOriginId(originId.get) }
+ diagnosticsParams ++= Seq(params)
+ }
+ diagnosticsParams
+ case None => Seq.empty[PublishDiagnosticsParams]
+ }
+ }
+
+ def getSourceFileCompileErrors(problems: Seq[Problem]): Map[File, Array[Problem]] = {
+ val problemsMap = Map.empty[File, Array[Problem]]
+
+ for (problem <- problems) {
+ try {
+ val sourceFile = problem.position.sourceFile.get
+ if (problemsMap.contains(sourceFile)) {
+ problemsMap(sourceFile) = problemsMap(sourceFile) ++ Array(problem)
+ } else {
+ problemsMap(sourceFile) = Array(problem)
+ }
+
+ } catch {
+ case e: Exception =>
+ }
+ }
+ problemsMap
+ }
+
+ def getDiagnostics(problems: Array[Problem], targetId: BuildTargetIdentifier, originId: Option[String]):
+ Seq[PublishDiagnosticsParams] = {
+ var diagnosticsParams = Seq.empty[PublishDiagnosticsParams]
+ for (( sourceFile, problems ) <- getSourceFileCompileErrors(problems)) {
+ var diagnostics = Seq.empty[Diagnostic]
+ for (problem <- problems) {
+ val start = new bsp.Position(
+ problem.position.startLine.asScala.getOrElse(0),
+ problem.position.startOffset.asScala.getOrElse(0))
+ val end = new bsp.Position(
+ problem.position.endLine.asScala.getOrElse(0),
+ problem.position.endOffset.asScala.getOrElse(0))
+ val diagnostic = new Diagnostic(new Range(start, end), problem.message)
+ diagnostic.setCode(getErrorCode(sourceFile, start, end))
+ diagnostic.setSource("compiler from mill")
+ diagnostic.setSeverity( problem.severity match {
+ case Severity.Info => DiagnosticSeverity.INFORMATION
+ case Severity.Error => DiagnosticSeverity.ERROR
+ case Severity.Warn => DiagnosticSeverity.WARNING
+ }
+ )
+ diagnostics ++= List(diagnostic)
+ }
+ val params = new PublishDiagnosticsParams(new TextDocumentIdentifier(sourceFile.toPath.toAbsolutePath.toUri.toString),
+ targetId, diagnostics.asJava, true)
+
+ if (originId.nonEmpty) { params.setOriginId(originId.get) }
+ diagnosticsParams ++= Seq(params)
+ }
+ diagnosticsParams
+ }
+
+ def getOriginId(params: CompileParams): Option[String] = {
+ try {
+ Option(params.getOriginId)
+ } catch {
+ case e: Exception => Option.empty[String]
+ }
+ }
+
+ def sendCompilationDiagnostics(problems: Array[Problem], targetId: BuildTargetIdentifier, compileParams: CompileParams) = {
+ for (publishDiagnosticsParams <-
+ getDiagnostics(problems, targetId, getOriginId(compileParams))) {
+ client.onBuildPublishDiagnostics(publishDiagnosticsParams)
+ }
+ }
+
+ def getCompilationLogger: ManagedLogger = {
+ val consoleAppender = MainAppender.defaultScreen(ConsoleOut.printStreamOut(
+ mill.util.DummyLogger.outputStream
+ ))
+ val l = LogExchange.logger("Hello")
+ LogExchange.unbindLoggerAppenders("Hello")
+ LogExchange.bindLoggerAppenders("Hello", (consoleAppender -> sbt.util.Level.Info) :: Nil)
+ l
+ }
+
//TODO: send task notifications - start, progress and finish
//TODO: if the client wants to give compilation arguments and the module
// already has some from the build file, what to do?
@@ -229,9 +361,10 @@ class MillBuildServer(evaluator: Evaluator,
var compileTime = 0
for (targetId <- compileParams.getTargets.asScala) {
if (moduleToTarget(targetIdToModule(targetId)).getCapabilities.getCanCompile) {
- var millModule = targetIdToModule(targetId)
+ val millModule = targetIdToModule(targetId)
//millModule.javacOptions = compileParams.getArguments.asScala
val compileTask = millModule.compile
+
// send notification to client that compilation of this target started
val taskStartParams = new TaskStartParams(new TaskId(compileTask.hashCode().toString))
taskStartParams.setEventTime(System.currentTimeMillis())
@@ -240,8 +373,13 @@ class MillBuildServer(evaluator: Evaluator,
taskStartParams.setData(new CompileTask(targetId))
client.onBuildTaskStart(taskStartParams)
- val result = millEvaluator.evaluate(Strict.Agg(compileTask))
+ val result = millEvaluator.evaluate(Strict.Agg(compileTask),
+ Option(new BspLoggedReporter(client,
+ targetId,
+ getOriginId(compileParams),
+ 10, getCompilationLogger)))
val endTime = System.currentTimeMillis()
+
compileTime += result.timings.map(timingTuple => timingTuple._2).sum
var statusCode = StatusCode.OK
diff --git a/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala b/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala
index 266d832a..a3202aab 100644
--- a/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala
+++ b/contrib/bsp/src/mill/contrib/bsp/ModuleUtils.scala
@@ -51,6 +51,9 @@ object ModuleUtils {
supportedLanguages.asJava,
dependencies,
capabilities)
+ if (module.isInstanceOf[ScalaModule]) {
+ buildTarget.setDataKind("scala")
+ }
buildTarget.setData(dataBuildTarget)
buildTarget.setDisplayName(module.millModuleSegments.last.value.toList.head.pathSegments.head)
buildTarget.setBaseDirectory(module.millSourcePath.toNIO.toAbsolutePath.toUri.toString)