From ddda4c28379fab05ec141e29f40e64631f2447c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Tomala?= Date: Mon, 22 Sep 2025 16:28:15 +0200 Subject: [PATCH 1/2] Fix test scope resources to not be added to the main scope --- .../src/main/scala/scala/build/CrossSources.scala | 7 +++++-- .../scala/scala/build/tests/DirectiveTests.scala | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/CrossSources.scala b/modules/build/src/main/scala/scala/build/CrossSources.scala index 4c48ce20b8..2757788bd4 100644 --- a/modules/build/src/main/scala/scala/build/CrossSources.scala +++ b/modules/build/src/main/scala/scala/build/CrossSources.scala @@ -273,7 +273,10 @@ object CrossSources { val resourceDirectoriesFromDirectives = { val resourceDirsFromCli = - allInputs.elements.flatMap { case rd: ResourceDirectory => Some(rd.path); case _ => None } + allInputs.elements.flatMap { + case rd: ResourceDirectory => Some(rd.path) + case _ => None + } val resourceDirsFromBuildOptions: Seq[os.Path] = buildOptions.flatMap(_.value.classPathOptions.resourcesDir).distinct resourceDirsFromBuildOptions @@ -318,7 +321,7 @@ object CrossSources { } val resourceDirs: Seq[WithBuildRequirements[os.Path]] = - resolveResourceDirs(finalInputs, preprocessedSources) + resolveResourceDirs(allInputs, preprocessedSources) lazy val allPathsWithDirectivesByScope: Map[Scope, Seq[(os.Path, Position.File)]] = (pathsWithDirectivePositions ++ inMemoryWithDirectivePositions ++ unwrappedScriptsWithDirectivePositions) diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala index e62528fa4b..1f115bd91a 100644 --- a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -389,6 +389,21 @@ class DirectiveTests extends TestUtil.ScalaCliBuildSuite { expect(resourceDirs == Seq(path)) } } + test("do not include test.resourceDir into sources for main scope") { + val testInputs = TestInputs( + os.rel / "simple.sc" -> + """//> using test.resourceDir foo + |""".stripMargin + ) + testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt, scope = Scope.Main) { + (root, _, maybeBuild) => + val build = + maybeBuild.toOption.flatMap(_.successfulOpt).getOrElse(sys.error("cannot happen")) + val resourceDirs = build.sources.resourceDirs + + expect(resourceDirs.isEmpty) + } + } test("parse boolean for publish.doc") { val testInputs = TestInputs( os.rel / "simple.sc" -> From 0436edc76776b3a9ec16c75e5c8e69d40acf2b71 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 2 Oct 2025 10:18:00 +0200 Subject: [PATCH 2/2] Fix `--watch` logic to track changes to resource directories added by directives, while the resource directories' scope stays respected; add tests & refactor --- .../src/main/scala/scala/build/Build.scala | 10 ++++++--- .../main/scala/scala/build/CrossSources.scala | 20 +++-------------- .../scala/build/tests/DirectiveTests.scala | 2 +- .../cli/integration/RunTestDefinitions.scala | 20 +++++++++++++++-- .../RunWithWatchTestDefinitions.scala | 22 ++++++++++++++++--- 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/modules/build/src/main/scala/scala/build/Build.scala b/modules/build/src/main/scala/scala/build/Build.scala index e53127fea8..80b9681bdb 100644 --- a/modules/build/src/main/scala/scala/build/Build.scala +++ b/modules/build/src/main/scala/scala/build/Build.scala @@ -778,15 +778,19 @@ object Build { val watcher = new Watcher(ListBuffer(), threads.fileWatcher, run(), info.foreach(_._1.shutdown())) - def doWatch(): Unit = { + def doWatch(): Unit = either { + val (crossSources: CrossSources, inputs0: Inputs) = + value(allInputs(inputs, options, logger)) val elements: Seq[Element] = - if res == null then inputs.elements + if res == null then inputs0.elements else res .map { builds => + val allResourceDirectories = + crossSources.resourceDirs.map(rd => ResourceDirectory(rd.value)) val mainElems = builds.main.inputs.elements val testElems = builds.get(Scope.Test).map(_.inputs.elements).getOrElse(Nil) - (mainElems ++ testElems).distinct + (mainElems ++ testElems ++ allResourceDirectories).distinct } .getOrElse(inputs.elements) for (elem <- elements) { diff --git a/modules/build/src/main/scala/scala/build/CrossSources.scala b/modules/build/src/main/scala/scala/build/CrossSources.scala index 2757788bd4..ee9b7e6674 100644 --- a/modules/build/src/main/scala/scala/build/CrossSources.scala +++ b/modules/build/src/main/scala/scala/build/CrossSources.scala @@ -271,22 +271,8 @@ object CrossSources { ) }).flatten - val resourceDirectoriesFromDirectives = { - val resourceDirsFromCli = - allInputs.elements.flatMap { - case rd: ResourceDirectory => Some(rd.path) - case _ => None - } - val resourceDirsFromBuildOptions: Seq[os.Path] = - buildOptions.flatMap(_.value.classPathOptions.resourcesDir).distinct - resourceDirsFromBuildOptions - .filter(!resourceDirsFromCli.contains(_)) - .map(ResourceDirectory(_)) - } - val finalInputs = allInputs.add(resourceDirectoriesFromDirectives) - val defaultMainElemPath = for { - defaultMainElem <- finalInputs.defaultMainClassElement + defaultMainElem <- allInputs.defaultMainClassElement } yield defaultMainElem.path val pathsWithDirectivePositions @@ -296,7 +282,7 @@ object CrossSources { val baseReqs0 = baseReqs(d.scopePath) WithBuildRequirements( d.requirements.fold(baseReqs0)(_ orElse baseReqs0), - (d.path, d.path.relativeTo(finalInputs.workspace)) + (d.path, d.path.relativeTo(allInputs.workspace)) ) -> d.directivesPositions } val inMemoryWithDirectivePositions @@ -382,7 +368,7 @@ object CrossSources { buildOptions, unwrappedScripts ) - crossSources -> finalInputs + crossSources -> allInputs } extension (uri: java.net.URI) diff --git a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala index 1f115bd91a..28c4d7bcc5 100644 --- a/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/DirectiveTests.scala @@ -396,7 +396,7 @@ class DirectiveTests extends TestUtil.ScalaCliBuildSuite { |""".stripMargin ) testInputs.withBuild(baseOptions, buildThreads, bloopConfigOpt, scope = Scope.Main) { - (root, _, maybeBuild) => + (_, _, maybeBuild) => val build = maybeBuild.toOption.flatMap(_.successfulOpt).getOrElse(sys.error("cannot happen")) val resourceDirs = build.sources.resourceDirs diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala index ab84b78478..e678b4ada6 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -567,9 +567,9 @@ abstract class RunTestDefinitions ): TestInputs = TestInputs( os.rel / "src" / "proj" / "resources" / "test" / "data" -> resourceContent, - os.rel / "src" / "proj" / "Test.scala" -> + os.rel / "src" / "proj" / "Example.scala" -> s"""$directive - |object Test { + |object Example { | def main(args: Array[String]): Unit = { | val cl = Thread.currentThread().getContextClassLoader | val is = cl.getResourceAsStream("test/data") @@ -599,6 +599,22 @@ abstract class RunTestDefinitions expect(res.out.trim() == expectedMessage) } } + test("resources via test directive") { + val expectedMessage = "hello" + resourcesInputs( + directive = "//> using test.resourceDirs ./resources", + resourceContent = expectedMessage + ) + .fromRoot { root => + val err = os.proc(TestUtil.cli, "run", ".") + .call(cwd = root, check = false, stderr = os.Pipe) + expect(err.err.trim().contains("java.lang.NullPointerException")) + expect(err.exitCode == 1) + val res = os.proc(TestUtil.cli, "run", ".", "--test") + .call(cwd = root) + expect(res.out.trim() == expectedMessage) + } + } def argsAsIsTest(): Unit = { val inputs = TestInputs( diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala index b3feb4d720..366aaf7b0a 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala @@ -345,18 +345,34 @@ trait RunWithWatchTestDefinitions { _: RunTestDefinitions => for { useDirective <- Seq(false, true) + testScope <- if (useDirective) Seq(false, true) else Seq(false) + scopeString = if (testScope) "test" else "main" // TODO make this pass reliably on Mac CI if !Properties.isMac || !TestUtil.isCI - directive = if (useDirective) "//> using resourceDirs ./resources" else "" + directive = + useDirective -> testScope match { + case (true, true) => "//> using test.resourceDirs ./resources" + case (true, false) => "//> using resourceDirs ./resources" + case _ => "" + } resourceOptions = if (useDirective) Nil else Seq("--resource-dirs", "./src/proj/resources") + scopeOptions = if (testScope) Seq("--test") else Nil title = if (useDirective) "directive" else "command line" - } test(s"resources via $title with --watch") { + } test(s"resources via $title with --watch ($scopeString)") { val expectedMessage1 = "Hello" val expectedMessage2 = "world" resourcesInputs(directive = directive, resourceContent = expectedMessage1) .fromRoot { root => TestUtil.withProcessWatching( - os.proc(TestUtil.cli, "run", "src", "--watch", resourceOptions, extraOptions) + os.proc( + TestUtil.cli, + "run", + "src", + "--watch", + resourceOptions, + scopeOptions, + extraOptions + ) .spawn(cwd = root, stderr = os.Pipe) ) { (proc, timeout, ec) => val output1 = TestUtil.readLine(proc.stdout, ec, timeout)