From e9089492cc9ddbf5b67ab2d77aeae9dfe2f424dd Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Thu, 2 Jan 2025 20:00:19 +0100 Subject: [PATCH] bugfix: Copy resources to client directory on successful compilation --- .../scala/bloop/BloopClassFileManager.scala | 44 ++++++++++++++++++- backend/src/main/scala/bloop/Compiler.scala | 3 +- .../src/main/scala/bloop/data/Project.scala | 21 +++------ .../bloop/engine/tasks/CompileTask.scala | 3 +- frontend/src/test/scala/bloop/RunSpec.scala | 39 +++++++++++++++- .../test/scala/bloop/util/TestProject.scala | 5 ++- 6 files changed, 93 insertions(+), 22 deletions(-) diff --git a/backend/src/main/scala/bloop/BloopClassFileManager.scala b/backend/src/main/scala/bloop/BloopClassFileManager.scala index 53c8dcf9bc..818f5e1724 100644 --- a/backend/src/main/scala/bloop/BloopClassFileManager.scala +++ b/backend/src/main/scala/bloop/BloopClassFileManager.scala @@ -201,6 +201,38 @@ final class BloopClassFileManager( } + private def copyResources( + resources: List[AbsolutePath], + copyTo: AbsolutePath, + config: ParallelOps.CopyConfiguration + ): Task[Unit] = { + val (singleFiles, classpathEntries) = + resources.partition(path => path.exists && path.isFile) + + val singleFilesCopy = Task { + for (file <- singleFiles) { + Files.copy( + file.underlying, + copyTo.underlying.resolve(file.underlying.toFile().getName()), + StandardCopyOption.REPLACE_EXISTING + ) + } + } + val classpathEntriesCopy = + for (entry <- classpathEntries) yield { + ParallelOps + .copyDirectories(config)( + entry.underlying, + copyTo.underlying, + inputs.ioScheduler, + enableCancellation = false, + inputs.logger + ) + } + + Task.gatherUnordered(singleFilesCopy :: classpathEntriesCopy).map(_ => ()) + } + def complete(success: Boolean): Unit = { val deleteAfterCompilation = Task { BloopPaths.delete(AbsolutePath(backupDir)) } @@ -220,7 +252,7 @@ final class BloopClassFileManager( val config = ParallelOps.CopyConfiguration(5, CopyMode.ReplaceExisting, Set.empty, Set.empty) - ParallelOps + val copyClassFiles = ParallelOps .copyDirectories(config)( newClassesDir, clientExternalClassesDir.underlying, @@ -233,6 +265,16 @@ final class BloopClassFileManager( () } .flatMap(_ => deleteAfterCompilation) + + Task + .gatherUnordered( + List( + copyClassFiles, + copyResources(inputs.resources, clientExternalClassesDir, config) + ) + ) + .map(_ => ()) + } } ) diff --git a/backend/src/main/scala/bloop/Compiler.scala b/backend/src/main/scala/bloop/Compiler.scala index 64e4d599c4..689a9f7a74 100644 --- a/backend/src/main/scala/bloop/Compiler.scala +++ b/backend/src/main/scala/bloop/Compiler.scala @@ -72,7 +72,8 @@ case class CompileInputs( ioScheduler: Scheduler, ioExecutor: Executor, invalidatedClassFilesInDependentProjects: Set[File], - generatedClassFilePathsInDependentProjects: Map[String, File] + generatedClassFilePathsInDependentProjects: Map[String, File], + resources: List[AbsolutePath] ) case class CompileOutPaths( diff --git a/frontend/src/main/scala/bloop/data/Project.scala b/frontend/src/main/scala/bloop/data/Project.scala index da2107c7fa..07f3b794bb 100644 --- a/frontend/src/main/scala/bloop/data/Project.scala +++ b/frontend/src/main/scala/bloop/data/Project.scala @@ -13,7 +13,6 @@ import bloop.ScalaInstance import bloop.bsp.ProjectUris import bloop.config.Config import bloop.config.ConfigCodecs -import bloop.engine.Dag import bloop.engine.SourceGenerator import bloop.engine.tasks.toolchains.JvmToolchain import bloop.engine.tasks.toolchains.ScalaJsToolchain @@ -29,6 +28,7 @@ import scalaz.Cord import xsbti.compile.ClasspathOptions import xsbti.compile.CompileOrder import scala.util.Success +import bloop.engine.Dag final case class Project( name: String, @@ -117,10 +117,8 @@ final case class Project( private def fullClasspath( dag: Dag[Project], client: ClientInfo, - rawClasspath: List[AbsolutePath], - pickValidResources: Project => Array[AbsolutePath] + rawClasspath: List[AbsolutePath] ): Array[AbsolutePath] = { - val addedResources = new mutable.HashSet[AbsolutePath]() val cp = (this.genericClassesDir :: rawClasspath).toBuffer // Add the resources right before the classes directory if found in the classpath @@ -128,16 +126,8 @@ final case class Project( val genericClassesDir = p.genericClassesDir val uniqueClassesDir = client.getUniqueClassesDirFor(p, forceGeneration = true) val index = cp.indexOf(genericClassesDir) - val newResources = pickValidResources(p).filterNot(r => addedResources.contains(r)) - newResources.foreach(r => addedResources.add(r)) - if (index == -1) { - // Not found? Weird. Let's add resources to end just in case - cp.appendAll(newResources) - } else { - // Replace in-place for the classes directory unique to the client + if (index != -1) { cp(index) = uniqueClassesDir - // Prepend resources to classes directories - cp.insertAll(index, newResources) } } @@ -145,7 +135,7 @@ final case class Project( } def fullClasspath(dag: Dag[Project], client: ClientInfo): Array[AbsolutePath] = { - fullClasspath(dag, client, rawClasspath, p => Project.pickValidResources(p.resources)) + fullClasspath(dag, client, rawClasspath) } def fullRuntimeClasspath(dag: Dag[Project], client: ClientInfo): Array[AbsolutePath] = { @@ -156,8 +146,7 @@ final case class Project( fullClasspath( dag, client, - rawRuntimeClasspath, - p => Project.pickValidResources(p.runtimeResources) + rawRuntimeClasspath ) } diff --git a/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala b/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala index eeb7050c30..daeb365bb0 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala @@ -162,7 +162,8 @@ object CompileTask { ExecutionContext.ioScheduler, ExecutionContext.ioExecutor, bundle.dependenciesData.allInvalidatedClassFiles, - bundle.dependenciesData.allGeneratedClassFilePaths + bundle.dependenciesData.allGeneratedClassFilePaths, + project.runtimeResources ) } diff --git a/frontend/src/test/scala/bloop/RunSpec.scala b/frontend/src/test/scala/bloop/RunSpec.scala index 967aa9ab26..4685d2967f 100644 --- a/frontend/src/test/scala/bloop/RunSpec.scala +++ b/frontend/src/test/scala/bloop/RunSpec.scala @@ -31,6 +31,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test import org.junit.experimental.categories.Category +import java.nio.file.StandardOpenOption @Category(Array(classOf[bloop.FastTests])) class RunSpec extends BloopHelpers { @@ -168,7 +169,7 @@ class RunSpec extends BloopHelpers { else Files.createDirectories(resource.underlying) } - val fullClasspath = rootWithAggregation.fullRuntimeClasspath(dag, state.client).toList + val fullClasspath = rootWithAggregation.fullRuntimeClasspath().toList Assert.assertFalse(dependentResources.isEmpty) dependentResources.foreach { r => Assert.assertTrue(s"Missing $r in $fullClasspath", fullClasspath.contains(r)) @@ -474,6 +475,42 @@ class RunSpec extends BloopHelpers { } } + @Test + def runSeesSingleFileResources(): Unit = { + TestUtil.withinWorkspace { workspace => + object Sources { + val `a/A.scala` = + """/a/A.scala + |object A { + | def main(args: Array[String]): Unit = { + | val res = getClass.getClassLoader.getResourceAsStream("resource.txt") + | val content = scala.io.Source.fromInputStream(res).mkString + | //assert("goodbye" == content) + | } + |}""".stripMargin + } + + val tmpDir = Files.createTempDirectory("runtime-single") + val resource = tmpDir.resolve("resource.txt") + Files.write(resource, "goodbye".getBytes(), StandardOpenOption.CREATE) + val logger = new RecordingLogger(ansiCodesSupported = false) + val `A` = TestProject( + workspace, + "a", + List(Sources.`a/A.scala`), + additionalResources = List(resource) + ) + + val projects = List(`A`) + val state = loadState(workspace, projects, logger) + val external = state.getClientExternalDir(A) + println(external) + val runState = state.run(`A`) + logger.dump() + assertEquals(ExitStatus.Ok, runState.status) + } + } + @Test def runUsesRuntimeEnvironment(): Unit = { TestUtil.withinWorkspace { workspace => diff --git a/frontend/src/test/scala/bloop/util/TestProject.scala b/frontend/src/test/scala/bloop/util/TestProject.scala index d8638e1a12..5188dd6694 100644 --- a/frontend/src/test/scala/bloop/util/TestProject.scala +++ b/frontend/src/test/scala/bloop/util/TestProject.scala @@ -113,7 +113,8 @@ abstract class BaseTestProject { order: Config.CompileOrder = Config.Mixed, jars: Array[AbsolutePath] = Array(), sourcesGlobs: List[Config.SourcesGlobs] = Nil, - sourceGenerators: List[Config.SourceGenerator] = Nil + sourceGenerators: List[Config.SourceGenerator] = Nil, + additionalResources: List[Path] = Nil ): TestProject = { val projectBaseDir = Files.createDirectories(baseDir.underlying.resolve(name)) val ProjectArchetype(sourceDir, outDir, resourceDir, classes, runtimeResourceDir) = @@ -146,7 +147,7 @@ abstract class BaseTestProject { if (strictDependencies) (directClasspath, transitiveClasspath) else (transitiveClasspath, transitiveClasspath) } - val compileResourcesList = Some(List(resourceDir.underlying)) + val compileResourcesList = Some(List(resourceDir.underlying) ::: additionalResources) val runtimeResourcesList = runtimeResources match { case None => compileResourcesList case Some(_) => Some(List(runtimeResourceDir.underlying))