diff --git a/modules/build/src/main/scala/scala/build/Build.scala b/modules/build/src/main/scala/scala/build/Build.scala index 79b1c0a69c..39db8ac0f2 100644 --- a/modules/build/src/main/scala/scala/build/Build.scala +++ b/modules/build/src/main/scala/scala/build/Build.scala @@ -233,7 +233,8 @@ object Build { () => options.javaHome().value.javaCommand ), logger, - options.suppressWarningOptions + options.suppressWarningOptions, + options.internal.exclude ) } val sharedOptions = crossSources.sharedOptions(options) diff --git a/modules/build/src/main/scala/scala/build/CrossSources.scala b/modules/build/src/main/scala/scala/build/CrossSources.scala index f2a54ca24c..c3777ffc47 100644 --- a/modules/build/src/main/scala/scala/build/CrossSources.scala +++ b/modules/build/src/main/scala/scala/build/CrossSources.scala @@ -1,12 +1,20 @@ package scala.build +import java.io.File + import scala.build.EitherCps.{either, value} import scala.build.Ops.* import scala.build.Positioned -import scala.build.errors.{BuildException, CompositeBuildException, MalformedDirectiveError} +import scala.build.errors.{ + BuildException, + CompositeBuildException, + ExcludeDefinitionError, + MalformedDirectiveError +} import scala.build.input.ElementsUtils.* import scala.build.input.* import scala.build.internal.Constants +import scala.build.internal.util.RegexUtils import scala.build.options.{ BuildOptions, BuildRequirements, @@ -16,6 +24,9 @@ import scala.build.options.{ WithBuildRequirements } import scala.build.preprocessing.* +import scala.build.testrunner.DynamicTestRunner.globPattern +import scala.util.Try +import scala.util.chaining.* final case class CrossSources( paths: Seq[WithBuildRequirements[(os.Path, os.RelPath)]], @@ -128,6 +139,7 @@ object CrossSources { preprocessors: Seq[Preprocessor], logger: Logger, suppressWarningOptions: SuppressWarningOptions, + exclude: Seq[Positioned[String]] = Nil, maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e) ): Either[BuildException, (CrossSources, Inputs)] = either { @@ -155,8 +167,21 @@ object CrossSources { .left.map(CompositeBuildException(_)) .map(_.flatten) + 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 { + case f: ProjectScalaFile => f + } + val excludeFromProjectFile = + value(preprocessSources(projectScalaFileOpt.toSeq)) + .flatMap(_.options).flatMap(_.internal.exclude) + exclude ++ excludeFromProjectFile + } + val preprocessedInputFromArgs: Seq[PreprocessedSource] = - value(preprocessSources(inputs.flattened())) + value( + preprocessSources(value(excludeSources(flattenedInputs, inputs.workspace, allExclude))) + ) val sourcesFromDirectives = preprocessedInputFromArgs @@ -166,11 +191,16 @@ object CrossSources { val inputsElemFromDirectives: Seq[SingleFile] = value(resolveInputsFromSources(sourcesFromDirectives, inputs.enableMarkdown)) val preprocessedSourcesFromDirectives: Seq[PreprocessedSource] = - value(preprocessSources(inputsElemFromDirectives)) - val allInputs = inputs.add(inputsElemFromDirectives) + value(preprocessSources(inputsElemFromDirectives.pipe(elements => + value(excludeSources(elements, inputs.workspace, allExclude)) + ))) + val allInputs = inputs.add(inputsElemFromDirectives).filter(elems => + value(excludeSources(elems, inputs.workspace, allExclude)) + ) val preprocessedSources = (preprocessedInputFromArgs ++ preprocessedSourcesFromDirectives).distinct + .pipe(sources => value(validateExcludeDirectives(sources, allInputs.workspace))) val scopedRequirements = preprocessedSources.flatMap(_.scopedRequirements) val scopedRequirementsByRoot = scopedRequirements.groupBy(_.path.root) @@ -305,4 +335,56 @@ object CrossSources { .left.map(CompositeBuildException(_)) .map(_.flatten) + /** Filters out the sources from the input sequence based on the provided 'exclude' patterns. The + * exclude patterns can be absolute paths, relative paths, or glob patterns. + * + * @throws BuildException + * If multiple 'exclude' patterns are defined across the input sources. + */ + private def excludeSources[E <: Element]( + elements: Seq[E], + workspaceDir: os.Path, + exclude: Seq[Positioned[String]] + ): Either[BuildException, Seq[E]] = either { + val excludePatterns = exclude.map(_.value).flatMap { p => + val maybeRelPath = Try(os.RelPath(p)).toOption + maybeRelPath match { + case Some(relPath) if os.isDir(workspaceDir / relPath) => + // exclude relative directory paths, add * to exclude all files in the directory + Seq(p, (workspaceDir / relPath / "*").toString) + case Some(relPath) => + Seq(p, (workspaceDir / relPath).toString) // exclude relative paths + case None => Seq(p) + } + } + + def isSourceIncluded(path: String, excludePatterns: Seq[String]): Boolean = + excludePatterns + .forall(pattern => !RegexUtils.globPattern(pattern).matcher(path).matches()) + + elements.filter { + case e: OnDisk => isSourceIncluded(e.path.toString, excludePatterns) + case _ => true + } + } + + /** Validates that exclude directives are defined only in the one source. + */ + def validateExcludeDirectives( + sources: Seq[PreprocessedSource], + workspaceDir: os.Path + ): Either[BuildException, Seq[PreprocessedSource]] = { + val excludeDirectives = sources.flatMap(_.options).map(_.internal.exclude).toList.flatten + + excludeDirectives match { + case Nil | Seq(_) => + Right(sources) + case _ => + val expectedProjectFilePath = workspaceDir / Constants.projectFileName + Left(new ExcludeDefinitionError( + excludeDirectives.flatMap(_.positions), + expectedProjectFilePath + )) + } + } } diff --git a/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala b/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala index f94e15c569..1aa8a73251 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspImpl.scala @@ -108,6 +108,7 @@ final class BspImpl( ), logger = persistentLogger, suppressWarningOptions = buildOptions.suppressWarningOptions, + exclude = buildOptions.internal.exclude, maybeRecoverOnError = maybeRecoverOnError(Scope.Main) ).left.map((_, Scope.Main)) } diff --git a/modules/build/src/main/scala/scala/build/input/Inputs.scala b/modules/build/src/main/scala/scala/build/input/Inputs.scala index 113213b2f2..331ee51981 100644 --- a/modules/build/src/main/scala/scala/build/input/Inputs.scala +++ b/modules/build/src/main/scala/scala/build/input/Inputs.scala @@ -65,6 +65,8 @@ final case class Inputs( def add(extraElements: Seq[Element]): Inputs = if elements.isEmpty then this else copy(elements = (elements ++ extraElements).distinct) + def filter(filterElements: Seq[Element] => Seq[Element]): Inputs = + copy(elements = filterElements(elements)) def generatedSrcRoot(scope: Scope): os.Path = workspace / Constants.workspaceDirName / projectName / "src_generated" / scope.name diff --git a/modules/build/src/main/scala/scala/build/internal/util/RegexUtils.scala b/modules/build/src/main/scala/scala/build/internal/util/RegexUtils.scala new file mode 100644 index 0000000000..c4c5abc061 --- /dev/null +++ b/modules/build/src/main/scala/scala/build/internal/util/RegexUtils.scala @@ -0,0 +1,23 @@ +package scala.build.internal.util + +import java.util.regex.Pattern + +object RegexUtils { + + /** Based on junit-interface [GlobFilter. + * compileGlobPattern](https://github.com/sbt/junit-interface/blob/f8c6372ed01ce86f15393b890323d96afbe6d594/src/main/java/com/novocode/junit/GlobFilter.java#L37) + * + * @return + * Pattern allows to regex input which contains only *, for example `*foo*` match to + * `MyTests.foo` + */ + def globPattern(expr: String): Pattern = { + val a = expr.split("\\*", -1) + val b = new StringBuilder() + for (i <- 0 until a.length) { + if (i != 0) b.append(".*") + if (a(i).nonEmpty) b.append(Pattern.quote(a(i).replaceAll("\n", "\\n"))) + } + Pattern.compile(b.toString) + } +} diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala index a09f003a07..74a8994ca4 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala @@ -67,6 +67,7 @@ case object ScalaPreprocessor extends Preprocessor { val usingDirectiveHandlers: Seq[DirectiveHandler[BuildOptions]] = Seq[DirectiveHandler[_ <: HasBuildOptions]]( + directives.Exclude.handler, directives.JavaHome.handler, directives.Jvm.handler, directives.MainClass.handler, diff --git a/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala b/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala new file mode 100644 index 0000000000..7d42e5f63a --- /dev/null +++ b/modules/build/src/test/scala/scala/build/tests/ExcludeTests.scala @@ -0,0 +1,183 @@ +package scala.build.tests + +import com.eed3si9n.expecty.Expecty.expect +import coursier.cache.{ArchiveCache, Cache} +import coursier.util.{Artifact, Task} + +import java.io.File +import scala.build.Ops.* +import scala.build.Sources +import scala.build.internal.CustomCodeWrapper +import scala.build.CrossSources +import scala.build.errors.ExcludeDefinitionError +import scala.build.options.{BuildOptions, Scope, SuppressWarningOptions} + +class ExcludeTests extends munit.FunSuite { + + val preprocessors = Sources.defaultPreprocessors( + CustomCodeWrapper, + ArchiveCache().withCache( + new Cache[Task] { + def fetch = _ => sys.error("shouldn't be used") + + def file(artifact: Artifact) = sys.error("shouldn't be used") + + def ec = sys.error("shouldn't be used") + } + ), + None, + () => sys.error("shouldn't be used") + ) + + test("throw error when exclude found in multiple file") { + val testInputs = TestInputs( + os.rel / "Hello.scala" -> + """//> using exclude "*.sc" + |""".stripMargin, + os.rel / "Main.scala" -> + """//> using exclude "*/test/*" + |""".stripMargin + ) + testInputs.withInputs { (_, inputs) => + val crossSources = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + ) + crossSources match { + case Left(_: ExcludeDefinitionError) => + case o => fail("Exception expected", clues(o)) + } + } + } + + test("throw error when exclude found in non top-level project.scala and file") { + val testInputs = TestInputs( + os.rel / "Main.scala" -> + """//> using exclude "*/test/*" + |""".stripMargin, + os.rel / "src" / "project.scala" -> + s"""//> using exclude "*.sc" """ + ) + testInputs.withInputs { (_, inputs) => + val crossSources = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + ) + crossSources match { + case Left(_: ExcludeDefinitionError) => + case o => fail("Exception expected", clues(o)) + } + } + } + + test("exclude relative paths") { + val testInputs = TestInputs( + os.rel / "Hello.scala" -> "object Hello", + os.rel / "Main.scala" -> + """object Main { + |}""".stripMargin, + os.rel / "project.scala" -> + s"""//> using exclude "Main.scala" """ + ) + testInputs.withInputs { (_, inputs) => + val (crossSources, _) = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + ).orThrow + val scopedSources = crossSources.scopedSources(BuildOptions()).orThrow + val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions())) + + expect(sources.paths.nonEmpty) + expect(sources.paths.length == 2) + expect(sources.paths.map(_._2) == Seq(os.rel / "Hello.scala", os.rel / "project.scala")) + } + } + + test("exclude absolute file paths") { + val testInputs = TestInputs( + os.rel / "Hello.scala" -> "object Hello", + os.rel / "Main.scala" -> + """object Main { + |}""".stripMargin, + os.rel / "project.scala" -> + s"""//> using exclude "$${.}${File.separator}Main.scala" """ + ) + testInputs.withInputs { (_, inputs) => + val (crossSources, _) = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + ).orThrow + val scopedSources = crossSources.scopedSources(BuildOptions()).orThrow + val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions())) + + expect(sources.paths.nonEmpty) + expect(sources.paths.length == 2) + expect(sources.paths.map(_._2) == Seq(os.rel / "Hello.scala", os.rel / "project.scala")) + } + } + + test("exclude relative directory paths") { + val testInputs = TestInputs( + os.rel / "Hello.scala" -> "object Hello", + os.rel / "src" / "scala" / "Main.scala" -> + """object Main { + |}""".stripMargin, + os.rel / "project.scala" -> + """//> using exclude "src/*.scala" """ + ) + testInputs.withInputs { (_, inputs) => + val (crossSources, _) = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + ).orThrow + val scopedSources = crossSources.scopedSources(BuildOptions()).orThrow + val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions())) + + expect(sources.paths.nonEmpty) + expect(sources.paths.length == 2) + expect(sources.paths.map(_._2) == Seq(os.rel / "Hello.scala", os.rel / "project.scala")) + } + } + + test("exclude relative directory paths with glob pattern") { + val testInputs = TestInputs( + os.rel / "Hello.scala" -> "object Hello", + os.rel / "src" / "scala" / "Main.scala" -> + """object Main { + |}""".stripMargin, + os.rel / "project.scala" -> + """//> using exclude "src/*.scala" """ + ) + testInputs.withInputs { (_, inputs) => + val (crossSources, _) = + CrossSources.forInputs( + inputs, + preprocessors, + TestLogger(), + SuppressWarningOptions() + ).orThrow + val scopedSources = crossSources.scopedSources(BuildOptions()).orThrow + val sources = scopedSources.sources(Scope.Main, crossSources.sharedOptions(BuildOptions())) + + expect(sources.paths.nonEmpty) + expect(sources.paths.length == 2) + expect(sources.paths.map(_._2) == Seq(os.rel / "Hello.scala", os.rel / "project.scala")) + } + } + +} diff --git a/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala b/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala index d45f247d89..e44d8e10fe 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala @@ -59,7 +59,8 @@ object Bsp extends ScalaCommand[BspOptions] { () => buildOptions0.javaHome().value.javaCommand ), persistentLogger, - buildOptions0.suppressWarningOptions + buildOptions0.suppressWarningOptions, + buildOptions0.internal.exclude ).map(_._2).getOrElse(initialInputs) Build.updateInputs(allInputs, buildOptions(sharedOptions)) diff --git a/modules/cli/src/main/scala/scala/cli/commands/dependencyupdate/DependencyUpdate.scala b/modules/cli/src/main/scala/scala/cli/commands/dependencyupdate/DependencyUpdate.scala index 5d1534e7f0..b5cc33410b 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/dependencyupdate/DependencyUpdate.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/dependencyupdate/DependencyUpdate.scala @@ -40,7 +40,8 @@ object DependencyUpdate extends ScalaCommand[DependencyUpdateOptions] { () => buildOptions.javaHome().value.javaCommand ), logger, - buildOptions.suppressWarningOptions + buildOptions.suppressWarningOptions, + buildOptions.internal.exclude ).orExit(logger) val scopedSources = crossSources.scopedSources(buildOptions).orExit(logger) diff --git a/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala b/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala index 55c8f6a0ea..ebb3e1d69e 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/export0/Export.scala @@ -50,7 +50,8 @@ object Export extends ScalaCommand[ExportOptions] { () => buildOptions.javaHome().value.javaCommand ), logger, - buildOptions.suppressWarningOptions + buildOptions.suppressWarningOptions, + buildOptions.internal.exclude ) } val scopedSources = value(crossSources.scopedSources(buildOptions)) diff --git a/modules/cli/src/main/scala/scala/cli/commands/publish/PublishSetup.scala b/modules/cli/src/main/scala/scala/cli/commands/publish/PublishSetup.scala index 1b5389dae9..fbebaf4719 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/publish/PublishSetup.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/publish/PublishSetup.scala @@ -90,7 +90,8 @@ object PublishSetup extends ScalaCommand[PublishSetupOptions] { () => cliBuildOptions.javaHome().value.javaCommand ), logger, - cliBuildOptions.suppressWarningOptions + cliBuildOptions.suppressWarningOptions, + cliBuildOptions.internal.exclude ).orExit(logger) val crossSourcesSharedOptions = crossSources.sharedOptions(cliBuildOptions) diff --git a/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala b/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala index 5b0e896787..8ba3f49f7b 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala @@ -40,7 +40,8 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] { () => options.javaHome().value.javaCommand ), logger, - options.suppressWarningOptions + options.suppressWarningOptions, + options.internal.exclude ) } diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala index d81d43d74f..72d92eab00 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala @@ -203,7 +203,9 @@ final case class SharedOptions( @Name("toolkit") @Tag(tags.implementation) @Tag(tags.inShortHelp) - withToolkit: Option[String] = None + withToolkit: Option[String] = None, + @HelpMessage("Exclude sources") + exclude: List[String] = Nil, ) extends HasGlobalOptions { // format: on @@ -363,7 +365,8 @@ final case class SharedOptions( localRepository = LocalRepo.localRepo(Directories.directories.localRepoDir), verbosity = Some(logging.verbosity), strictBloopJsonCheck = strictBloopJsonCheck, - interactive = Some(() => interactive) + interactive = Some(() => interactive), + exclude = exclude.map(Positioned.commandLine) ), notForBloopOptions = bo.PostBuildOptions( scalaJsLinkerOptions = linkerOptions(js), diff --git a/modules/core/src/main/scala/scala/build/errors/ExcludeDefinitionError.scala b/modules/core/src/main/scala/scala/build/errors/ExcludeDefinitionError.scala new file mode 100644 index 0000000000..20fc2a2351 --- /dev/null +++ b/modules/core/src/main/scala/scala/build/errors/ExcludeDefinitionError.scala @@ -0,0 +1,10 @@ +package scala.build.errors + +import scala.build.Position + +final class ExcludeDefinitionError(positions: Seq[Position], expectedProjectFilePath: os.Path) + extends BuildException( + s"""Found exclude directives in files: + | ${positions.map(_.render()).distinct.mkString(", ")} + |exclude directive must be defined in project configuration file: $expectedProjectFilePath.""".stripMargin + ) diff --git a/modules/directives/src/main/scala/scala/build/preprocessing/directives/Exclude.scala b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Exclude.scala new file mode 100644 index 0000000000..8293f331ce --- /dev/null +++ b/modules/directives/src/main/scala/scala/build/preprocessing/directives/Exclude.scala @@ -0,0 +1,38 @@ +package scala.build.preprocessing.directives + +import scala.build.EitherCps.{either, value} +import scala.build.Ops.* +import scala.build.directives.* +import scala.build.errors.{BuildException, CompositeBuildException, WrongSourcePathError} +import scala.build.options.{BuildOptions, InternalOptions, JavaOpt, ShadowingSeq} +import scala.build.{Logger, Positioned, options} +import scala.cli.commands.SpecificationLevel +import scala.util.Try + +@DirectiveGroupName("Exclude sources") +@DirectiveExamples("//> using exclude \"utils.scala\"") +@DirectiveUsage( + "`//> using exclude `_pattern_ | `//> using exclude `_pattern_, _pattern_ …", + """`//> using exclude "*.sc"` + | + |`//> using exclude "examples/*", "*/resources/*" …`""".stripMargin +) +@DirectiveDescription("Exclude sources from the project") +@DirectiveLevel(SpecificationLevel.SHOULD) +// format: off +final case class Exclude( + exclude: List[Positioned[String]] = Nil +) extends HasBuildOptions { +// format: on + def buildOptions: Either[BuildException, BuildOptions] = either { + BuildOptions( + internal = InternalOptions( + exclude = exclude + ) + ) + } +} + +object Exclude { + val handler: DirectiveHandler[Exclude] = DirectiveHandler.derive +} 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 f9846f61e5..a2588f780b 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -1327,4 +1327,23 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) expect(res3.out.trim().contains(s"$expectedMessage1$expectedMessage2")) } } + test("exclude file") { + val message = "Hello" + val inputs = TestInputs( + os.rel / "Hello.scala" -> + s"""object Hello extends App { + | println("$message") + |}""".stripMargin, + os.rel / "Main.scala" -> + """object Main { + | val msg: String = 1 // compilation fails + |}""".stripMargin + ) + inputs.fromRoot { root => + val res = + os.proc(TestUtil.cli, extraOptions, ".", "--exclude", "*Main.scala").call(cwd = root) + val output = res.out.trim() + expect(output == message) + } + } } diff --git a/modules/options/src/main/scala/scala/build/options/InternalOptions.scala b/modules/options/src/main/scala/scala/build/options/InternalOptions.scala index feb66a7f3b..f8efed0482 100644 --- a/modules/options/src/main/scala/scala/build/options/InternalOptions.scala +++ b/modules/options/src/main/scala/scala/build/options/InternalOptions.scala @@ -24,7 +24,8 @@ final case class InternalOptions( * really needed. */ keepResolution: Boolean = false, - extraSourceFiles: Seq[Positioned[os.Path]] = Nil + extraSourceFiles: Seq[Positioned[os.Path]] = Nil, + exclude: Seq[Positioned[String]] = Nil ) { def verbosityOrDefault: Int = verbosity.getOrElse(0) def strictBloopJsonCheckOrDefault: Boolean = diff --git a/website/docs/commands/compile.md b/website/docs/commands/compile.md index 63e0d606cd..491e765a2d 100644 --- a/website/docs/commands/compile.md +++ b/website/docs/commands/compile.md @@ -461,3 +461,25 @@ You can also add javac options with the using directive `//> using javacOpt`: ```scala compile //> using javacOpt "source", "1.8", "target", "1.8" ``` + +## Exclude sources + +To exclude specific source files or entire directories from a Scala CLI project, use the `exclude` directive or command +line parameter `--exclude` along with a pattern: + +- an absolute path: `/root/path/to/your/project/Main.scala` +- a relative path: `src/main/scala/Main.scala` +- a glob pattern: `*.sc` + +:::note +The `exclude` directive should be placed in your `project.scala` file, which Scala CLI uses to determine the project +root directory. +For more details on `project.file`, see [the `Project root directory` reference](/docs/reference/root-dir). +::: + +For example, to exclude all files in the `example/scala` directory, add the following directive to your + `project.file` file: + +```scala title=project.scala +//> using exclude "example/scala" +``` \ No newline at end of file diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index baa1a5b16b..06d2cd1fc5 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1497,6 +1497,10 @@ Aliases: `--toolkit` Add toolkit to classPath +### `--exclude` + +Exclude sources + ## Snippet options Available in commands: diff --git a/website/docs/reference/directives.md b/website/docs/reference/directives.md index 711ae15147..509ae4d218 100644 --- a/website/docs/reference/directives.md +++ b/website/docs/reference/directives.md @@ -68,6 +68,17 @@ Add dependencies `//> using dep "tabby:tabby:0.2.3,url=https://github.com/bjornregnell/tabby/releases/download/v0.2.3/tabby_3-0.2.3.jar"` +### Exclude sources + +Exclude sources from the project + +`//> using exclude "*.sc"` + +`//> using exclude "examples/*", "*/resources/*" …` + +#### Examples +`//> using exclude "utils.scala"` + ### JVM version Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 7926be7491..43ae19391d 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -1008,6 +1008,12 @@ Aliases: `--toolkit` Add toolkit to classPath +### `--exclude` + +`IMPLEMENTATION specific` per Scala Runner specification + +Exclude sources + ## Snippet options Available in commands: diff --git a/website/docs/reference/scala-command/directives.md b/website/docs/reference/scala-command/directives.md index 49131d76c6..34428cec45 100644 --- a/website/docs/reference/scala-command/directives.md +++ b/website/docs/reference/scala-command/directives.md @@ -123,6 +123,17 @@ Manually add sources to the project #### Examples `//> using file "utils.scala"` +### Exclude sources + +Exclude sources from the project + +`//> using exclude "*.sc"` + +`//> using exclude "examples/*", "*/resources/*" …` + +#### Examples +`//> using exclude "utils.scala"` + ### JVM version Use a specific JVM, such as `14`, `adopt:11`, or `graalvm:21`, or `system` diff --git a/website/docs/reference/scala-command/runner-specification.md b/website/docs/reference/scala-command/runner-specification.md index 03b27f3ca1..16a7ce6d94 100644 --- a/website/docs/reference/scala-command/runner-specification.md +++ b/website/docs/reference/scala-command/runner-specification.md @@ -572,6 +572,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + --- @@ -1266,6 +1270,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + --- @@ -1799,6 +1807,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -2362,6 +2374,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -2934,6 +2950,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -3464,6 +3484,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--respect-project-filters** Use project filters defined in the configuration. Turned on by default, use `--respect-project-filters:false` to disable it. @@ -4069,6 +4093,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--java-prop-option** Add java properties. Note that options equal `-Dproperty=value` are assumed to be java properties and don't require to be passed after `--java-prop`. @@ -4675,6 +4703,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--json-options** Command-line options JSON file @@ -5518,6 +5550,10 @@ Add toolkit to classPath Aliases: `--toolkit` +**--exclude** + +Exclude sources + **--bsp-directory** Custom BSP configuration location