@@ -67,6 +67,7 @@ import bloop.reporter.BspProjectReporter
6767import bloop .reporter .ProblemPerPhase
6868import bloop .reporter .ReporterConfig
6969import bloop .reporter .ReporterInputs
70+ import bloop .util .JavaCompat ._
7071import bloop .task .Task
7172import bloop .testing .LoggingEventHandler
7273import bloop .testing .TestInternals
@@ -86,6 +87,8 @@ import monix.reactive.subjects.BehaviorSubject
8687import bloop .data .Platform .Js
8788import bloop .data .Platform .Jvm
8889import bloop .data .Platform .Native
90+ import sbt .internal .inc .PlainVirtualFileConverter
91+ import bloop .io .ByteHasher
8992
9093final class BloopBspServices (
9194 callSiteState : State ,
@@ -150,6 +153,9 @@ final class BloopBspServices(
150153 .requestAsync(endpoints.BuildTarget .jvmTestEnvironment)(p => schedule(jvmTestEnvironment(p)))
151154 .requestAsync(endpoints.BuildTarget .jvmRunEnvironment)(p => schedule(jvmRunEnvironment(p)))
152155 .notificationAsync(BloopBspDefinitions .stopClientCaching)(p => stopClientCaching(p))
156+ .requestAsync(BloopBspDefinitions .debugIncrementalCompilation)(p =>
157+ schedule(debugIncrementalCompilation(p))
158+ )
153159
154160 // Internal state, initial value defaults to
155161 @ volatile private var currentState : State = callSiteState
@@ -424,6 +430,110 @@ final class BloopBspServices(
424430 Task .eval { originToCompileStores.remove(params.originId); () }.executeAsync
425431 }
426432
433+ def debugIncrementalCompilation (
434+ params : BloopBspDefinitions .DebugIncrementalCompilationParams
435+ ): BspEndpointResponse [BloopBspDefinitions .DebugIncrementalCompilationResult ] = {
436+ def debugInfo (
437+ projects : Seq [ProjectMapping ],
438+ state : State
439+ ): BspResult [BloopBspDefinitions .DebugIncrementalCompilationResult ] = {
440+ Task .now {
441+ val debugInfos = projects.map {
442+ case (target, project) =>
443+ collectDebugInfo(target, project, state)
444+ }
445+ (state, Right (BloopBspDefinitions .DebugIncrementalCompilationResult (debugInfos.toList)))
446+ }
447+ }
448+
449+ ifInitialized(None ) { (state : State , _ : BspServerLogger ) =>
450+ mapToProjects(params.targets, state) match {
451+ case Left (error) => Task .now((state, Left (Response .invalidRequest(error))))
452+ case Right (mappings) => debugInfo(mappings, state)
453+ }
454+ }
455+ }
456+
457+ private def collectDebugInfo (
458+ target : bsp.BuildTargetIdentifier ,
459+ project : Project ,
460+ state : State
461+ ): BloopBspDefinitions .IncrementalCompilationDebugInfo = {
462+ import bloop .bsp .BloopBspDefinitions ._
463+ import java .nio .file .Files
464+
465+ val projectAnalysisFile = state.client
466+ .getUniqueClassesDirFor(project, forceGeneration = false )
467+ .resolve(s " ../../ ${project.name}-analysis.bin " )
468+
469+ val converter = PlainVirtualFileConverter .converter
470+ // Extract analysis info from successful compilation results
471+ val analysisInfo = state.results.lastSuccessfulResult(project) match {
472+ case Some (success) =>
473+ val maybeAnalysis = success.previous.analysis()
474+ val analysis = maybeAnalysis.toOption match {
475+ case Some (analysis : sbt.internal.inc.Analysis ) => analysis
476+ case _ => sbt.internal.inc.Analysis .empty
477+ }
478+ val compilationInfo = analysis
479+ .readCompilations()
480+ .getAllCompilations
481+ .toList
482+ .map { compilation =>
483+ s " ${compilation.getStartTime} -> ${compilation.getOutput()}"
484+ }
485+ .mkString(" \n " )
486+
487+ val relations = analysis.relations
488+ val lastModifiedA =
489+ if (Files .exists(projectAnalysisFile.underlying))
490+ Files .getLastModifiedTime(projectAnalysisFile.underlying).toMillis()
491+ else 0L
492+ val hashes = success.sources.map { sourceHash =>
493+ val sourcePath = converter.toPath(sourceHash.source)
494+ val exists = Files .exists(sourcePath)
495+ val lastModified = if (exists) sourcePath.toFile.lastModified() else 0L
496+ val currentHash =
497+ if (exists) ByteHasher .hashFileContents(sourcePath.toFile)
498+ else 0
499+
500+ FileHashInfo (
501+ uri = bsp.Uri (sourcePath.toUri()),
502+ currentHash = currentHash,
503+ analysisHash = Some (sourceHash.hash),
504+ lastModified = lastModified,
505+ exists = exists
506+ )
507+ }
508+ val analysisInfo =
509+ AnalysisDebugInfo (
510+ lastModified = lastModifiedA,
511+ sourceFiles = analysis.readStamps.getAllSourceStamps.size(),
512+ classFiles = analysis.readStamps.getAllProductStamps.size(),
513+ internalDependencies = relations.allProducts.size,
514+ externalDependencies = relations.allLibraryDeps.size,
515+ location = bsp.Uri (projectAnalysisFile.toBspUri)
516+ )
517+ Some (
518+ IncrementalCompilationDebugInfo (
519+ target = target,
520+ analysisInfo = Some (analysisInfo),
521+ allFileHashes = hashes.toList,
522+ lastCompilationInfo = compilationInfo
523+ )
524+ )
525+ case _ => None
526+ }
527+ analysisInfo.getOrElse(
528+ IncrementalCompilationDebugInfo (
529+ target = target,
530+ analysisInfo = None ,
531+ allFileHashes = Nil ,
532+ lastCompilationInfo = " "
533+ )
534+ )
535+ }
536+
427537 def linkProjects (
428538 userProjects : Seq [ProjectMapping ],
429539 state : State ,
0 commit comments