Skip to content

Commit 540469a

Browse files
committed
improvement: Add a debugging endpoint
1 parent 5457f2b commit 540469a

File tree

5 files changed

+224
-4
lines changed

5 files changed

+224
-4
lines changed

backend/src/main/scala/sbt/internal/inc/bloop/BloopZincCompiler.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import sbt.internal.inc.bloop.internal.BloopIncremental
2424
import sbt.internal.inc.bloop.internal.BloopLookup
2525
import sbt.internal.inc.bloop.internal.BloopStamps
2626
import sbt.util.InterfaceUtil
27-
import xsbti.AnalysisCallback
2827
import xsbti.VirtualFile
2928
import xsbti.compile._
3029

@@ -137,8 +136,7 @@ object BloopZincCompiler {
137136
val lookup = new BloopLookup(config, previousSetup, logger)
138137
val analysis = invalidateAnalysisFromSetup(config.currentSetup, previousSetup, setOfSources, prev, manager, logger)
139138

140-
// Scala needs the explicit type signature to infer the function type arguments
141-
val compile: (Set[VirtualFile], DependencyChanges, AnalysisCallback, ClassFileManager) => Task[Unit] = compiler.compile(_, _, _, _, cancelPromise)
139+
val compile = compiler.compile(_, _, _, _, cancelPromise)
142140
BloopIncremental
143141
.compile(
144142
setOfSources,

frontend/src/main/scala/bloop/bsp/BloopBspDefinitions.scala

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package bloop.bsp
22

3+
import ch.epfl.scala.bsp.BuildTargetIdentifier
34
import ch.epfl.scala.bsp.Uri
45

56
import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
@@ -41,4 +42,70 @@ object BloopBspDefinitions {
4142
StopClientCachingParams.codec,
4243
Endpoint.unitCodec
4344
)
45+
46+
// Incremental compilation debugging endpoint definitions
47+
final case class DebugIncrementalCompilationParams(
48+
targets: List[BuildTargetIdentifier]
49+
)
50+
51+
object DebugIncrementalCompilationParams {
52+
implicit val codec: JsonValueCodec[DebugIncrementalCompilationParams] =
53+
JsonCodecMaker.makeWithRequiredCollectionFields
54+
}
55+
56+
final case class IncrementalCompilationDebugInfo(
57+
target: BuildTargetIdentifier,
58+
analysisInfo: Option[AnalysisDebugInfo],
59+
allFileHashes: List[FileHashInfo],
60+
lastCompilationInfo: String
61+
)
62+
63+
object IncrementalCompilationDebugInfo {
64+
implicit val codec: JsonValueCodec[IncrementalCompilationDebugInfo] =
65+
JsonCodecMaker.makeWithRequiredCollectionFields
66+
}
67+
68+
final case class AnalysisDebugInfo(
69+
lastModified: Long,
70+
sourceFiles: Int,
71+
classFiles: Int,
72+
internalDependencies: Int,
73+
externalDependencies: Int,
74+
location: Uri
75+
)
76+
77+
object AnalysisDebugInfo {
78+
implicit val codec: JsonValueCodec[AnalysisDebugInfo] =
79+
JsonCodecMaker.makeWithRequiredCollectionFields
80+
}
81+
82+
final case class FileHashInfo(
83+
uri: Uri,
84+
currentHash: Int,
85+
analysisHash: Option[Int],
86+
lastModified: Long,
87+
exists: Boolean
88+
)
89+
90+
object FileHashInfo {
91+
implicit val codec: JsonValueCodec[FileHashInfo] =
92+
JsonCodecMaker.makeWithRequiredCollectionFields
93+
}
94+
95+
final case class DebugIncrementalCompilationResult(
96+
debugInfos: List[IncrementalCompilationDebugInfo]
97+
)
98+
99+
object DebugIncrementalCompilationResult {
100+
implicit val codec: JsonValueCodec[DebugIncrementalCompilationResult] =
101+
JsonCodecMaker.makeWithRequiredCollectionFields
102+
}
103+
104+
object debugIncrementalCompilation
105+
extends Endpoint[DebugIncrementalCompilationParams, DebugIncrementalCompilationResult](
106+
"bloop/debugIncrementalCompilation"
107+
)(
108+
DebugIncrementalCompilationParams.codec,
109+
JsonCodecMaker.makeWithRequiredCollectionFields
110+
)
44111
}

frontend/src/main/scala/bloop/bsp/BloopBspServices.scala

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import bloop.reporter.BspProjectReporter
6767
import bloop.reporter.ProblemPerPhase
6868
import bloop.reporter.ReporterConfig
6969
import bloop.reporter.ReporterInputs
70+
import bloop.util.JavaCompat._
7071
import bloop.task.Task
7172
import bloop.testing.LoggingEventHandler
7273
import bloop.testing.TestInternals
@@ -86,6 +87,8 @@ import monix.reactive.subjects.BehaviorSubject
8687
import bloop.data.Platform.Js
8788
import bloop.data.Platform.Jvm
8889
import bloop.data.Platform.Native
90+
import sbt.internal.inc.PlainVirtualFileConverter
91+
import bloop.io.ByteHasher
8992

9093
final 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,

frontend/src/test/scala/bloop/bsp/BspBaseSuite.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,18 @@ abstract class BspBaseSuite extends BaseSuite with BspClientTest {
518518
awaitForTask(jvmEnvironmentTask)
519519
}
520520

521+
def debugIncrementalCompilation(
522+
project: TestProject
523+
): (ManagedBspTestState, BloopBspDefinitions.DebugIncrementalCompilationResult) = {
524+
val debugTask = runAfterTargets(project) { target =>
525+
rpcRequest(
526+
BloopBspDefinitions.debugIncrementalCompilation,
527+
BloopBspDefinitions.DebugIncrementalCompilationParams(List(target))
528+
)
529+
}
530+
await(debugTask)
531+
}
532+
521533
def jvmTestEnvironment(
522534
project: TestProject,
523535
originId: Option[String]

frontend/src/test/scala/bloop/bsp/BspTestSpec.scala

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import bloop.util.TestUtil
1616
import com.github.plokhotnyuk.jsoniter_scala.core.writeToArray
1717
import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker
1818
import jsonrpc4s.RawJson
19-
import scalaz.std.java.time
2019

2120
object TcpBspTestSpec extends BspTestSpec(BspProtocol.Tcp)
2221
object LocalBspTestSpec extends BspTestSpec(BspProtocol.Local)
@@ -394,4 +393,38 @@ class BspTestSpec(override val protocol: BspProtocol) extends BspBaseSuite {
394393
}
395394
}
396395
}
396+
397+
test("debugIncrementalCompilation endpoint succeeds") {
398+
TestUtil.withinWorkspace { workspace =>
399+
val sources = List(
400+
"""/Foo.scala
401+
|object Foo {
402+
| def main(args: Array[String]): Unit = {
403+
| println("Hello World")
404+
| }
405+
|}
406+
""".stripMargin
407+
)
408+
409+
val logger = new RecordingLogger(ansiCodesSupported = false)
410+
val A = TestProject(workspace, "a", sources)
411+
412+
loadBspState(workspace, List(A), logger) { state =>
413+
// First compile the project to generate incremental compilation data
414+
val compiled = state.compile(A)
415+
assertExitStatus(compiled, ExitStatus.Ok)
416+
417+
val (_, debugInfo) = compiled.debugIncrementalCompilation(A)
418+
419+
val debugInformation = debugInfo.debugInfos.head
420+
// Verify we get debug information
421+
assert(debugInformation.target == A.bspId)
422+
assert(debugInformation.lastCompilationInfo.nonEmpty)
423+
assert(debugInformation.analysisInfo.isDefined)
424+
assert(debugInformation.analysisInfo.get.classFiles == 2)
425+
assert(debugInformation.analysisInfo.get.sourceFiles == 1)
426+
assert(debugInformation.allFileHashes.size == 1)
427+
}
428+
}
429+
}
397430
}

0 commit comments

Comments
 (0)