Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup BSP server implementation #2814

Merged
merged 1 commit into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 57 additions & 97 deletions bsp/worker/src/mill/bsp/worker/BspCompileProblemReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@ package mill.bsp.worker
import ch.epfl.scala.bsp4j._
import ch.epfl.scala.{bsp4j => bsp}
import mill.api.{CompileProblemReporter, Problem}
import os.Path

import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger}
import scala.collection.concurrent
import scala.jdk.CollectionConverters._
import scala.collection.mutable

/**
* Specialized reporter that sends compilation diagnostics
Expand All @@ -33,40 +28,31 @@ private class BspCompileProblemReporter(
taskId: TaskId,
compilationOriginId: Option[String]
) extends CompileProblemReporter {
private var errors = 0
private var warnings = 0

private[this] val errors = new AtomicInteger(0)
private[this] val warnings = new AtomicInteger(0)
private[this] val infos = new AtomicInteger(0)
private[this] val diagnosticMap
: concurrent.Map[TextDocumentIdentifier, bsp.PublishDiagnosticsParams] =
new ConcurrentHashMap[TextDocumentIdentifier, bsp.PublishDiagnosticsParams]().asScala
private[this] val started = new AtomicBoolean(false)
private[this] val finished = new AtomicBoolean(false)
object diagnostics {
private val map = mutable.Map.empty[TextDocumentIdentifier, java.util.List[Diagnostic]]
def add(textDocument: TextDocumentIdentifier, diagnostic: Diagnostic): Unit =
map.getOrElseUpdate(textDocument, new java.util.ArrayList).add(diagnostic)

val compileTask = new CompileTask(targetId)
def getAll(textDocument: TextDocumentIdentifier): java.util.List[Diagnostic] =
map.getOrElse(textDocument, new java.util.ArrayList)
}

override def logError(problem: Problem): Unit = {
client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
errors.incrementAndGet()
reportProblem(problem)
errors += 1
}

override def logInfo(problem: Problem): Unit = {
client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
infos.incrementAndGet()
reportProblem(problem)
}

// Obtains the parameters for sending diagnostics about the given Problem ( as well as
// about all previous problems generated for the same text file ) related to the specified
// targetId, incorporating the given optional originId ( generated by the client for the
// compile request )
// TODO: document that if the problem is a general information without a text document
// associated to it, then the document field of the diagnostic is set to the uri of the target
private[this] def getDiagnostics(
problem: Problem,
targetId: bsp.BuildTargetIdentifier,
originId: Option[String]
): bsp.PublishDiagnosticsParams = {
val diagnostic = getSingleDiagnostic(problem)
private def reportProblem(problem: Problem): Unit = {
val diagnostic = toDiagnostic(problem)
val sourceFile = problem.position.sourceFile
val textDocument = new TextDocumentIdentifier(
sourceFile match {
Expand All @@ -76,46 +62,14 @@ private class BspCompileProblemReporter(
f.toPath.toUri.toString
}
)
val params = new bsp.PublishDiagnosticsParams(
textDocument,
targetId,
appendDiagnostics(textDocument, diagnostic).asJava,
true
)

if (originId.nonEmpty) {
params.setOriginId(originId.get)
}
diagnosticMap.put(textDocument, params)
params
}

private[this] def ensureDiagnostics(textDocument: TextDocumentIdentifier)
: bsp.PublishDiagnosticsParams = {
diagnosticMap.putIfAbsent(
textDocument,
new bsp.PublishDiagnosticsParams(
textDocument,
targetId,
List.empty[Diagnostic].asJava,
true
)
)
diagnosticMap(textDocument)
}

// Update the published diagnostics for the given text file by
// adding the recently computed diagnostic to the list of
// all previous diagnostics generated for the same file.
private[this] def appendDiagnostics(
textDocument: TextDocumentIdentifier,
currentDiagnostic: Diagnostic
): List[Diagnostic] = {
ensureDiagnostics(textDocument).getDiagnostics.asScala.toList ++ List(currentDiagnostic)
diagnostics.add(textDocument, diagnostic)
val diagnosticList = new java.util.LinkedList[Diagnostic]()
diagnosticList.add(diagnostic)
sendBuildPublishDiagnostics(textDocument, diagnosticList, reset = false)
}

// Computes the diagnostic related to the given Problem
private[this] def getSingleDiagnostic(problem: Problem): Diagnostic = {
private def toDiagnostic(problem: Problem): Diagnostic = {
// Zinc's range starts at 1 whereas BSP at 0
def correctLine = (_: Int) - 1

Expand All @@ -142,52 +96,58 @@ private class BspCompileProblemReporter(
diagnostic
}

private def sendBuildPublishDiagnostics(
textDocument: TextDocumentIdentifier,
diagnosticList: java.util.List[Diagnostic],
reset: Boolean
) = {
val params = new bsp.PublishDiagnosticsParams(
textDocument,
targetId,
diagnosticList,
reset
)
compilationOriginId.foreach(params.setOriginId(_))
client.onBuildPublishDiagnostics(params)
}

override def logWarning(problem: Problem): Unit = {
client.onBuildPublishDiagnostics(getDiagnostics(problem, targetId, compilationOriginId))
warnings.incrementAndGet()
reportProblem(problem)
warnings += 1
}

override def fileVisited(file: Path): Unit = {
override def fileVisited(file: os.Path): Unit = {
val uri = file.toNIO.toUri.toString
client.onBuildPublishDiagnostics(ensureDiagnostics(new TextDocumentIdentifier(uri)))
val textDocument = new TextDocumentIdentifier(uri)
sendBuildPublishDiagnostics(textDocument, diagnostics.getAll(textDocument), reset = true)
}

override def printSummary(): Unit = {
finish()
}

// Compute the compilation status code
private[this] def getStatusCode: StatusCode = {
if (errors.get > 0) StatusCode.ERROR else StatusCode.OK
}

override def start(): Unit = {
if (!started.get()) {
val taskStartParams = new TaskStartParams(taskId)
taskStartParams.setEventTime(System.currentTimeMillis())
taskStartParams.setData(compileTask)
taskStartParams.setDataKind(TaskStartDataKind.COMPILE_TASK)
taskStartParams.setMessage(s"Compiling target ${targetDisplayName}")
client.onBuildTaskStart(taskStartParams)
started.set(true)
}
val taskStartParams = new TaskStartParams(taskId)
taskStartParams.setEventTime(System.currentTimeMillis())
taskStartParams.setData(new CompileTask(targetId))
taskStartParams.setDataKind(TaskStartDataKind.COMPILE_TASK)
taskStartParams.setMessage(s"Compiling target ${targetDisplayName}")
client.onBuildTaskStart(taskStartParams)
}

override def finish(): Unit = {
if (!finished.get()) {
val taskFinishParams = new TaskFinishParams(taskId, getStatusCode)
taskFinishParams.setEventTime(System.currentTimeMillis())
taskFinishParams.setMessage(s"Compiled ${targetDisplayName}")
taskFinishParams.setDataKind(TaskFinishDataKind.COMPILE_REPORT)
val compileReport = new CompileReport(targetId, errors.get, warnings.get)
compilationOriginId match {
case Some(id) => compileReport.setOriginId(id)
case None =>
}
taskFinishParams.setData(compileReport)
client.onBuildTaskFinish(taskFinishParams)
finished.set(true)
val taskFinishParams =
new TaskFinishParams(taskId, if (errors > 0) StatusCode.ERROR else StatusCode.OK)
taskFinishParams.setEventTime(System.currentTimeMillis())
taskFinishParams.setMessage(s"Compiled ${targetDisplayName}")
taskFinishParams.setDataKind(TaskFinishDataKind.COMPILE_REPORT)
val compileReport = new CompileReport(targetId, errors, warnings)
compilationOriginId match {
case Some(id) => compileReport.setOriginId(id)
case None =>
}
taskFinishParams.setData(compileReport)
client.onBuildTaskFinish(taskFinishParams)
}

}
2 changes: 1 addition & 1 deletion scalalib/src/mill/scalalib/ScalaModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase { outer =>
scalacOptions = scalacOptions,
compilerClasspath = scalaCompilerClasspath(),
scalacPluginClasspath = semanticDbPluginClasspath(),
reporter = T.reporter.apply(hashCode),
reporter = None,
reportCachedProblems = zincReportCachedProblems()
)
.map(r =>
Expand Down
2 changes: 1 addition & 1 deletion scalalib/src/mill/scalalib/SemanticDbJavaModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ trait SemanticDbJavaModule extends CoursierModule {
compileClasspath =
(compileClasspath() ++ resolvedSemanticDbJavaPluginIvyDeps()).map(_.path),
javacOptions = javacOpts,
reporter = T.reporter.apply(hashCode()),
reporter = None,
reportCachedProblems = zincReportCachedProblems()
).map(r =>
SemanticDbJavaModule.copySemanticdbFiles(r.classes.path, T.workspace, T.dest / "data")
Expand Down