Skip to content

Commit 01f9e7e

Browse files
adpi2tgodzik
authored andcommitted
refactor and make it work
1 parent 0ece9e5 commit 01f9e7e

15 files changed

+303
-246
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package bloop
2+
3+
import java.io.File
4+
import java.util.concurrent.atomic.AtomicReference
5+
6+
import scala.jdk.CollectionConverters._
7+
8+
import bloop.io.AbsolutePath
9+
import bloop.task.Task
10+
11+
import monix.reactive.Observable
12+
import monix.reactive.subjects.PublishSubject
13+
import sbt.internal.inc.PlainVirtualFileConverter
14+
import xsbti.VirtualFileRef
15+
import xsbti.compile.CompileAnalysis
16+
import xsbti.compile.analysis.Stamp
17+
18+
/**
19+
* Each time a new compile analysis is produced for a given client, it is given to
20+
* the [[ClientClassObserver]] which computes the list of classes that changed or got created.
21+
*
22+
* A client can subscribe to the observer to get notified of classes to update.
23+
* It is used by DAP to hot reload classes in the debuggee process.
24+
*
25+
* @param clientClassesDir the class directory for the client
26+
*/
27+
private[bloop] class ClientClassesObserver(val classesDir: AbsolutePath) {
28+
private val converter = PlainVirtualFileConverter.converter
29+
private val previousAnalysis: AtomicReference[CompileAnalysis] = new AtomicReference()
30+
private val classesSubject: PublishSubject[Seq[String]] = PublishSubject()
31+
32+
def observable: Observable[Seq[String]] = classesSubject
33+
34+
def nextAnalysis(analysis: CompileAnalysis): Task[Unit] = {
35+
val prev = previousAnalysis.getAndSet(analysis)
36+
if (prev != null && classesSubject.size > 0) {
37+
Task {
38+
val previousStamps = prev.readStamps.getAllProductStamps
39+
analysis.readStamps.getAllProductStamps.asScala.iterator.collect {
40+
case (vf, stamp) if isClassFile(vf) && isNewer(stamp, previousStamps.get(vf)) =>
41+
getFullyQualifiedClassName(vf)
42+
}.toSeq
43+
}
44+
.flatMap { classesToUpdate =>
45+
Task.fromFuture(classesSubject.onNext(classesToUpdate)).map(_ => ())
46+
}
47+
} else Task.unit
48+
}
49+
50+
private def isClassFile(vf: VirtualFileRef): Boolean = vf.id.endsWith(".class")
51+
52+
private def isNewer(current: Stamp, previous: Stamp): Boolean =
53+
previous == null || {
54+
val currentHash = current.getHash
55+
val previousHash = previous.getHash
56+
currentHash.isPresent &&
57+
(!previousHash.isPresent || currentHash.get != previousHash.get)
58+
}
59+
60+
private def getFullyQualifiedClassName(vf: VirtualFileRef): String = {
61+
val path = converter.toPath(vf)
62+
val relativePath = classesDir.underlying.relativize(path)
63+
relativePath.toString.replace(File.separator, ".").stripSuffix(".class")
64+
}
65+
}

backend/src/main/scala/bloop/CompileBackgroundTasks.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import bloop.tracing.BraveTracer
88

99
abstract class CompileBackgroundTasks {
1010
def trigger(
11-
clientClassesDir: AbsolutePath,
11+
clientClassesObserver: ClientClassesObserver,
1212
clientReporter: Reporter,
1313
clientTracer: BraveTracer,
1414
clientLogger: Logger
@@ -20,7 +20,7 @@ object CompileBackgroundTasks {
2020
val empty: CompileBackgroundTasks = {
2121
new CompileBackgroundTasks {
2222
def trigger(
23-
clientClassesDir: AbsolutePath,
23+
clientClassesObserver: ClientClassesObserver,
2424
clientReporter: Reporter,
2525
clientTracer: BraveTracer,
2626
clientLogger: Logger

backend/src/main/scala/bloop/Compiler.scala

+42-21
Original file line numberDiff line numberDiff line change
@@ -452,11 +452,12 @@ object Compiler {
452452

453453
val backgroundTasks = new CompileBackgroundTasks {
454454
def trigger(
455-
clientClassesDir: AbsolutePath,
455+
clientClassesObserver: ClientClassesObserver,
456456
clientReporter: Reporter,
457457
clientTracer: BraveTracer,
458458
clientLogger: Logger
459459
): Task[Unit] = Task.defer {
460+
val clientClassesDir = clientClassesObserver.classesDir
460461
clientLogger.debug(s"Triggering background tasks for $clientClassesDir")
461462
val updateClientState =
462463
updateExternalClassesDirWithReadOnly(clientClassesDir, clientTracer, clientLogger)
@@ -472,10 +473,20 @@ object Compiler {
472473
}
473474

474475
val deleteNewClassesDir = Task(BloopPaths.delete(AbsolutePath(newClassesDir)))
475-
val allTasks = List(deleteNewClassesDir, updateClientState, writeAnalysisIfMissing)
476+
val publishClientAnalysis = Task {
477+
rebaseAnalysisClassFiles(
478+
analysis,
479+
readOnlyClassesDir,
480+
clientClassesDir.underlying,
481+
sourcesWithFatal
482+
)
483+
}
484+
.flatMap(clientClassesObserver.nextAnalysis)
476485
Task
477-
.gatherUnordered(allTasks)
478-
.map(_ => ())
486+
.gatherUnordered(
487+
List(deleteNewClassesDir, updateClientState, writeAnalysisIfMissing)
488+
)
489+
.flatMap(_ => publishClientAnalysis)
479490
.onErrorHandleWith(err => {
480491
clientLogger.debug("Caught error in background tasks"); clientLogger.trace(err);
481492
Task.raiseError(err)
@@ -495,14 +506,12 @@ object Compiler {
495506
)
496507
} else {
497508
val allGeneratedProducts = allGeneratedRelativeClassFilePaths.toMap
498-
val analysisForFutureCompilationRuns = {
499-
rebaseAnalysisClassFiles(
500-
analysis,
501-
readOnlyClassesDir,
502-
newClassesDir,
503-
sourcesWithFatal
504-
)
505-
}
509+
val analysisForFutureCompilationRuns = rebaseAnalysisClassFiles(
510+
analysis,
511+
readOnlyClassesDir,
512+
newClassesDir,
513+
sourcesWithFatal
514+
)
506515

507516
val resultForFutureCompilationRuns = {
508517
resultForDependentCompilationsInSameRun.withAnalysis(
@@ -517,12 +526,12 @@ object Compiler {
517526
// Schedule the tasks to run concurrently after the compilation end
518527
val backgroundTasksExecution = new CompileBackgroundTasks {
519528
def trigger(
520-
clientClassesDir: AbsolutePath,
529+
clientClassesObserver: ClientClassesObserver,
521530
clientReporter: Reporter,
522531
clientTracer: BraveTracer,
523532
clientLogger: Logger
524533
): Task[Unit] = {
525-
val clientClassesDirPath = clientClassesDir.toString
534+
val clientClassesDir = clientClassesObserver.classesDir
526535
val successBackgroundTasks =
527536
backgroundTasksWhenNewSuccessfulAnalysis
528537
.map(f => f(clientClassesDir, clientReporter, clientTracer))
@@ -543,15 +552,26 @@ object Compiler {
543552
val syntax = path.syntax
544553
if (syntax.startsWith(readOnlyClassesDirPath)) {
545554
val rebasedFile = AbsolutePath(
546-
syntax.replace(readOnlyClassesDirPath, clientClassesDirPath)
555+
syntax.replace(readOnlyClassesDirPath, clientClassesDir.toString)
547556
)
548557
if (rebasedFile.exists) {
549558
Files.delete(rebasedFile.underlying)
550559
}
551560
}
552561
}
553562
}
554-
Task.gatherUnordered(List(firstTask, secondTask)).map(_ => ())
563+
564+
val publishClientAnalysis = Task {
565+
rebaseAnalysisClassFiles(
566+
analysis,
567+
newClassesDir,
568+
clientClassesDir.underlying,
569+
sourcesWithFatal
570+
)
571+
}.flatMap(clientClassesObserver.nextAnalysis)
572+
Task
573+
.gatherUnordered(List(firstTask, secondTask))
574+
.flatMap(_ => publishClientAnalysis)
555575
}
556576

557577
allClientSyncTasks.doOnFinish(_ => Task(clientReporter.reportEndCompilation()))
@@ -691,11 +711,12 @@ object Compiler {
691711
): CompileBackgroundTasks = {
692712
new CompileBackgroundTasks {
693713
def trigger(
694-
clientClassesDir: AbsolutePath,
714+
clientClassesObserver: ClientClassesObserver,
695715
clientReporter: Reporter,
696716
tracer: BraveTracer,
697717
clientLogger: Logger
698718
): Task[Unit] = {
719+
val clientClassesDir = clientClassesObserver.classesDir
699720
val backgroundTasks = tasks.map(f => f(clientClassesDir, clientReporter, tracer))
700721
Task.gatherUnordered(backgroundTasks).memoize.map(_ => ())
701722
}
@@ -783,19 +804,19 @@ object Compiler {
783804
*/
784805
def rebaseAnalysisClassFiles(
785806
analysis0: CompileAnalysis,
786-
readOnlyClassesDir: Path,
787-
newClassesDir: Path,
807+
origin: Path,
808+
target: Path,
788809
sourceFilesWithFatalWarnings: scala.collection.Set[File]
789810
): Analysis = {
790811
// Cast to the only internal analysis that we support
791812
val analysis = analysis0.asInstanceOf[Analysis]
792813
def rebase(file: VirtualFileRef): VirtualFileRef = {
793814

794815
val filePath = converter.toPath(file).toAbsolutePath()
795-
if (!filePath.startsWith(readOnlyClassesDir)) file
816+
if (!filePath.startsWith(origin)) file
796817
else {
797818
// Hash for class file is the same because the copy duplicates metadata
798-
val path = newClassesDir.resolve(readOnlyClassesDir.relativize(filePath))
819+
val path = target.resolve(origin.relativize(filePath))
799820
converter.toVirtualFile(path)
800821
}
801822
}

build.sc

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ object Dependencies {
6464
def pprint = ivy"com.lihaoyi::pprint:0.8.1"
6565
def sbtTestAgent = ivy"org.scala-sbt:test-agent:1.9.8"
6666
def sbtTestInterface = ivy"org.scala-sbt:test-interface:1.0"
67-
def scalaDebugAdapter = ivy"ch.epfl.scala::scala-debug-adapter:4.0.0"
67+
def scalaDebugAdapter = ivy"ch.epfl.scala::scala-debug-adapter:4.0.1"
6868
def scalaJsLinker1 = ivy"org.scala-js::scalajs-linker:$scalaJs1Version"
6969
def scalaJsEnvs1 = ivy"org.scala-js::scalajs-js-envs:$scalaJsEnvsVersion"
7070
def scalaJsEnvNode1 = ivy"org.scala-js::scalajs-env-nodejs:$scalaJsEnvsVersion"

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -603,10 +603,7 @@ final class BloopBspServices(
603603
params: bsp.DebugSessionParams
604604
): BspEndpointResponse[bsp.DebugSessionAddress] = {
605605

606-
def inferDebuggee(
607-
projects: Seq[Project],
608-
state: State
609-
): BspResponse[Debuggee] = {
606+
def inferDebuggee(projects: Seq[Project], state: State): BspResponse[Debuggee] = {
610607
def convert[A: JsonValueCodec](
611608
f: A => Either[String, Debuggee]
612609
): Either[Response.Error, Debuggee] = {

frontend/src/main/scala/bloop/dap/BloopDebugToolsResolver.scala

+6-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,12 @@ class BloopDebugToolsResolver(logger: Logger) extends DebugToolsResolver {
3434
}
3535
}
3636

37-
override def resolveUnpickler(scalaVersion: ScalaVersion): Try[ClassLoader] = {
38-
getOrTryUpdate(stepFilterCache, scalaVersion) {
39-
val unpicklerModule = s"${BuildInfo.unpicklerName}_${scalaVersion.binaryVersion}"
40-
val stepFilter = Artifact(BuildInfo.organization, unpicklerModule, BuildInfo.version)
41-
val tastyCore = Artifact("org.scala-lang", "tasty-core_3", scalaVersion.value)
37+
override def resolveDecoder(scalaVersion: ScalaVersion): Try[ClassLoader] = {
38+
getOrTryUpdate(decoderCache, scalaVersion) {
39+
val decoderModule = s"${BuildInfo.decoderName}_${scalaVersion.binaryVersion}"
40+
val artifact = Artifact(BuildInfo.organization, decoderModule, BuildInfo.version)
4241
DependencyResolution
43-
.resolveWithErrors(List(stepFilter, tastyCore), logger)
42+
.resolveWithErrors(List(artifact), logger)
4443
.map(jars => toClassLoader(jars, true))
4544
.toTry
4645
}
@@ -66,5 +65,5 @@ class BloopDebugToolsResolver(logger: Logger) extends DebugToolsResolver {
6665

6766
object BloopDebugToolsResolver {
6867
private val expressionCompilerCache: mutable.Map[ScalaVersion, ClassLoader] = mutable.Map.empty
69-
private val stepFilterCache: mutable.Map[ScalaVersion, ClassLoader] = mutable.Map.empty
68+
private val decoderCache: mutable.Map[ScalaVersion, ClassLoader] = mutable.Map.empty
7069
}

0 commit comments

Comments
 (0)