From d691ee167b4e113c2299b4e38de2ce9e942d8332 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 23 May 2024 14:25:00 +0200 Subject: [PATCH] Copy directories in best effort noop compilation after reconnecting client --- backend/src/main/scala/bloop/Compiler.scala | 164 ++++++++++-------- .../scala/bloop/bsp/BspMetalsClientSpec.scala | 48 +++++ 2 files changed, 138 insertions(+), 74 deletions(-) diff --git a/backend/src/main/scala/bloop/Compiler.scala b/backend/src/main/scala/bloop/Compiler.scala index 2d47dbede..6d192dd60 100644 --- a/backend/src/main/scala/bloop/Compiler.scala +++ b/backend/src/main/scala/bloop/Compiler.scala @@ -392,17 +392,39 @@ object Compiler { None ) reporter.reportEndCompilation() - Some(Failed(problems, t, elapsed, CompileBackgroundTasks.empty, bestEffortProducts)) + val backgroundTasks = new CompileBackgroundTasks { + def trigger( + 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 firstTask = Task { BloopPaths.delete(AbsolutePath(newClassesDir)) } + val secondTask = updateExternalClassesDirWithReadOnly( + clientClassesDir, + clientTracer, + clientLogger, + compileInputs, + compileProducts.newClassesDir, + readOnlyCopyDenylist = mutable.HashSet.empty, + allInvalidatedClassFilesForProject, + allInvalidatedExtraCompileProducts + ) + Task + .gatherUnordered(List(firstTask, secondTask)) + .map(_ => ()) + } + } + Some(Failed(problems, t, elapsed, backgroundTasks, bestEffortProducts)) } else None case _ => None } if (noopBestEffortResult.isDefined) { logger.debug("Skipping redundant best-effort compilation") - return Task { - BloopPaths.delete(AbsolutePath(newClassesDir)) - noopBestEffortResult.get - } + return Task { noopBestEffortResult.get } } BloopZincCompiler @@ -451,41 +473,6 @@ object Compiler { PreviousResult.of(Optional.of(result.analysis()), Optional.of(result.setup())) val analysis = result.analysis() - def updateExternalClassesDirWithReadOnly( - clientClassesDir: AbsolutePath, - clientTracer: BraveTracer, - clientLogger: Logger - ): Task[Unit] = Task.defer { - val descriptionMsg = s"Updating external classes dir with read only $clientClassesDir" - clientTracer.traceTaskVerbose(descriptionMsg) { _ => - Task.defer { - clientLogger.debug(descriptionMsg) - val invalidatedClassFiles = - allInvalidatedClassFilesForProject.iterator.map(_.toPath).toSet - val invalidatedExtraProducts = - allInvalidatedExtraCompileProducts.iterator.map(_.toPath).toSet - val invalidatedInThisProject = invalidatedClassFiles ++ invalidatedExtraProducts - val denyList = invalidatedInThisProject ++ readOnlyCopyDenylist.iterator - val config = - ParallelOps.CopyConfiguration(5, CopyMode.ReplaceIfMetadataMismatch, denyList) - val lastCopy = ParallelOps.copyDirectories(config)( - readOnlyClassesDir, - clientClassesDir.underlying, - compileInputs.ioScheduler, - enableCancellation = false, - compileInputs.logger - ) - - lastCopy.map { _ => - clientLogger.debug( - s"Finished copying classes from $readOnlyClassesDir to $clientClassesDir" - ) - () - } - } - } - } - def persistAnalysis(analysis: CompileAnalysis, out: AbsolutePath): Task[Unit] = { // Important to memoize it, it's triggered by different clients Task(persist(out, analysis, result.setup, tracer, logger)).memoize @@ -520,7 +507,16 @@ object Compiler { val clientClassesDir = clientClassesObserver.classesDir clientLogger.debug(s"Triggering background tasks for $clientClassesDir") val updateClientState = - updateExternalClassesDirWithReadOnly(clientClassesDir, clientTracer, clientLogger) + updateExternalClassesDirWithReadOnly( + clientClassesDir, + clientTracer, + clientLogger, + compileInputs, + readOnlyClassesDir, + readOnlyCopyDenylist, + allInvalidatedClassFilesForProject, + allInvalidatedExtraCompileProducts + ) val writeAnalysisIfMissing = { if (compileOut.analysisOut.exists) Task.unit @@ -603,7 +599,12 @@ object Compiler { val firstTask = updateExternalClassesDirWithReadOnly( clientClassesDir, clientTracer, - clientLogger + clientLogger, + compileInputs, + readOnlyClassesDir, + readOnlyCopyDenylist, + allInvalidatedClassFilesForProject, + allInvalidatedExtraCompileProducts ) val secondTask = Task { @@ -695,6 +696,46 @@ object Compiler { } } + def updateExternalClassesDirWithReadOnly( + clientClassesDir: AbsolutePath, + clientTracer: BraveTracer, + clientLogger: Logger, + compileInputs: CompileInputs, + readOnlyClassesDir: Path, + readOnlyCopyDenylist: mutable.HashSet[Path], + allInvalidatedClassFilesForProject: mutable.HashSet[File], + allInvalidatedExtraCompileProducts: mutable.HashSet[File] + ): Task[Unit] = Task.defer { + val descriptionMsg = s"Updating external classes dir with read only $clientClassesDir" + clientTracer.traceTaskVerbose(descriptionMsg) { _ => + Task.defer { + clientLogger.debug(descriptionMsg) + val invalidatedClassFiles = + allInvalidatedClassFilesForProject.iterator.map(_.toPath).toSet + val invalidatedExtraProducts = + allInvalidatedExtraCompileProducts.iterator.map(_.toPath).toSet + val invalidatedInThisProject = invalidatedClassFiles ++ invalidatedExtraProducts + val denyList = invalidatedInThisProject ++ readOnlyCopyDenylist.iterator + val config = + ParallelOps.CopyConfiguration(5, CopyMode.ReplaceIfMetadataMismatch, denyList) + val lastCopy = ParallelOps.copyDirectories(config)( + readOnlyClassesDir, + clientClassesDir.underlying, + compileInputs.ioScheduler, + enableCancellation = false, + compileInputs.logger + ) + + lastCopy.map { _ => + clientLogger.debug( + s"Finished copying classes from $readOnlyClassesDir to $clientClassesDir" + ) + () + } + } + } + } + def findFailedProblems( reporter: ZincReporter, compileFailedMaybe: Option[xsbti.CompileFailed] @@ -808,7 +849,7 @@ object Compiler { /** * Handles successful Best Effort compilation. * - * Does not persist incremental compilation analysis, becouse as of time of commiting the compiler is not able + * Does not persist incremental compilation analysis, because as of time of commiting the compiler is not able * to always run the necessary phases, nor is zinc adjusted to handle betasty files correctly. * * Returns a [[bloop.Result.Failed]] with generated CompileProducts and a hash value of inputs and outputs included. @@ -829,36 +870,6 @@ object Compiler { val readOnlyClassesDirPath = readOnlyClassesDir.toString val newClassesDir = compileOut.internalNewClassesDir.underlying - def updateExternalClassesDirWithReadOnly( - clientClassesDir: AbsolutePath, - clientTracer: BraveTracer, - clientLogger: Logger - ): Task[Unit] = Task.defer { - val descriptionMsg = s"Updating external classes dir with read only $clientClassesDir" - clientTracer.traceTaskVerbose(descriptionMsg) { _ => - Task.defer { - clientLogger.debug(descriptionMsg) - val denyList = Set.empty[Path] - val config = - ParallelOps.CopyConfiguration(5, CopyMode.ReplaceIfMetadataMismatch, denyList) - val lastCopy = ParallelOps.copyDirectories(config)( - readOnlyClassesDir, - clientClassesDir.underlying, - compileInputs.ioScheduler, - enableCancellation = false, - compileInputs.logger - ) - - lastCopy.map { _ => - clientLogger.debug( - s"Finished copying classes from $readOnlyClassesDir to $clientClassesDir" - ) - () - } - } - } - } - reporter.processEndCompilation( previousSuccessfulProblems, ch.epfl.scala.bsp.StatusCode.Error, @@ -902,7 +913,12 @@ object Compiler { val firstTask = updateExternalClassesDirWithReadOnly( clientClassesDir, clientTracer, - clientLogger + clientLogger, + compileInputs, + readOnlyClassesDir, + readOnlyCopyDenylist = mutable.HashSet.empty, + allInvalidatedClassFilesForProject, + allInvalidatedExtraCompileProducts ) val secondTask = Task { diff --git a/frontend/src/test/scala/bloop/bsp/BspMetalsClientSpec.scala b/frontend/src/test/scala/bloop/bsp/BspMetalsClientSpec.scala index 5a662a029..ca2807f10 100644 --- a/frontend/src/test/scala/bloop/bsp/BspMetalsClientSpec.scala +++ b/frontend/src/test/scala/bloop/bsp/BspMetalsClientSpec.scala @@ -559,6 +559,54 @@ class BspMetalsClientSpec( } } + test("best-effort: regain artifacts after disconnecting and reconnecting to the client") { + TestUtil.withinWorkspace { workspace => + val `A` = TestProject( + workspace, + "A", + dummyBestEffortSources, + scalaVersion = Some(bestEffortScalaVersion) + ) + val `B` = TestProject( + workspace, + "B", + dummyBestEffortDepSources, + directDependencies = List(`A`), + scalaVersion = Some(bestEffortScalaVersion) + ) + val projects = List(`A`, `B`) + TestProject.populateWorkspace(workspace, projects) + val logger = new RecordingLogger(ansiCodesSupported = false) + val extraParams = BloopExtraBuildParams( + ownsBuildFiles = None, + clientClassesRootDir = None, + semanticdbVersion = Some(semanticdbVersion), + supportedScalaVersions = Some(List(bestEffortScalaVersion)), + javaSemanticdbVersion = None, + enableBestEffortMode = Some(true) + ) + loadBspState(workspace, projects, logger, "Metals", bloopExtraParams = extraParams) { state => + val compiledStateA = state.compile(`A`, arguments = Some(List("--best-effort"))).toTestState + val compiledStateB = state.compile(`B`, arguments = Some(List("--best-effort"))).toTestState + } + loadBspState( + workspace, + projects, + logger, + "Metals reconnected", + bloopExtraParams = extraParams + ) { state => + val compiledStateA = state.compile(`A`, arguments = Some(List("--best-effort"))).toTestState + assertSemanticdbFileFor("TypeError.scala", compiledStateA, "A") + assertBetastyFile("TypeError.betasty", compiledStateA, "A") + val compiledStateB = state.compile(`B`, arguments = Some(List("--best-effort"))).toTestState + assertSemanticdbFileFor("TypeErrorDependency.scala", compiledStateB, "B") + assertBetastyFile("TypeErrorDependency.betasty", compiledStateB, "B") + state.findBuildTarget(`A`) + } + } + } + test("compile is successful with semanticDB and javac processorpath") { TestUtil.withinWorkspace { workspace => val logger = new RecordingLogger(ansiCodesSupported = false)