@@ -41,6 +41,9 @@ import bloop.data.ClientInfo
4141import bloop .data .ClientInfo .BspClientInfo
4242import bloop .data .JdkConfig
4343import bloop .data .Platform
44+ import bloop .data .Platform .Js
45+ import bloop .data .Platform .Jvm
46+ import bloop .data .Platform .Native
4447import bloop .data .Project
4548import bloop .data .WorkspaceSettings
4649import bloop .engine .Aggregate
@@ -58,6 +61,7 @@ import bloop.engine.tasks.toolchains.ScalaNativeToolchain
5861import bloop .exec .Forker
5962import bloop .internal .build .BuildInfo
6063import bloop .io .AbsolutePath
64+ import bloop .io .ByteHasher
6165import bloop .io .Environment .lineSeparator
6266import bloop .io .RelativePath
6367import bloop .logging .BspServerLogger
@@ -70,22 +74,21 @@ import bloop.reporter.ReporterInputs
7074import bloop .task .Task
7175import bloop .testing .LoggingEventHandler
7276import bloop .testing .TestInternals
77+ import bloop .util .JavaCompat ._
7378import bloop .util .JavaRuntime
7479
80+ import com .github .plokhotnyuk .jsoniter_scala .core .JsonValueCodec
7581import com .github .plokhotnyuk .jsoniter_scala .core ._
82+ import com .github .plokhotnyuk .jsoniter_scala .core .readFromArray
7683import com .github .plokhotnyuk .jsoniter_scala .macros .JsonCodecMaker
77- import com .github .plokhotnyuk .jsoniter_scala .core .JsonValueCodec
7884import jsonrpc4s ._
79- import com .github .plokhotnyuk .jsoniter_scala .core .readFromArray
8085import monix .execution .Cancelable
8186import monix .execution .CancelablePromise
8287import monix .execution .Scheduler
8388import monix .execution .atomic .AtomicBoolean
8489import monix .execution .atomic .AtomicInt
8590import monix .reactive .subjects .BehaviorSubject
86- import bloop .data .Platform .Js
87- import bloop .data .Platform .Jvm
88- import bloop .data .Platform .Native
91+ import sbt .internal .inc .PlainVirtualFileConverter
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,138 @@ 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+ val debugInfos = projects.map {
441+ case (target, project) =>
442+ collectDebugInfo(target, project, state)
443+ }
444+ Task .sequence(debugInfos).map { debugInfos =>
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+ ): Task [BloopBspDefinitions .IncrementalCompilationDebugInfo ] = {
462+ val allSources = bloop.io.SourceHasher
463+ .findAndHashSourcesInProject(
464+ project,
465+ _ => Task .now(Nil ),
466+ 20 ,
467+ Promise [Unit ](),
468+ ioScheduler,
469+ state.logger
470+ )
471+ .map(res => res.map(_.sortBy(_.source.id())))
472+ .executeOn(ioScheduler)
473+ allSources.map { allSources =>
474+ import bloop .bsp .BloopBspDefinitions ._
475+ import java .nio .file .Files
476+
477+ val projectAnalysisFile = state.client
478+ .getUniqueClassesDirFor(project, forceGeneration = false )
479+ .resolve(s " ../../ ${project.name}-analysis.bin " )
480+
481+ val converter = PlainVirtualFileConverter .converter
482+ // Extract analysis info from successful compilation results
483+ val analysisInfo = state.results.lastSuccessfulResult(project) match {
484+ case Some (success) =>
485+ val maybeAnalysis = success.previous.analysis()
486+ val analysis = maybeAnalysis.toOption match {
487+ case Some (analysis : sbt.internal.inc.Analysis ) => analysis
488+ case _ => sbt.internal.inc.Analysis .empty
489+ }
490+ val compilationInfo = analysis
491+ .readCompilations()
492+ .getAllCompilations
493+ .toList
494+ .map { compilation =>
495+ s " ${compilation.getStartTime} -> ${compilation.getOutput()}"
496+ }
497+ .mkString(" \n " )
498+
499+ val relations = analysis.relations
500+ val lastModifiedA =
501+ if (Files .exists(projectAnalysisFile.underlying))
502+ Files .getLastModifiedTime(projectAnalysisFile.underlying).toMillis()
503+ else 0L
504+ val changedSource = allSources match {
505+ case Left (_) => Nil
506+ case Right (value) =>
507+ value.filterNot { sourceHash =>
508+ success.sources.exists(_.source.id() == sourceHash.source.id())
509+ }
510+ }
511+ val hashes = success.sources.map { sourceHash =>
512+ val sourcePath = converter.toPath(sourceHash.source)
513+ val exists = Files .exists(sourcePath)
514+ val lastModified = if (exists) sourcePath.toFile.lastModified() else 0L
515+ val currentHash =
516+ if (exists) ByteHasher .hashFileContents(sourcePath.toFile)
517+ else 0
518+
519+ FileHashInfo (
520+ uri = bsp.Uri (sourcePath.toUri()),
521+ currentHash = currentHash,
522+ analysisHash = Some (sourceHash.hash),
523+ lastModified = lastModified,
524+ exists = exists
525+ )
526+ }
527+ val currentFailedResult = state.results.latestResult(project) match {
528+ case _ : Compiler .Result .Success => " "
529+ case otherwise => otherwise.toString()
530+ }
531+
532+ val analysisInfo =
533+ AnalysisDebugInfo (
534+ lastModified = lastModifiedA,
535+ sourceFiles = analysis.readStamps.getAllSourceStamps.size(),
536+ classFiles = analysis.readStamps.getAllProductStamps.size(),
537+ internalDependencies = relations.allProducts.size,
538+ externalDependencies = relations.allLibraryDeps.size,
539+ location = bsp.Uri (projectAnalysisFile.toBspUri),
540+ excludedFiles = changedSource.map(_.source.toString())
541+ )
542+ Some (
543+ IncrementalCompilationDebugInfo (
544+ target = target,
545+ analysisInfo = Some (analysisInfo),
546+ allFileHashes = hashes.toList,
547+ lastCompilationInfo = compilationInfo,
548+ maybeFailedCompilation = currentFailedResult
549+ )
550+ )
551+ case _ => None
552+ }
553+ analysisInfo.getOrElse(
554+ IncrementalCompilationDebugInfo (
555+ target = target,
556+ analysisInfo = None ,
557+ allFileHashes = Nil ,
558+ lastCompilationInfo = " " ,
559+ maybeFailedCompilation = " "
560+ )
561+ )
562+ }
563+ }
564+
427565 def linkProjects (
428566 userProjects : Seq [ProjectMapping ],
429567 state : State ,
0 commit comments