diff --git a/modules/build/src/main/scala/scala/build/CrossSources.scala b/modules/build/src/main/scala/scala/build/CrossSources.scala index c7e731752a..2151abc135 100644 --- a/modules/build/src/main/scala/scala/build/CrossSources.scala +++ b/modules/build/src/main/scala/scala/build/CrossSources.scala @@ -190,9 +190,14 @@ object CrossSources { val flattenedInputs = inputs.flattened() val allExclude = { // supports only one exclude directive in one source file, which should be the project file. - val projectScalaFileOpt = flattenedInputs.collectFirst { + val projectScalaFileCandidates = flattenedInputs.collect { case f: ProjectScalaFile => f } + + // this relies upon the inferred workspace root being the outermost directory. + // which is the common case when you pass a single directory. + val projectScalaFileOpt = + projectScalaFileCandidates.sortBy(_.subPath.segments.size).headOption val excludeFromProjectFile = value(preprocessSources(projectScalaFileOpt.toSeq)) .flatMap(_.options).flatMap(_.internal.exclude) diff --git a/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala b/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala index bdd68dce15..aabc59a7b9 100644 --- a/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala @@ -213,4 +213,40 @@ class ExcludeTests extends TestUtil.ScalaCliBuildSuite { } } + test("exclude nested scala-cli project") { + val testInputs = TestInputs( + os.rel / "Hello.scala" -> "object Hello", + // this project.scala needs to come first so that the inferred workspace is the outermost one. + os.rel / "project.scala" -> + """//> using exclude */examples/*""", + os.rel / "examples" / "fail.scala" -> + """val i: Int = "abc";""", + os.rel / "examples" / "project.scala" -> + """val unused = 23""" + ) + testInputs.withInputs { (root, inputs) => + val (crossSources, _) = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + )(using ScalaCliInvokeData.dummy).orThrow + val scopedSources = crossSources.scopedSources(BuildOptions()) + .orThrow + val sources = + scopedSources.sources( + Scope.Main, + crossSources.sharedOptions(BuildOptions()), + root, + TestLogger() + ) + .orThrow + + expect(sources.paths.nonEmpty) + expect(sources.paths.length == 2) + expect(sources.paths.map(_._2) == Seq(os.rel / "Hello.scala", os.rel / "project.scala")) + } + } + }