Skip to content

Commit bdd782b

Browse files
jchyblwronski
authored andcommitted
Fix deleting/renaming resources
Previously this would have no effect on the program. With this commit a mapping feature was introduced to resources, which remembers from where a resource file originates. An integration test for the issue was also added.
1 parent 8046a08 commit bdd782b

File tree

3 files changed

+115
-22
lines changed

3 files changed

+115
-22
lines changed

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import scala.build.Ops.*
1616
import scala.build.compiler.{ScalaCompiler, ScalaCompilerMaker}
1717
import scala.build.errors.*
1818
import scala.build.internal.{Constants, CustomCodeWrapper, MainClass, Util}
19+
import scala.build.internal.resource.ResourceMapper
1920
import scala.build.options.*
2021
import scala.build.options.validation.ValidationException
2122
import scala.build.postprocessing.*
@@ -392,9 +393,9 @@ object Build {
392393

393394
val builds = value(buildScopes())
394395

395-
copyResourceToClassesDir(builds.main)
396+
ResourceMapper.copyResourceToClassesDir(builds.main)
396397
for (testBuild <- builds.get(Scope.Test))
397-
copyResourceToClassesDir(testBuild)
398+
ResourceMapper.copyResourceToClassesDir(testBuild)
398399

399400
if (actionableDiagnostics.getOrElse(false)) {
400401
val projectOptions = builds.get(Scope.Test).getOrElse(builds.main).options
@@ -404,26 +405,6 @@ object Build {
404405
builds
405406
}
406407

407-
private def copyResourceToClassesDir(build: Build): Unit = build match {
408-
case b: Build.Successful =>
409-
for {
410-
resourceDirPath <- b.sources.resourceDirs.filter(os.exists(_))
411-
resourceFilePath <- os.walk(resourceDirPath).filter(os.isFile(_))
412-
relativeResourcePath = resourceFilePath.relativeTo(resourceDirPath)
413-
// dismiss files generated by scala-cli
414-
if !relativeResourcePath.startsWith(os.rel / Constants.workspaceDirName)
415-
} {
416-
val destPath = b.output / relativeResourcePath
417-
os.copy(
418-
resourceFilePath,
419-
destPath,
420-
replaceExisting = true,
421-
createFolders = true
422-
)
423-
}
424-
case _ =>
425-
}
426-
427408
private def build(
428409
inputs: Inputs,
429410
sources: Sources,
@@ -482,6 +463,13 @@ object Build {
482463
def classesDir(root: os.Path, projectName: String, scope: Scope, suffix: String = ""): os.Path =
483464
classesRootDir(root, projectName) / s"${scope.name}$suffix"
484465

466+
def resourcesRegistry(
467+
root: os.Path,
468+
projectName: String,
469+
scope: Scope
470+
): os.Path =
471+
root / Constants.workspaceDirName / projectName / s"resources-${scope.name}"
472+
485473
def scalaNativeSupported(
486474
options: BuildOptions,
487475
inputs: Inputs
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package scala.build.internal.resource
2+
3+
import scala.build.Build
4+
import scala.build.internal.Constants
5+
6+
object ResourceMapper {
7+
8+
private def resourceMapping(build: Build.Successful): Map[os.Path, os.RelPath] = {
9+
val seq = for {
10+
resourceDirPath <- build.sources.resourceDirs.filter(os.exists(_))
11+
resourceFilePath <- os.walk(resourceDirPath).filter(os.isFile(_))
12+
relativeResourcePath = resourceFilePath.relativeTo(resourceDirPath)
13+
// dismiss files generated by scala-cli
14+
if !relativeResourcePath.startsWith(os.rel / Constants.workspaceDirName)
15+
} yield (resourceFilePath, relativeResourcePath)
16+
17+
seq.toMap
18+
}
19+
20+
def copyResourcesToDirWithMapping(
21+
output: os.Path,
22+
registryFilePath: os.Path,
23+
newMapping: Map[os.Path, os.RelPath]
24+
): Unit = {
25+
26+
val oldRegistry =
27+
if (os.exists(registryFilePath))
28+
os.read(registryFilePath)
29+
.linesIterator
30+
.filter(_.nonEmpty)
31+
.map(os.RelPath(_))
32+
.toSet
33+
else
34+
Set.empty
35+
val removedFiles = oldRegistry -- newMapping.values
36+
37+
for (f <- removedFiles)
38+
os.remove(output / f)
39+
40+
for ((inputPath, outputPath) <- newMapping)
41+
os.copy(
42+
inputPath,
43+
output / outputPath,
44+
replaceExisting = true,
45+
createFolders = true
46+
)
47+
48+
if (newMapping.isEmpty)
49+
os.remove(registryFilePath)
50+
else
51+
os.write.over(
52+
registryFilePath,
53+
newMapping.map(_._2.toString).toVector.sorted.mkString("\n")
54+
)
55+
}
56+
57+
/** Copies and maps resources from their original path to the destination path in build output,
58+
* also caching output paths in a file.
59+
*
60+
* Remembering the mapping this way allows for the resource to be removed if the original file is
61+
* renamed/deleted.
62+
*/
63+
def copyResourceToClassesDir(build: Build): Unit = build match {
64+
case b: Build.Successful =>
65+
val fullFilePath = Build.resourcesRegistry(b.inputs.workspace, b.inputs.projectName, b.scope)
66+
copyResourcesToDirWithMapping(b.output, fullFilePath, resourceMapping(b))
67+
case _ =>
68+
}
69+
}

modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,6 +2096,42 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String])
20962096
}
20972097
}
20982098

2099+
test("deleting resources after building") {
2100+
val projectDir = "projectDir"
2101+
val fileName = "main.scala"
2102+
val resourceContent = "hello world"
2103+
val resourcePath = os.rel / projectDir / "resources" / "test.txt"
2104+
val inputs = TestInputs(
2105+
os.rel / projectDir / fileName ->
2106+
s"""
2107+
|//> using resourceDir "resources"
2108+
|
2109+
|object Main {
2110+
| def main(args: Array[String]) = {
2111+
| val inputStream = getClass().getResourceAsStream("/test.txt")
2112+
| if (inputStream == null) println("null")
2113+
| else println("non null")
2114+
| }
2115+
|}
2116+
|""".stripMargin,
2117+
resourcePath -> resourceContent
2118+
)
2119+
2120+
inputs.fromRoot { root =>
2121+
def runCli() =
2122+
os.proc(TestUtil.cli, extraOptions, projectDir)
2123+
.call(cwd = root)
2124+
.out.trim()
2125+
2126+
val output1 = runCli()
2127+
expect(output1 == "non null")
2128+
2129+
os.remove(root / resourcePath)
2130+
val output2 = runCli()
2131+
expect(output2 == "null")
2132+
}
2133+
}
2134+
20992135
private def maybeScalapyPrefix =
21002136
if (actualScalaVersion.startsWith("2.13.")) ""
21012137
else "import me.shadaj.scalapy.py" + System.lineSeparator()

0 commit comments

Comments
 (0)