Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bugfix: Copy resources to client directory on successful compilation
Browse files Browse the repository at this point in the history
tgodzik committed Jan 8, 2025

Verified

This commit was signed with the committer’s verified signature.
dcermak Dan Čermák
1 parent c1ff013 commit a33d3e7
Showing 7 changed files with 99 additions and 23 deletions.
40 changes: 39 additions & 1 deletion backend/src/main/scala/bloop/BloopClassFileManager.scala
Original file line number Diff line number Diff line change
@@ -201,6 +201,34 @@ 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 singleFilesToCopy =
for (file <- singleFiles)
yield file.underlying -> copyTo.underlying.resolve(file.underlying.toFile().getName())

val classpathEntriesCopy =
for (entry <- classpathEntries) yield {
ParallelOps
.copyDirectories(config)(
entry.underlying,
copyTo.underlying,
inputs.ioScheduler,
enableCancellation = false,
inputs.logger,
singleFilesToCopy
)
}

Task.gatherUnordered(classpathEntriesCopy).map(_ => ())
}

def complete(success: Boolean): Unit = {

val deleteAfterCompilation = Task { BloopPaths.delete(AbsolutePath(backupDir)) }
@@ -220,7 +248,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 +261,16 @@ final class BloopClassFileManager(
()
}
.flatMap(_ => deleteAfterCompilation)

Task
.gatherUnordered(
List(
copyClassFiles,
copyResources(inputs.resources, clientExternalClassesDir, config)
)
)
.map(_ => ())

}
}
)
3 changes: 2 additions & 1 deletion backend/src/main/scala/bloop/Compiler.scala
Original file line number Diff line number Diff line change
@@ -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(
16 changes: 14 additions & 2 deletions backend/src/main/scala/bloop/io/ParallelOps.scala
Original file line number Diff line number Diff line change
@@ -71,7 +71,8 @@ object ParallelOps {
target: Path,
scheduler: Scheduler,
enableCancellation: Boolean,
logger: Logger
logger: Logger,
additionalFiles: Seq[(Path, Path)] = Nil
): Task[FileWalk] = Task.defer {
val isCancelled = AtomicBoolean(false)

@@ -82,6 +83,14 @@ object ParallelOps {
MulticastStrategy.publish
)(scheduler)

val copyAdditionalFiles = Task {
additionalFiles.foreach {
case (from, to) =>
val attributes = Files.readAttributes(from, classOf[BasicFileAttributes])
observer.onNext(((from, attributes), to))
}
}

val discovery = new FileVisitor[Path] {
var firstVisit: Boolean = true
var currentTargetDirectory: Path = target
@@ -274,7 +283,10 @@ object ParallelOps {
}
}

val orderlyDiscovery = Task.fromFuture(subscribed.future).flatMap(_ => discoverFileTree)
val orderlyDiscovery = Task
.fromFuture(subscribed.future)
.flatMap(_ => copyAdditionalFiles)
.flatMap(_ => discoverFileTree)
val aggregatedCopyTask = Task {
Task.mapBoth(orderlyDiscovery, copyFilesInParallel) { case (fileWalk, _) => fileWalk }
}.flatten.executeOn(scheduler)
21 changes: 5 additions & 16 deletions frontend/src/main/scala/bloop/data/Project.scala
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import java.nio.charset.StandardCharsets

import scala.collection.mutable
import scala.util.Properties
import scala.util.Success
import scala.util.Try
import scala.util.control.NonFatal

@@ -28,7 +29,6 @@ import bloop.util.JavaRuntime
import scalaz.Cord
import xsbti.compile.ClasspathOptions
import xsbti.compile.CompileOrder
import scala.util.Success

final case class Project(
name: String,
@@ -117,35 +117,25 @@ 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
Dag.dfs(dag, mode = Dag.PreOrder).foreach { p =>
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)
}
}

cp.toArray
}

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
)
}

3 changes: 2 additions & 1 deletion frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala
Original file line number Diff line number Diff line change
@@ -162,7 +162,8 @@ object CompileTask {
ExecutionContext.ioScheduler,
ExecutionContext.ioExecutor,
bundle.dependenciesData.allInvalidatedClassFiles,
bundle.dependenciesData.allGeneratedClassFilePaths
bundle.dependenciesData.allGeneratedClassFilePaths,
project.runtimeResources
)
}

34 changes: 34 additions & 0 deletions frontend/src/test/scala/bloop/RunSpec.scala
Original file line number Diff line number Diff line change
@@ -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 {
@@ -474,6 +475,39 @@ 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 runState = state.run(`A`)
assertEquals(ExitStatus.Ok, runState.status)
}
}

@Test
def runUsesRuntimeEnvironment(): Unit = {
TestUtil.withinWorkspace { workspace =>
5 changes: 3 additions & 2 deletions frontend/src/test/scala/bloop/util/TestProject.scala
Original file line number Diff line number Diff line change
@@ -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))

0 comments on commit a33d3e7

Please sign in to comment.