Skip to content

Commit

Permalink
refactor and make it work
Browse files Browse the repository at this point in the history
  • Loading branch information
adpi2 committed Feb 19, 2024
1 parent f8c5138 commit c80bdcc
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 241 deletions.
65 changes: 65 additions & 0 deletions backend/src/main/scala/bloop/ClientClassesObserver.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package bloop

import java.io.File
import java.util.concurrent.atomic.AtomicReference

import scala.jdk.CollectionConverters._

import bloop.io.AbsolutePath
import bloop.task.Task

import monix.reactive.Observable
import monix.reactive.subjects.PublishSubject
import sbt.internal.inc.PlainVirtualFileConverter
import xsbti.VirtualFileRef
import xsbti.compile.CompileAnalysis
import xsbti.compile.analysis.Stamp

/**
* Each time a new compile analysis is produced for a given client, it is given to
* the [[ClientClassObserver]] which computes the list of classes that changed or got created.
*
* A client can subscribe to the observer to get notified of classes to update.
* It is used by DAP to hot reload classes in the debuggee process.
*
* @param clientClassesDir the class directory for the client
*/
private[bloop] class ClientClassesObserver(val classesDir: AbsolutePath) {
private val converter = PlainVirtualFileConverter.converter
private val previousAnalysis: AtomicReference[CompileAnalysis] = new AtomicReference()
private val classesSubject: PublishSubject[Seq[String]] = PublishSubject()

def observable: Observable[Seq[String]] = classesSubject

def nextAnalysis(analysis: CompileAnalysis): Task[Unit] = {
val prev = previousAnalysis.getAndSet(analysis)
if (prev != null && classesSubject.size > 0) {
Task {
val previousStamps = prev.readStamps.getAllProductStamps
analysis.readStamps.getAllProductStamps.asScala.iterator.collect {
case (vf, stamp) if isClassFile(vf) && isNewer(stamp, previousStamps.get(vf)) =>
getFullyQualifiedClassName(vf)
}.toSeq
}
.flatMap { classesToUpdate =>
Task.fromFuture(classesSubject.onNext(classesToUpdate)).map(_ => ())
}
} else Task.unit
}

private def isClassFile(vf: VirtualFileRef): Boolean = vf.id.endsWith(".class")

private def isNewer(current: Stamp, previous: Stamp): Boolean =
previous == null || {
val currentHash = current.getHash
val previousHash = previous.getHash
currentHash.isPresent &&
(!previousHash.isPresent || currentHash.get != previousHash.get)
}

private def getFullyQualifiedClassName(vf: VirtualFileRef): String = {
val path = converter.toPath(vf)
val relativePath = classesDir.underlying.relativize(path)
relativePath.toString.replace(File.separator, ".").stripSuffix(".class")
}
}
4 changes: 2 additions & 2 deletions backend/src/main/scala/bloop/CompileBackgroundTasks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import bloop.tracing.BraveTracer

abstract class CompileBackgroundTasks {
def trigger(
clientClassesDir: AbsolutePath,
clientClassesObserver: ClientClassesObserver,
clientReporter: Reporter,
clientTracer: BraveTracer,
clientLogger: Logger
Expand All @@ -20,7 +20,7 @@ object CompileBackgroundTasks {
val empty: CompileBackgroundTasks = {
new CompileBackgroundTasks {
def trigger(
clientClassesDir: AbsolutePath,
clientClassesObserver: ClientClassesObserver,
clientReporter: Reporter,
clientTracer: BraveTracer,
clientLogger: Logger
Expand Down
63 changes: 42 additions & 21 deletions backend/src/main/scala/bloop/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -452,11 +452,12 @@ object Compiler {

val backgroundTasks = new CompileBackgroundTasks {
def trigger(
clientClassesDir: AbsolutePath,
clientClassesObserver: ClientClassesObserver,
clientReporter: Reporter,
clientTracer: BraveTracer,
clientLogger: Logger
): Task[Unit] = Task.defer {
val clientClassesDir = clientClassesObserver.classesDir
clientLogger.debug(s"Triggering background tasks for $clientClassesDir")
val updateClientState =
updateExternalClassesDirWithReadOnly(clientClassesDir, clientTracer, clientLogger)
Expand All @@ -472,10 +473,20 @@ object Compiler {
}

val deleteNewClassesDir = Task(BloopPaths.delete(AbsolutePath(newClassesDir)))
val allTasks = List(deleteNewClassesDir, updateClientState, writeAnalysisIfMissing)
val publishClientAnalysis = Task {
rebaseAnalysisClassFiles(
analysis,
readOnlyClassesDir,
clientClassesDir.underlying,
sourcesWithFatal
)
}
.flatMap(clientClassesObserver.nextAnalysis)
Task
.gatherUnordered(allTasks)
.map(_ => ())
.gatherUnordered(
List(deleteNewClassesDir, updateClientState, writeAnalysisIfMissing)
)
.flatMap(_ => publishClientAnalysis)
.onErrorHandleWith(err => {
clientLogger.debug("Caught error in background tasks"); clientLogger.trace(err);
Task.raiseError(err)
Expand All @@ -495,14 +506,12 @@ object Compiler {
)
} else {
val allGeneratedProducts = allGeneratedRelativeClassFilePaths.toMap
val analysisForFutureCompilationRuns = {
rebaseAnalysisClassFiles(
analysis,
readOnlyClassesDir,
newClassesDir,
sourcesWithFatal
)
}
val analysisForFutureCompilationRuns = rebaseAnalysisClassFiles(
analysis,
readOnlyClassesDir,
newClassesDir,
sourcesWithFatal
)

val resultForFutureCompilationRuns = {
resultForDependentCompilationsInSameRun.withAnalysis(
Expand All @@ -517,12 +526,12 @@ object Compiler {
// Schedule the tasks to run concurrently after the compilation end
val backgroundTasksExecution = new CompileBackgroundTasks {
def trigger(
clientClassesDir: AbsolutePath,
clientClassesObserver: ClientClassesObserver,
clientReporter: Reporter,
clientTracer: BraveTracer,
clientLogger: Logger
): Task[Unit] = {
val clientClassesDirPath = clientClassesDir.toString
val clientClassesDir = clientClassesObserver.classesDir
val successBackgroundTasks =
backgroundTasksWhenNewSuccessfulAnalysis
.map(f => f(clientClassesDir, clientReporter, clientTracer))
Expand All @@ -543,15 +552,26 @@ object Compiler {
val syntax = path.syntax
if (syntax.startsWith(readOnlyClassesDirPath)) {
val rebasedFile = AbsolutePath(
syntax.replace(readOnlyClassesDirPath, clientClassesDirPath)
syntax.replace(readOnlyClassesDirPath, clientClassesDir.toString)
)
if (rebasedFile.exists) {
Files.delete(rebasedFile.underlying)
}
}
}
}
Task.gatherUnordered(List(firstTask, secondTask)).map(_ => ())

val publishClientAnalysis = Task {
rebaseAnalysisClassFiles(
analysis,
newClassesDir,
clientClassesDir.underlying,
sourcesWithFatal
)
}.flatMap(clientClassesObserver.nextAnalysis)
Task
.gatherUnordered(List(firstTask, secondTask))
.flatMap(_ => publishClientAnalysis)
}

allClientSyncTasks.doOnFinish(_ => Task(clientReporter.reportEndCompilation()))
Expand Down Expand Up @@ -691,11 +711,12 @@ object Compiler {
): CompileBackgroundTasks = {
new CompileBackgroundTasks {
def trigger(
clientClassesDir: AbsolutePath,
clientClassesObserver: ClientClassesObserver,
clientReporter: Reporter,
tracer: BraveTracer,
clientLogger: Logger
): Task[Unit] = {
val clientClassesDir = clientClassesObserver.classesDir
val backgroundTasks = tasks.map(f => f(clientClassesDir, clientReporter, tracer))
Task.gatherUnordered(backgroundTasks).memoize.map(_ => ())
}
Expand Down Expand Up @@ -783,19 +804,19 @@ object Compiler {
*/
def rebaseAnalysisClassFiles(
analysis0: CompileAnalysis,
readOnlyClassesDir: Path,
newClassesDir: Path,
origin: Path,
target: Path,
sourceFilesWithFatalWarnings: scala.collection.Set[File]
): Analysis = {
// Cast to the only internal analysis that we support
val analysis = analysis0.asInstanceOf[Analysis]
def rebase(file: VirtualFileRef): VirtualFileRef = {

val filePath = converter.toPath(file).toAbsolutePath()
if (!filePath.startsWith(readOnlyClassesDir)) file
if (!filePath.startsWith(origin)) file
else {
// Hash for class file is the same because the copy duplicates metadata
val path = newClassesDir.resolve(readOnlyClassesDir.relativize(filePath))
val path = target.resolve(origin.relativize(filePath))
converter.toVirtualFile(path)
}
}
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/main/scala/bloop/bsp/BloopBspServices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -603,10 +603,7 @@ final class BloopBspServices(
params: bsp.DebugSessionParams
): BspEndpointResponse[bsp.DebugSessionAddress] = {

def inferDebuggee(
projects: Seq[Project],
state: State
): BspResponse[Debuggee] = {
def inferDebuggee(projects: Seq[Project], state: State): BspResponse[Debuggee] = {
def convert[A: JsonValueCodec](
f: A => Either[String, Debuggee]
): Either[Response.Error, Debuggee] = {
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/main/scala/bloop/dap/BloopDebugToolsResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ class BloopDebugToolsResolver(logger: Logger) extends DebugToolsResolver {
}
}

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

object BloopDebugToolsResolver {
private val expressionCompilerCache: mutable.Map[ScalaVersion, ClassLoader] = mutable.Map.empty
private val stepFilterCache: mutable.Map[ScalaVersion, ClassLoader] = mutable.Map.empty
private val decoderCache: mutable.Map[ScalaVersion, ClassLoader] = mutable.Map.empty
}
Loading

0 comments on commit c80bdcc

Please sign in to comment.