Skip to content

Commit

Permalink
Re-publish warnings on BSP server startup
Browse files Browse the repository at this point in the history
Fixes #726
  • Loading branch information
jvican committed Nov 21, 2018
1 parent a222261 commit 8aaf828
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 130 deletions.
114 changes: 88 additions & 26 deletions frontend/src/main/scala/bloop/bsp/BloopBspServices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ package bloop.bsp

import java.io.InputStream
import java.nio.file.{FileSystems, PathMatcher}
import java.util.concurrent.TimeUnit
import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
import java.util.concurrent.atomic.AtomicInteger

import bloop.{Compiler, ScalaInstance}
import bloop.cli.{Commands, ExitStatus}
import bloop.{CompileMode, Compiler, ScalaInstance}
import bloop.cli.{BloopReporter, Commands, ExitStatus, ReporterKind}
import bloop.data.{Platform, Project}
import bloop.engine.tasks.Tasks
import bloop.engine.tasks.{CompilationTask, Tasks}
import bloop.engine.tasks.toolchains.{ScalaJsToolchain, ScalaNativeToolchain}
import bloop.engine._
import bloop.internal.build.BuildInfo
import bloop.io.{AbsolutePath, RelativePath}
import bloop.logging.{BspServerLogger, DebugFilter}
import bloop.reporter.{BspProjectReporter, Reporter, ReporterConfig}
import bloop.testing.{BspLoggingEventHandler, TestInternals}
import monix.eval.Task
import ch.epfl.scala.bsp.{BuildTargetIdentifier, endpoints}
Expand All @@ -24,6 +25,7 @@ import ch.epfl.scala.bsp
import ch.epfl.scala.bsp.ScalaBuildTarget.encodeScalaBuildTarget
import monix.execution.atomic.AtomicInt

import scala.collection.concurrent.TrieMap
import scala.concurrent.duration.FiniteDuration

final class BloopBspServices(
Expand All @@ -48,9 +50,9 @@ final class BloopBspServices(

// Disable ansi codes for now so that the BSP clients don't get unescaped color codes
private val taskIdCounter: AtomicInt = AtomicInt(0)
private val bspForwarderLogger = BspServerLogger(callSiteState, client, taskIdCounter, false)
private val bspLogger = BspServerLogger(callSiteState, client, taskIdCounter, false)
final val services = JsonRpcServices
.empty(bspForwarderLogger)
.empty(bspLogger)
.requestAsync(endpoints.Build.initialize)(initialize(_))
.notification(endpoints.Build.initialized)(initialized(_))
.request(endpoints.Build.shutdown)(shutdown(_))
Expand All @@ -75,10 +77,10 @@ final class BloopBspServices(
private def reloadState(config: AbsolutePath): Task[State] = {
val pool = currentState.pool
val defaultOpts = currentState.commonOptions
bspForwarderLogger.debug(s"Reloading bsp state for ${config.syntax}")
bspLogger.debug(s"Reloading bsp state for ${config.syntax}")
// TODO(jvican): Modify the in, out and err streams to go through the BSP wire
State.loadActiveStateFor(config, pool, defaultOpts, bspForwarderLogger).map { state0 =>
val newState = state0.copy(logger = bspForwarderLogger)
State.loadActiveStateFor(config, pool, defaultOpts, bspLogger).map { state0 =>
val newState = state0.copy(logger = bspLogger)
currentState = newState
newState
}
Expand All @@ -87,9 +89,9 @@ final class BloopBspServices(
private def saveState(state: State): Task[Unit] = {
Task {
val configDir = state.build.origin
bspForwarderLogger.debug(s"Saving bsp state for ${configDir.syntax}")
bspLogger.debug(s"Saving bsp state for ${configDir.syntax}")
// Spawn the analysis persistence after every save
val persistOut = (msg: String) => bspForwarderLogger.debug(msg)
val persistOut = (msg: String) => bspLogger.debug(msg)
Tasks.persist(state, persistOut).runAsync(ExecutionContext.scheduler)
// Save the state globally so that it can be accessed by other clients
State.stateCache.updateBuild(state)
Expand Down Expand Up @@ -182,31 +184,91 @@ final class BloopBspServices(
}
}

private val compiledTargetsAtLeastOnce =
new TrieMap[bsp.BuildTargetIdentifier, Boolean]()

/**
* Checks whether a project should compile with fresh reporting enabled.
*
* Enabling fresh reporting in a project will allow the bloop server to
* publish diagnostics from previous runs to the client. For an example
* of where this is required, see https://github.com/scalacenter/bloop/issues/726
*
* @param mappings The projects mappings we work with.
* @return The list of projects we want to enable fresh reporting for.
*/
def detectProjectsWithFreshReporting(mappings: Seq[ProjectMapping]): Seq[Project] = {
val filteredMappings = mappings.filter {
case (btid, project) =>
compiledTargetsAtLeastOnce.putIfAbsent(btid, true) match {
case Some(_) => false
case None => true
}
}

filteredMappings.map(_._2)
}

def compileProjects(
projects0: Seq[ProjectMapping],
state: State
): BspResult[bsp.CompileResult] = {
val projects = Dag.reduce(state.build.dags, projects0.map(_._2).toSet)
val action = projects.foldLeft(Exit(ExitStatus.Ok): Action) {
case (action, project) => Run(Commands.Compile(project.name), action)
}

val projectsWithFreshReporting = detectProjectsWithFreshReporting(projects0).toSet
def reportError(p: Project, problems: List[Problem], elapsedMs: Long): String = {
// Don't show warnings in this "final report", we're handling them in the reporter
val count = bloop.reporter.Problem.count(problems)
s"${p.name} [${elapsedMs}ms] (errors ${count.errors})"
}

Interpreter.execute(action, Task.now(state)).map { newState =>
val compiledProjects = state.results.diffLatest(newState.results)
val errorMsgs = compiledProjects.flatMap {
def compile(projects: Set[Project]): Task[State] = {
// TODO(jvican): Improve when https://github.com/scalacenter/bloop/issues/575 is fixdd
val (_, finalTaskState) = projects.foldLeft((false, Task.now(state))) {
case ((deduplicateFailures, taskState), project) =>
val newTaskState = taskState.flatMap { state =>
val reportAllPreviousProblems = projectsWithFreshReporting.contains(project)
val cwd = state.build.origin.getParent
val config = ReporterConfig.defaultFormat.copy(reverseOrder = false)
val createReporter = (project: Project, cwd: AbsolutePath) => {
new BspProjectReporter(
project,
bspLogger,
cwd,
identity,
config,
reportAllPreviousProblems
)
}

val compileTask = CompilationTask.compile(
state,
project,
createReporter,
deduplicateFailures,
CompileMode.Sequential,
false,
false
)

compileTask.map(_.mergeStatus(ExitStatus.Ok))
}

(true, newTaskState)
}

finalTaskState
}

val projects = Dag.reduce(state.build.dags, projects0.map(_._2).toSet)
compile(projects).map { newState =>
val compiledResults = state.results.diffLatest(newState.results)
val errorMsgs = compiledResults.flatMap {
case (p, result) =>
result match {
case Compiler.Result.Empty => Nil
case Compiler.Result.Blocked(_) => Nil
case Compiler.Result.Success(_, _, _) => Nil
case Compiler.Result.GlobalError(problem) => List(problem)
case Compiler.Result.Cancelled(problems, elapsed) => Nil
case Compiler.Result.Cancelled(problems, elapsed) =>
List(reportError(p, problems, elapsed))
case Compiler.Result.Failed(problems, t, elapsed) =>
val acc = List(reportError(p, problems, elapsed))
Expand All @@ -232,7 +294,7 @@ final class BloopBspServices(
mapToProjects(params.targets, state) match {
case Left(error) =>
// Log the mapping error to the user via a log event + an error status code
bspForwarderLogger.error(error)
bspLogger.error(error)
Task.now((state, Right(bsp.CompileResult(None, bsp.StatusCode.Error, None))))
case Right(mappings) => compileProjects(mappings, state)
}
Expand All @@ -255,7 +317,7 @@ final class BloopBspServices(
mapToProjects(params.targets, state) match {
case Left(error) =>
// Log the mapping error to the user via a log event + an error status code
bspForwarderLogger.error(error)
bspLogger.error(error)
Task.now((state, Right(bsp.TestResult(None, bsp.StatusCode.Error, None))))
case Right(mappings) =>
compileProjects(mappings, state).flatMap {
Expand Down Expand Up @@ -323,7 +385,7 @@ final class BloopBspServices(
mapToProject(params.target, state) match {
case Left(error) =>
// Log the mapping error to the user via a log event + an error status code
bspForwarderLogger.error(error)
bspLogger.error(error)
Task.now((state, Right(bsp.RunResult(None, bsp.StatusCode.Error))))
case Right((tid, project)) =>
compileProjects(List((tid, project)), state).flatMap {
Expand Down Expand Up @@ -443,7 +505,7 @@ final class BloopBspServices(
mapToProjects(request.targets, state) match {
case Left(error) =>
// Log the mapping error to the user via a log event + an error status code
bspForwarderLogger.error(error)
bspLogger.error(error)
Task.now((state, Right(bsp.SourcesResult(Nil))))
case Right(mappings) => sources(mappings, state)
}
Expand Down Expand Up @@ -479,7 +541,7 @@ final class BloopBspServices(
mapToProjects(request.targets, state) match {
case Left(error) =>
// Log the mapping error to the user via a log event + an error status code
bspForwarderLogger.error(error)
bspLogger.error(error)
Task.now((state, Right(bsp.DependencySourcesResult(Nil))))
case Right(mappings) => sources(mappings, state)
}
Expand Down Expand Up @@ -511,7 +573,7 @@ final class BloopBspServices(
mapToProjects(request.targets, state) match {
case Left(error) =>
// Log the mapping error to the user via a log event + an error status code
bspForwarderLogger.error(error)
bspLogger.error(error)
// TODO(jvican): Add status code to scalac options result
Task.now((state, Right(bsp.ScalacOptionsResult(Nil))))
case Right(mappings) => scalacOptions(mappings, state)
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/main/scala/bloop/engine/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package bloop.engine

import bloop.CompileMode
import bloop.bsp.BspServer
import bloop.cli.{BspProtocol, Commands, ExitStatus, OptimizerConfig, ReporterKind}
import bloop.cli._
import bloop.cli.CliParsers.CommandsMessages
import bloop.cli.completion.{Case, Mode}
import bloop.io.{AbsolutePath, RelativePath, SourceWatcher}
import bloop.logging.DebugFilter
import bloop.testing.{LoggingEventHandler, TestInternals}
import bloop.engine.tasks.{CompilationTask, LinkTask, Tasks}
import bloop.cli.Commands.{CompilingCommand, LinkingCommand}
import bloop.config.Config
import bloop.cli.Commands.CompilingCommand
import bloop.data.{Platform, Project}
import bloop.engine.Feedback.XMessageString
import bloop.engine.tasks.toolchains.{ScalaJsToolchain, ScalaNativeToolchain}
Expand Down Expand Up @@ -132,10 +131,12 @@ object Interpreter {
val compilerMode: CompileMode.ConfigurableMode = CompileMode.Sequential
val compileTask = state.flatMap { state =>
val config = ReporterKind.toReporterConfig(cmd.reporter)
val createReporter = (project: Project, cwd: AbsolutePath) =>
CompilationTask.toReporter(project, cwd, config, state.logger)
CompilationTask.compile(
state,
project,
config,
createReporter,
deduplicateFailures,
compilerMode,
cmd.pipeline,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import bloop.io.AbsolutePath
import bloop.logging.{DebugFilter, Logger}
import bloop.reporter.LogReporter
import monix.eval.Task
import sbt.internal.inc.FileAnalysisStore
import xsbti.compile.{CompileAnalysis, MiniSetup, PreviousResult}

import scala.concurrent.Await
Expand All @@ -31,7 +32,7 @@ import scala.concurrent.duration.Duration
*/
final class ResultsCache private (
all: Map[Project, Compiler.Result],
successful: Map[Project, PreviousResult]
successful: Map[Project, PreviousResult],
) {

/** Returns the last succesful result if present, empty otherwise. */
Expand Down Expand Up @@ -99,7 +100,6 @@ object ResultsCache {
}

def loadAsync(build: Build, cwd: AbsolutePath, logger: Logger): Task[ResultsCache] = {
import sbt.internal.inc.FileAnalysisStore
import bloop.util.JavaCompat.EnrichOptional

def fetchPreviousResult(p: Project): Task[Compiler.Result] = {
Expand Down
40 changes: 23 additions & 17 deletions frontend/src/main/scala/bloop/engine/tasks/CompilationTask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ object CompilationTask {
*
* @param state The current state of Bloop.
* @param project The project to compile.
* @param reporterConfig Configuration of the compilation messages reporter.
* @param excludeRoot If `true`, compile only the dependencies of `project`. Otherwise,
* also compile `project`.
* @param createReporter A function that creates a per-project compilation reporter.
* @param excludeRoot If `true`, compile only the dependencies of `project`.
* Otherwise, also compile `project`.
* @return The new state of Bloop after compilation.
*/
def compile(
state: State,
project: Project,
reporterConfig: ReporterConfig,
createReporter: (Project, AbsolutePath) => Reporter,
sequentialCompilation: Boolean,
userCompileMode: CompileMode.ConfigurableMode,
pipeline: Boolean,
Expand All @@ -51,7 +51,7 @@ object CompilationTask {
case Right(CompileSourcesAndInstance(sources, instance, javaOnly)) =>
val previousResult = state.results.latestResult(project)
val previousSuccesful = state.results.lastSuccessfulResultOrEmpty(project)
val reporter = createCompilationReporter(project, cwd, reporterConfig, state.logger)
val reporter = createReporter(project, cwd)

val (scalacOptions, compileMode) = {
if (!pipeline) (project.scalacOptions.toArray, userCompileMode)
Expand Down Expand Up @@ -191,23 +191,23 @@ object CompilationTask {

val instances = results.iterator.flatMap(_.bundle.project.scalaInstance.toIterator).toSet
instances.foreach { i =>
// Initialize a compiler so that we can reset the global state after a build compilation
val logger = state.logger
val scalac = state.compilerCache.get(i).scalac().asInstanceOf[AnalyzingCompiler]
val config = ReporterConfig.defaultFormat
val cwd = state.commonOptions.workingPath
val reporter = new LogReporter(logger, cwd, identity, config)
val output = new sbt.internal.inc.ConcreteSingleOutput(tmpDir.toFile)
val cached = scalac.newCachedCompiler(Array.empty[String], output, logger, reporter)
// Reset the global ir caches on the cached compiler only for the store IRs
scalac.resetGlobalIRCaches(mergedStore, cached, logger)
// Initialize a compiler so that we can reset the global state after a build compilation
val logger = state.logger
val scalac = state.compilerCache.get(i).scalac().asInstanceOf[AnalyzingCompiler]
val config = ReporterConfig.defaultFormat
val cwd = state.commonOptions.workingPath
val reporter = new LogReporter(logger, cwd, identity, config)
val output = new sbt.internal.inc.ConcreteSingleOutput(tmpDir.toFile)
val cached = scalac.newCachedCompiler(Array.empty[String], output, logger, reporter)
// Reset the global ir caches on the cached compiler only for the store IRs
scalac.resetGlobalIRCaches(mergedStore, cached, logger)
}
} finally {
Files.delete(tmpDir)
}
}

private def createCompilationReporter(
def toReporter(
project: Project,
cwd: AbsolutePath,
config: ReporterConfig,
Expand All @@ -216,7 +216,13 @@ object CompilationTask {
logger match {
case bspLogger: BspServerLogger =>
// Disable reverse order to show errors as they come for BSP clients
new BspProjectReporter(project, bspLogger, cwd, identity, config.copy(reverseOrder = false))
new BspProjectReporter(
project,
bspLogger,
cwd,
identity,
config.copy(reverseOrder = false),
false)
case _ => new LogReporter(logger, cwd, identity, config)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ final class BspServerLogger private (
()
}

def diagnostic(project: Project, problem: Problem, clear: Boolean): Unit = {
def diagnostic(project: Project, problem: xsbti.Problem, clear: Boolean): Unit = {
import sbt.util.InterfaceUtil.toOption
val message = problem.message
val problemPos = problem.position
Expand Down
Loading

0 comments on commit 8aaf828

Please sign in to comment.