diff --git a/modules/build/src/main/scala/scala/build/Build.scala b/modules/build/src/main/scala/scala/build/Build.scala index 6cf00cad12..387906d7b9 100644 --- a/modules/build/src/main/scala/scala/build/Build.scala +++ b/modules/build/src/main/scala/scala/build/Build.scala @@ -139,7 +139,8 @@ object Build { logger: Logger, buildClient: BloopBuildClient, bloopServer: bloop.BloopServer, - crossBuilds: Boolean + crossBuilds: Boolean, + isTest: Boolean ): Either[BuildException, Builds] = either { val crossSources = value { @@ -170,7 +171,7 @@ object Build { def doBuild( overrideOptions: BuildOptions - ): Either[BuildException, (Build, Build)] = either { + ): Either[BuildException, (Build, Option[Build])] = either { val baseOptions = overrideOptions.orElse(sharedOptions) val scopedSources = value(crossSources.scopedSources(baseOptions)) @@ -212,7 +213,8 @@ object Build { scope, logger, buildClient, - bloopServer + bloopServer, + isTest ) value(res) @@ -220,33 +222,38 @@ object Build { val mainBuild = value(doBuildScope(mainOptions, mainSources, Scope.Main)) - val testBuild = value { - mainBuild match { - case s: Build.Successful => - val extraTestOptions = BuildOptions( - classPathOptions = ClassPathOptions( - extraClassPath = Seq(s.output) - ) - ) - val testOptions0 = extraTestOptions.orElse(testOptions) - doBuildScope(testOptions0, testSources, Scope.Test) - case _ => - Right(Build.Cancelled( - inputs, - sharedOptions, - Scope.Test, - "Parent build failed or cancelled" - )) + val testBuildOpt = + if (isTest) { + val testBuild = value { + mainBuild match { + case s: Build.Successful => + val extraTestOptions = BuildOptions( + classPathOptions = ClassPathOptions( + extraClassPath = Seq(s.output) + ) + ) + val testOptions0 = extraTestOptions.orElse(testOptions) + doBuildScope(testOptions0, testSources, Scope.Test) + case _ => + Right(Build.Cancelled( + inputs, + sharedOptions, + Scope.Test, + "Parent build failed or cancelled" + )) + } + } + Some(testBuild) } - } + else None doPostProcess(mainBuild, inputs0, Scope.Main) - doPostProcess(testBuild, inputs0, Scope.Test) + if (testBuildOpt.nonEmpty) doPostProcess(testBuildOpt.get, inputs0, Scope.Test) - (mainBuild, testBuild) + (mainBuild, testBuildOpt) } - def buildScopes(): Either[BuildException, (Build, Seq[Build], Build, Seq[Build])] = + def buildScopes(): Either[BuildException, (Build, Seq[Build], Option[Build], Seq[Build])] = either { val (mainBuild, testBuild) = value(doBuild(BuildOptions())) @@ -259,7 +266,7 @@ object Build { .sequence .left.map(CompositeBuildException(_)) } - (extraBuilds.map(_._1), extraBuilds.map(_._2)) + (extraBuilds.map(_._1), extraBuilds.flatMap(_._2)) } else (Nil, Nil) @@ -267,12 +274,12 @@ object Build { (mainBuild, extraMainBuilds, testBuild, extraTestBuilds) } - val (mainBuild, extraBuilds, testBuild, extraTestBuilds) = value(buildScopes()) + val (mainBuild, extraBuilds, testBuildOpt, extraTestBuilds) = value(buildScopes()) copyResourceToClassesDir(mainBuild) - copyResourceToClassesDir(testBuild) + if (testBuildOpt.nonEmpty) copyResourceToClassesDir(testBuildOpt.get) - Builds(Seq(mainBuild, testBuild), Seq(extraBuilds, extraTestBuilds)) + Builds(Seq(mainBuild) ++ testBuildOpt.toSeq, Seq(extraBuilds, extraTestBuilds)) } private def copyResourceToClassesDir(build: Build) = build match { @@ -304,7 +311,8 @@ object Build { scope: Scope, logger: Logger, buildClient: BloopBuildClient, - bloopServer: bloop.BloopServer + bloopServer: bloop.BloopServer, + isTest: Boolean ): Either[BuildException, Build] = either { val build0 = value { @@ -331,7 +339,8 @@ object Build { logger, successful.options.javaHome().value.javaCommand, buildClient, - bloopServer + bloopServer, + isTest ) res.flatMap { case Some(b) => Right(b) @@ -390,7 +399,8 @@ object Build { threads: BuildThreads, bloopConfig: BloopRifleConfig, logger: Logger, - crossBuilds: Boolean + crossBuilds: Boolean, + isTest: Boolean ): Either[BuildException, Builds] = { val buildClient = BloopBuildClient.create( logger, @@ -414,7 +424,8 @@ object Build { logger = logger, buildClient = buildClient, bloopServer = bloopServer, - crossBuilds = crossBuilds + crossBuilds = crossBuilds, + isTest ) } } @@ -424,14 +435,16 @@ object Build { options: BuildOptions, bloopConfig: BloopRifleConfig, logger: Logger, - crossBuilds: Boolean + crossBuilds: Boolean, + isTest: Boolean ): Either[BuildException, Builds] = build( inputs, options, /*scope,*/ BuildThreads.create(), bloopConfig, logger, - crossBuilds = crossBuilds + crossBuilds = crossBuilds, + isTest ) def validate( @@ -452,7 +465,8 @@ object Build { bloopConfig: BloopRifleConfig, logger: Logger, crossBuilds: Boolean, - postAction: () => Unit = () => () + postAction: () => Unit = () => (), + isTest: Boolean )(action: Either[BuildException, Builds] => Unit): Watcher = { val buildClient = BloopBuildClient.create( @@ -480,7 +494,8 @@ object Build { logger, buildClient, bloopServer, - crossBuilds = crossBuilds + crossBuilds = crossBuilds, + isTest ) action(res) } @@ -909,7 +924,8 @@ object Build { logger: Logger, javaCommand: String, buildClient: BloopBuildClient, - bloopServer: bloop.BloopServer + bloopServer: bloop.BloopServer, + isTest: Boolean ): Either[BuildException, Option[Build]] = either { val jmhProjectName = inputs.projectName + "_jmh" val jmhOutputDir = inputs.workspace / Constants.workspaceDirName / jmhProjectName @@ -955,7 +971,8 @@ object Build { logger, buildClient, bloopServer, - crossBuilds = false + crossBuilds = false, + isTest ) } Some(jmhBuilds.main) diff --git a/modules/build/src/main/scala/scala/build/options/TestOptions.scala b/modules/build/src/main/scala/scala/build/options/TestOptions.scala index c9ffe5a273..01dedefe2b 100644 --- a/modules/build/src/main/scala/scala/build/options/TestOptions.scala +++ b/modules/build/src/main/scala/scala/build/options/TestOptions.scala @@ -1,7 +1,8 @@ package scala.build.options final case class TestOptions( - frameworkOpt: Option[String] = None + frameworkOpt: Option[String] = None, + shouldCompileTest: Option[Boolean] = None ) object TestOptions { diff --git a/modules/build/src/test/scala/scala/build/tests/TestInputs.scala b/modules/build/src/test/scala/scala/build/tests/TestInputs.scala index 0659a3b4f7..53ba23e585 100644 --- a/modules/build/src/test/scala/scala/build/tests/TestInputs.scala +++ b/modules/build/src/test/scala/scala/build/tests/TestInputs.scala @@ -39,7 +39,8 @@ final case class TestInputs( buildThreads, bloopConfig, TestLogger(), - crossBuilds = false + crossBuilds = false, + isTest = true ) f(root, inputs, res.map(_.main)) } diff --git a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala index 4f058f6c00..fa273c7841 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala @@ -67,7 +67,8 @@ object Compile extends ScalaCommand[CompileOptions] { bloopRifleConfig, logger, crossBuilds = cross, - postAction = () => WatchUtil.printWatchMessage() + postAction = () => WatchUtil.printWatchMessage(), + isTest = buildOptions.testOptions.shouldCompileTest.getOrElse(false) ) { res => for (builds <- res.orReport(logger)) postBuild(builds, allowExit = false) @@ -81,7 +82,8 @@ object Compile extends ScalaCommand[CompileOptions] { buildOptions, bloopRifleConfig, logger, - crossBuilds = cross + crossBuilds = cross, + isTest = buildOptions.testOptions.shouldCompileTest.getOrElse(false) ) val builds = res.orExit(logger) postBuild(builds, allowExit = true) diff --git a/modules/cli/src/main/scala/scala/cli/commands/CompileOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/CompileOptions.scala index 86df36bb72..3818e46254 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/CompileOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/CompileOptions.scala @@ -18,12 +18,15 @@ final case class CompileOptions( @Name("p") @Name("classpath") @HelpMessage("Print the resulting class path") - classPath: Boolean = false + classPath: Boolean = false, + @HelpMessage("Compile test scope") + test: Boolean = false ) { // format: on def buildOptions: BuildOptions = - shared.buildOptions(enableJmh = false, jmhVersion = None) + shared.buildOptions(enableJmh = false, jmhVersion = None, shouldCompileTest = test) + } object CompileOptions { diff --git a/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala b/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala index 9434b45f38..07fa0a9980 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Metabrowse.scala @@ -31,7 +31,14 @@ object Metabrowse extends ScalaCommand[MetabrowseOptions] { val bloopRifleConfig = options.shared.bloopRifleConfig() val builds = - Build.build(inputs, options.buildOptions, bloopRifleConfig, logger, crossBuilds = false) + Build.build( + inputs, + options.buildOptions, + bloopRifleConfig, + logger, + crossBuilds = false, + isTest = false + ) .orExit(logger) val successfulBuild = builds.main match { diff --git a/modules/cli/src/main/scala/scala/cli/commands/Package.scala b/modules/cli/src/main/scala/scala/cli/commands/Package.scala index 1920cd4dc0..837dbd78a0 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Package.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Package.scala @@ -54,7 +54,8 @@ object Package extends ScalaCommand[PackageOptions] { bloopRifleConfig, logger, crossBuilds = cross, - postAction = () => WatchUtil.printWatchMessage() + postAction = () => WatchUtil.printWatchMessage(), + isTest = false ) { res => res.orReport(logger).map(_.main).foreach { case s: Build.Successful => @@ -69,7 +70,14 @@ object Package extends ScalaCommand[PackageOptions] { } else { val builds = - Build.build(inputs, initialBuildOptions, bloopRifleConfig, logger, crossBuilds = cross) + Build.build( + inputs, + initialBuildOptions, + bloopRifleConfig, + logger, + crossBuilds = cross, + isTest = false + ) .orExit(logger) builds.main match { case s: Build.Successful => diff --git a/modules/cli/src/main/scala/scala/cli/commands/Repl.scala b/modules/cli/src/main/scala/scala/cli/commands/Repl.scala index 0cf004d7ef..63b2f02297 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Repl.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Repl.scala @@ -83,7 +83,8 @@ object Repl extends ScalaCommand[ReplOptions] { bloopRifleConfig, logger, crossBuilds = cross, - postAction = () => WatchUtil.printWatchMessage() + postAction = () => WatchUtil.printWatchMessage(), + isTest = false ) { res => for (builds <- res.orReport(logger)) builds.main match { @@ -98,7 +99,14 @@ object Repl extends ScalaCommand[ReplOptions] { } else { val builds = - Build.build(inputs, initialBuildOptions, bloopRifleConfig, logger, crossBuilds = cross) + Build.build( + inputs, + initialBuildOptions, + bloopRifleConfig, + logger, + crossBuilds = cross, + isTest = false + ) .orExit(logger) builds.main match { case s: Build.Successful => diff --git a/modules/cli/src/main/scala/scala/cli/commands/Run.scala b/modules/cli/src/main/scala/scala/cli/commands/Run.scala index 6e2738f1e9..15fddc8e05 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Run.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Run.scala @@ -69,7 +69,8 @@ object Run extends ScalaCommand[RunOptions] { bloopRifleConfig, logger, crossBuilds = cross, - postAction = () => WatchUtil.printWatchMessage() + postAction = () => WatchUtil.printWatchMessage(), + isTest = false ) { res => res.orReport(logger).map(_.main).foreach { case s: Build.Successful => @@ -84,7 +85,14 @@ object Run extends ScalaCommand[RunOptions] { } else { val builds = - Build.build(inputs, initialBuildOptions, bloopRifleConfig, logger, crossBuilds = cross) + Build.build( + inputs, + initialBuildOptions, + bloopRifleConfig, + logger, + crossBuilds = cross, + isTest = false + ) .orExit(logger) builds.main match { case s: Build.Successful => diff --git a/modules/cli/src/main/scala/scala/cli/commands/SharedOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/SharedOptions.scala index 1ee7e0b181..eb70b76ea9 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/SharedOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/SharedOptions.scala @@ -110,7 +110,8 @@ final case class SharedOptions( def buildOptions( enableJmh: Boolean, jmhVersion: Option[String], - ignoreErrors: Boolean = false + ignoreErrors: Boolean = false, + shouldCompileTest: Boolean = false ): BuildOptions = { val platformOpt = if (js.js) Some(Platform.JS) @@ -166,7 +167,8 @@ final case class SharedOptions( cache = Some(coursierCache), localRepository = LocalRepo.localRepo(directories.directories.localRepoDir), verbosity = Some(logging.verbosity) - ) + ), + testOptions = scala.build.options.TestOptions(shouldCompileTest = Some(shouldCompileTest)) ) } diff --git a/modules/cli/src/main/scala/scala/cli/commands/Test.scala b/modules/cli/src/main/scala/scala/cli/commands/Test.scala index 6a1dccb372..3d49bf088e 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Test.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Test.scala @@ -100,7 +100,8 @@ object Test extends ScalaCommand[TestOptions] { bloopRifleConfig, logger, crossBuilds = cross, - postAction = () => WatchUtil.printWatchMessage() + postAction = () => WatchUtil.printWatchMessage(), + isTest = true ) { res => for (builds <- res.orReport(logger)) maybeTest(builds, allowExit = false) @@ -110,7 +111,14 @@ object Test extends ScalaCommand[TestOptions] { } else { val builds = - Build.build(inputs, initialBuildOptions, bloopRifleConfig, logger, crossBuilds = cross) + Build.build( + inputs, + initialBuildOptions, + bloopRifleConfig, + logger, + crossBuilds = cross, + isTest = true + ) .orExit(logger) maybeTest(builds, allowExit = true) } diff --git a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala index 7a186684da..6ead105026 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala @@ -15,7 +15,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) val simpleInputs = TestInputs( Seq( - os.rel / "MyTests.scala" -> + os.rel / "MyTests.test.scala" -> """//> using lib "com.lihaoyi::utest::0.7.10" |import utest._ | @@ -33,7 +33,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) test("no arg") { simpleInputs.fromRoot { root => - os.proc(TestUtil.cli, "compile", extraOptions, ".").call(cwd = root).out.text() + os.proc(TestUtil.cli, "compile", "--test", extraOptions, ".").call(cwd = root).out.text() } } @@ -70,7 +70,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) | } |} |""".stripMargin, - os.rel / "Tests.scala" -> + os.rel / "Tests.test.scala" -> """//> using lib "com.lihaoyi::pprint:0.6.6" |//> using target.scope "test" | @@ -87,7 +87,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) ) ) inputs.fromRoot { root => - os.proc(TestUtil.cli, "compile", extraOptions, ".").call(cwd = root) + os.proc(TestUtil.cli, "compile", "--test", extraOptions, ".").call(cwd = root) } } @@ -101,7 +101,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) | println(message) |} |""".stripMargin, - os.rel / "Tests.scala" -> + os.rel / "Tests.test.scala" -> """//> using lib "com.lihaoyi::utest:0.7.10" |//> using target.scope "test" | @@ -119,7 +119,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) ) ) inputs.fromRoot { root => - val res = os.proc(TestUtil.cli, "compile", extraOptions, ".") + val res = os.proc(TestUtil.cli, "compile", "--test", extraOptions, ".") .call(cwd = root, check = false, stderr = os.Pipe, mergeErrIntoOut = true) expect(res.exitCode == 1) val expectedInOutput = diff --git a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala index c938a115e5..eab4068e5c 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala @@ -300,4 +300,40 @@ abstract class PackageTestDefinitions(val scalaVersionOpt: Option[String]) } } + test("ignore test scope") { + val inputs = TestInputs( + Seq( + os.rel / "Main.scala" -> + """|object Main { + | def main(args: Array[String]): Unit = { + | println("Hello World") + | } + |}""".stripMargin, + os.rel / "Tests.test.scala" -> + """|import utest._ // compilation error, not included test library + | + |object Tests extends TestSuite { + | val tests = Tests { + | test("message") { + | assert(1 == 1) + | } + | } + |}""".stripMargin + ) + ) + inputs.fromRoot { root => + os.proc(TestUtil.cli, "package", extraOptions, ".").call( + cwd = root, + stdin = os.Inherit, + stdout = os.Inherit + ) + + val outputName = if (Properties.isWin) "app.bat" else "app" + val launcher = root / outputName + + val output = os.proc(launcher.toString).call(cwd = root).out.text().trim + expect(output == "Hello World") + } + } + } diff --git a/website/docs/commands/compile.md b/website/docs/commands/compile.md index 558f543cbe..aa928f91e4 100644 --- a/website/docs/commands/compile.md +++ b/website/docs/commands/compile.md @@ -24,6 +24,13 @@ The most common `compile` options are shown below. For a full list of options, run `scala compile --help`, or check the options linked in the [reference documentation](../reference/commands.md#compile). +## Test scope + +`--test` makes `scala-cli` compile main and test scopes: +```bash ignore +scala-cli compile --test Hello.scala +``` + ## Watch mode `--watch` makes `scala-cli` watch your code for changes, and re-compiles it upon any change: diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 967f73e3a0..ce264c6773 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -169,6 +169,10 @@ Aliases: `-p`, `--classpath` Print the resulting class path +#### `--test` + +Compile test scope + ## Compile cross options Available in commands: