Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions backend/src/main/scala/bloop/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ case class CompileInputs(
ioExecutor: Executor,
invalidatedClassFilesInDependentProjects: Set[File],
generatedClassFilePathsInDependentProjects: Map[String, File],
resources: List[AbsolutePath]
resources: List[AbsolutePath],
resourceMappings: List[(AbsolutePath, String)]
)

case class CompileOutPaths(
Expand Down Expand Up @@ -828,6 +829,11 @@ object Compiler {
compileInputs.logger,
compileInputs.ioScheduler
)
val copyMappedResources = bloop.io.ResourceMapper.copyMappedResources(
compileInputs.resourceMappings,
clientClassesDir,
compileInputs.logger
)
val lastCopy = ParallelOps.copyDirectories(config)(
readOnlyClassesDir,
clientClassesDir.underlying,
Expand All @@ -836,7 +842,7 @@ object Compiler {
compileInputs.logger
)

Task.gatherUnordered(List(copyResources, lastCopy)).map { _ =>
Task.gatherUnordered(List(copyResources, copyMappedResources, lastCopy)).map { _ =>
clientLogger.debug(
s"Finished copying classes from $readOnlyClassesDir to $clientClassesDir"
)
Expand Down
114 changes: 114 additions & 0 deletions backend/src/main/scala/bloop/io/ResourceMapper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package bloop.io

import java.nio.file.Files
import java.nio.file.StandardCopyOption

import scala.util.control.NonFatal

import bloop.logging.DebugFilter
import bloop.logging.Logger
import bloop.task.Task

/**
* Utility for handling resource file mappings.
*
* Resource mappings allow files to be copied from source locations to custom
* target paths, similar to SBT's "mappings" field.
*/
object ResourceMapper {
private implicit val filter: DebugFilter.All.type = DebugFilter.All

/**
* Copy mapped resources to the target directory.
*
* @param mappings List of (source, targetRelativePath) tuples
* @param classesDir Base directory where resources should be copied
* @param logger Logger for debug/error messages
* @return Task that completes when all resources are copied
*/
def copyMappedResources(
mappings: List[(AbsolutePath, String)],
classesDir: AbsolutePath,
logger: Logger
): Task[Unit] = {
val tasks = mappings.map {
case (source, targetRelPath) =>
Task {
val target = classesDir.resolve(targetRelPath)
if (!target.getParent.exists) {
Files.createDirectories(target.getParent.underlying)
}

if (source.isDirectory) {
import java.nio.file.FileVisitResult
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes

val sourcePath = source.underlying
val targetPath = target.underlying
Files.walkFileTree(
sourcePath,
new SimpleFileVisitor[Path] {
override def visitFile(
file: Path,
attrs: BasicFileAttributes
): FileVisitResult = {
val relPath = sourcePath.relativize(file)
val targetFile = targetPath.resolve(relPath)
Files.createDirectories(targetFile.getParent)
Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING)
FileVisitResult.CONTINUE
}
}
)
} else if (source.exists) {
Files.copy(source.underlying, target.underlying, StandardCopyOption.REPLACE_EXISTING)
} else {
logger.warn(s"Source file $source does not exist, skipping mapping to $targetRelPath")
}
()
}
}
Task.gatherUnordered(tasks).map(_ => ())
}

/**
* Check if any mapped resources have changed since the given timestamp.
*
* Note: This is currently not used. Incremental compilation change detection
* for resource mappings would require integration with Zinc's analysis.
* For now, mapped resources are always copied during compilation.
*
* @param mappings List of (source, targetRelativePath) tuples
* @param lastModified Timestamp to compare against
* @return true if any source file is newer than lastModified
*/
private[bloop] def hasMappingsChanged(
mappings: List[(AbsolutePath, String)],
lastModified: Long
): Boolean = {
import java.nio.file.Files
mappings.exists {
case (source, _) =>
if (source.isDirectory) {
hasDirectoryChanged(source, lastModified)
} else {
source.exists && Files.getLastModifiedTime(source.underlying).toMillis > lastModified
}
}
}

private def hasDirectoryChanged(dir: AbsolutePath, lastModified: Long): Boolean = {
if (dir.exists) {
val stream = Files.walk(dir.underlying)
try {
stream.anyMatch(path => Files.getLastModifiedTime(path).toMillis > lastModified)
} finally {
stream.close()
}
} else {
false
}
}
}
Comment on lines +102 to +114
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private def hasDirectoryChanged(dir: AbsolutePath, lastModified: Long): Boolean = {
if (!dir.exists) return false
val stream = Files.walk(dir.underlying)
try {
stream.anyMatch(path => Files.getLastModifiedTime(path).toMillis > lastModified)
} finally {
stream.close()
}
}
}
private def hasDirectoryChanged(dir: AbsolutePath, lastModified: Long): Boolean = {
if (dir.exists) {
val stream = Files.walk(dir.underlying)
try {
stream.anyMatch(path => Files.getLastModifiedTime(path).toMillis > lastModified)
} finally {
stream.close()
}
}
} else {
false
}

4 changes: 4 additions & 0 deletions frontend/src/main/scala/bloop/data/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ final case class Project(
scalaInstance: Option[ScalaInstance],
rawClasspath: List[AbsolutePath],
resources: List[AbsolutePath],
resourceMappings: List[(AbsolutePath, String)],
compileSetup: Config.CompileSetup,
genericClassesDir: AbsolutePath,
isBestEffort: Boolean,
Expand Down Expand Up @@ -347,6 +348,8 @@ object Project {
val tags = project.tags.getOrElse(Nil)
val projectDirectory = AbsolutePath(project.directory)

val resourceMappings = List.empty[(AbsolutePath, String)]

Project(
project.name,
projectDirectory,
Expand All @@ -355,6 +358,7 @@ object Project {
instance,
compileClasspath,
compileResources,
resourceMappings,
setup,
AbsolutePath(project.classesDir),
isBestEffort = false,
Expand Down
26 changes: 22 additions & 4 deletions frontend/src/main/scala/bloop/engine/tasks/CompileTask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,23 @@ object CompileTask {
logger,
ExecutionContext.ioScheduler
)

// Also copy mapped resources if any
val copyMappedResourcesTask: Task[Unit] =
bloop.io.ResourceMapper.copyMappedResources(
project.resourceMappings,
bundle.clientClassesObserver.classesDir,
logger
)

val allCopyTasks =
Task.gatherUnordered(List(copyResourcesTask, copyMappedResourcesTask))
Task.now(
ResultBundle(
Compiler.Result.Empty,
None,
None,
copyResourcesTask.runAsync(ExecutionContext.ioScheduler)
allCopyTasks.map(_ => ()).runAsync(ExecutionContext.ioScheduler)
)
)
case Right(CompileSourcesAndInstance(sources, instance, _)) =>
Expand Down Expand Up @@ -184,7 +195,8 @@ object CompileTask {
ExecutionContext.ioExecutor,
bundle.dependenciesData.allInvalidatedClassFiles,
bundle.dependenciesData.allGeneratedClassFilePaths,
project.runtimeResources
project.runtimeResources,
project.resourceMappings
)
}

Expand Down Expand Up @@ -245,8 +257,14 @@ object CompileTask {
compileProjectTracer,
logger
)
.doOnFinish(_ => Task(compileProjectTracer.terminate()))
postCompilationTasks.runAsync(ExecutionContext.ioScheduler)

// Copy mapped resources after compilation
val allTasks = Task
.gatherUnordered(List(postCompilationTasks))
.map(_ => ())
.doOnFinish(_ => Task(compileProjectTracer.terminate()))

allTasks.runAsync(ExecutionContext.ioScheduler)
}

// Populate the last successful result if result was success
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/test/scala/bloop/DagSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class DagSpec {
// format: OFF
def dummyOrigin: Origin = TestUtil.syntheticOriginFor(dummyPath)
def dummyProject(name: String, dependencies: List[String]): Project =
Project(name, dummyPath, None, dependencies, Some(dummyInstance), Nil, Nil, compileOptions,
Project(name, dummyPath, None, dependencies, Some(dummyInstance), Nil, Nil, Nil, compileOptions,
dummyPath, isBestEffort = false, Nil, Nil, Nil, Nil, None, Nil, Nil, Config.TestOptions.empty, dummyPath, dummyPath,
Project.defaultPlatform(logger, Nil, Nil), None, None, Nil, dummyOrigin)
// format: ON
Expand Down
Loading