Skip to content

Commit

Permalink
Copy directories in best effort noop compilation after reconnecting c…
Browse files Browse the repository at this point in the history
…lient
  • Loading branch information
jchyb committed May 23, 2024
1 parent 9cded83 commit d691ee1
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 74 deletions.
164 changes: 90 additions & 74 deletions backend/src/main/scala/bloop/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -603,7 +599,12 @@ object Compiler {
val firstTask = updateExternalClassesDirWithReadOnly(
clientClassesDir,
clientTracer,
clientLogger
clientLogger,
compileInputs,
readOnlyClassesDir,
readOnlyCopyDenylist,
allInvalidatedClassFilesForProject,
allInvalidatedExtraCompileProducts
)

val secondTask = Task {
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -902,7 +913,12 @@ object Compiler {
val firstTask = updateExternalClassesDirWithReadOnly(
clientClassesDir,
clientTracer,
clientLogger
clientLogger,
compileInputs,
readOnlyClassesDir,
readOnlyCopyDenylist = mutable.HashSet.empty,
allInvalidatedClassFilesForProject,
allInvalidatedExtraCompileProducts
)

val secondTask = Task {
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/test/scala/bloop/bsp/BspMetalsClientSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit d691ee1

Please sign in to comment.