From 9b074b08715329dbfcbc57af284723d06da479d0 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 8 Sep 2022 12:22:41 +0200 Subject: [PATCH 1/5] Rename the `--classpath` Scala CLI compile option to `--print-classpath` --- .../src/main/scala/scala/cli/commands/CompileOptions.scala | 4 ++-- modules/cli/src/main/scala/scala/cli/commands/Compile.scala | 6 +++--- .../scala/cli/integration/CompileTestDefinitions.scala | 4 ++-- .../scala/scala/cli/integration/RunTestDefinitions.scala | 2 +- .../test/scala/scala/cli/integration/TestTestsDefault.scala | 2 +- website/docs/commands/compile.md | 6 +++--- website/docs/reference/cli-options.md | 4 ++-- website/docs/reference/scala-command/cli-options.md | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala index a59ca1c1f1..8193f1c3ca 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala @@ -14,9 +14,9 @@ final case class CompileOptions( cross: CrossOptions = CrossOptions(), @Name("p") - @Name("classpath") + @Name("printClasspath") @HelpMessage("Print the resulting class path") - classPath: Boolean = false, + printClassPath: Boolean = false, @Name("output-directory") @HelpMessage("Copy compilation results to output directory using either relative or absolute path") 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 d063e5aed2..dd3770f070 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala @@ -36,8 +36,8 @@ object Compile extends ScalaCommand[CompileOptions] { Update.checkUpdateSafe(logger) val cross = options.cross.cross.getOrElse(false) - if (options.classPath && cross) { - System.err.println(s"Error: cannot specify both --class-path and --cross") + if (options.printClassPath && cross) { + System.err.println(s"Error: cannot specify both --print-class-path and --cross") sys.exit(1) } @@ -66,7 +66,7 @@ object Compile extends ScalaCommand[CompileOptions] { build <- builds.get(Scope.Test).orElse(builds.get(Scope.Main)) s <- build.successfulOpt } yield s - if (options.classPath) + if (options.printClassPath) for (s <- successulBuildOpt) { val cp = s.fullClassPath.map(_.toString).mkString(File.pathSeparator) println(cp) 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 2ddaf19e91..c061c30e06 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala @@ -109,7 +109,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) "--test", "--output", tempOutput, - "--class-path", + "--print-class-path", extraOptions, "." ).call(cwd = @@ -416,7 +416,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) os.proc( TestUtil.cli, "compile", - "--class-path", + "--print-class-path", extraOptions, "." ).call(cwd = root) 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 44073a3e62..4282bf2cbb 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -1267,7 +1267,7 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) // format: off val cmd = Seq[os.Shellable]( TestUtil.cli, "compile", extraOptions, - "--class-path", ".", + "--print-class-path", ".", if (inlineDelambdafy) Seq("-Ydelambdafy:inline") else Nil ) // format: on diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala b/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala index b1b844a8e1..3982680f6b 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestTestsDefault.scala @@ -33,7 +33,7 @@ class TestTestsDefault extends TestTestDefinitions(scalaVersionOpt = None) { ) inputs.fromRoot { root => - val compileRes = os.proc(TestUtil.cli, "compile", "--class-path", baseExtraOptions, ".") + val compileRes = os.proc(TestUtil.cli, "compile", "--print-class-path", baseExtraOptions, ".") .call(cwd = root) val cp = compileRes.out.trim().split(File.pathSeparator) expect(cp.length == 1) // only class dir, no scala JARs diff --git a/website/docs/commands/compile.md b/website/docs/commands/compile.md index 6fe70e19a9..dc98ef1c27 100644 --- a/website/docs/commands/compile.md +++ b/website/docs/commands/compile.md @@ -254,16 +254,16 @@ scala-cli compile Hello.scala --compiler-plugin org.typelevel:::kind-projector:0 ## Printing a class path -`--class-path` makes `scala-cli compile` print a class path: +`--print-class-path` makes `scala-cli compile` print a class path: ```bash -scala-cli compile --class-path Hello.scala +scala-cli compile --print-class-path Hello.scala # /work/.scala/project-cef76d561e/classes:~/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.14/scala-library-2.12.14.jar:~/Library/Caches/ScalaCli/local-repo/0.1.0/org.virtuslab.scala-cli/runner_2.12/0.0.1-SNAPSHOT/jars/runner_2.12.jar:~/Library/Caches/ScalaCli/local-repo/0.1.0/org.virtuslab.scala-cli/stubs/0.0.1-SNAPSHOT/jars/stubs.jar ``` This is handy when working with other tools. For example, you can pass this class path to `java -cp`: ```bash -java -cp "$(scala-cli compile --class-path Hello.scala)" Hello +java -cp "$(scala-cli compile --print-class-path Hello.scala)" Hello # Hello ``` diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index a987493b16..ecd7502fcf 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -113,9 +113,9 @@ Available in commands: -### `--class-path` +### `--print-class-path` -Aliases: `-p`, `--classpath` +Aliases: `-p`, `--print-classpath` Print the resulting class path diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 4799915c3b..2e6ee7f474 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -97,9 +97,9 @@ Available in commands: -### `--class-path` +### `--print-class-path` -Aliases: `-p`, `--classpath` +Aliases: `-p`, `--print-classpath` Print the resulting class path From a93f6bcbbb97af581ffad0cb6413be9f9fe371f1 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 8 Sep 2022 13:56:59 +0200 Subject: [PATCH 2/5] Change `-d` to an alias for the compile sub-command's `--output` option --- .../src/main/scala/scala/cli/commands/CompileOptions.scala | 2 ++ .../scala/scala/cli/commands/SharedDependencyOptions.scala | 1 - website/docs/reference/cli-options.md | 4 ++-- website/docs/reference/scala-command/cli-options.md | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala index 8193f1c3ca..6bf7df2799 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala @@ -19,6 +19,8 @@ final case class CompileOptions( printClassPath: Boolean = false, @Name("output-directory") + @Name("d") + @Name("destination") @HelpMessage("Copy compilation results to output directory using either relative or absolute path") @ValueDescription("/example/path") output: Option[String] = None, diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/SharedDependencyOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/SharedDependencyOptions.scala index 1a0a3f3443..618995df12 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/SharedDependencyOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/SharedDependencyOptions.scala @@ -9,7 +9,6 @@ final case class SharedDependencyOptions( @Group("Dependency") @HelpMessage("Add dependencies") @Name("dep") - @Name("d") dependency: List[String] = Nil, @Group("Dependency") diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index ecd7502fcf..71267ee092 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -121,7 +121,7 @@ Print the resulting class path ### `--output` -Aliases: `--output-directory` +Aliases: `--output-directory`, `-d`, `--destination` Copy compilation results to output directory using either relative or absolute path @@ -151,7 +151,7 @@ Available in commands: ### `--dependency` -Aliases: `--dep`, `-d` +Aliases: `--dep` Add dependencies diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 2e6ee7f474..48d2e871e5 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -105,7 +105,7 @@ Print the resulting class path ### `--output` -Aliases: `--output-directory` +Aliases: `--output-directory`, `-d`, `--destination` Copy compilation results to output directory using either relative or absolute path @@ -123,7 +123,7 @@ Available in commands: ### `--dependency` -Aliases: `--dep`, `-d` +Aliases: `--dep` Add dependencies From a183554ab0e9ef4245ad070e41dcf607efd68183 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 8 Sep 2022 14:25:23 +0200 Subject: [PATCH 3/5] Add extra aliases for the `--extra-jar` option to satisfy backwards compat with the `scala` command --- .../scala/cli/commands/SharedOptions.scala | 10 +++++- .../cli/integration/RunTestDefinitions.scala | 35 +++++++++++++++++++ website/docs/reference/cli-options.md | 4 +-- .../reference/scala-command/cli-options.md | 4 +-- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala index bba1ef8beb..8ab9e6ced6 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala @@ -55,11 +55,19 @@ final case class SharedOptions( markdown: MarkdownOptions = MarkdownOptions(), @Group("Java") - @HelpMessage("Add extra JARs in the class path") + @HelpMessage("Add extra JARs and compiled classes to the class path") @ValueDescription("paths") @Name("jar") @Name("jars") @Name("extraJar") + @Name("class") + @Name("extraClass") + @Name("classes") + @Name("extraClasses") + @Name("-classpath") + @Name("classpath") + @Name("classPath") + @Name("extraClassPath") extraJars: List[String] = Nil, @Group("Java") 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 4282bf2cbb..2baa83e880 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -2219,6 +2219,41 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) } } + test("-classpath allows to run with scala-cli compile -d option pre-compiled classes") { + val preCompileDir = "PreCompileDir" + val preCompiledInput = "Message.scala" + val runDir = "RunDir" + val mainInput = "Main.scala" + val expectedOutput = "Hello" + TestInputs( + os.rel / preCompileDir / preCompiledInput -> "case class Message(value: String)", + os.rel / runDir / mainInput -> s"""object Main extends App { println(Message("$expectedOutput").value) }""" + ).fromRoot { (root: os.Path) => + val preCompileOutputDir = "out" + + // first, precompile to an explicitly specified output directory with -d + os.proc( + TestUtil.cli, + "compile", + preCompiledInput, + "-d", + preCompileOutputDir, + extraOptions + ).call(cwd = root / preCompileDir) + + // next, run while relying on the pre-compiled class, specifying the path with -classpath + val runRes = os.proc( + TestUtil.cli, + "run", + mainInput, + "-classpath", + (os.rel / os.up / preCompileDir / preCompileOutputDir).toString, + extraOptions + ).call(cwd = root / runDir) + expect(runRes.out.trim == expectedOutput) + } + } + if (actualScalaVersion.startsWith("3")) test("should throw exception for code compiled by scala 3.1.3") { val exceptionMsg = "Throw exception in Scala" diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 71267ee092..0cd82c169c 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1238,9 +1238,9 @@ Show help for scalac. This is an alias for --scalac-option -help ### `--extra-jars` -Aliases: `--jar`, `--jars`, `--extra-jar` +Aliases: `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `--classpath`, `--class-path`, `--extra-class-path` -Add extra JARs in the class path +Add extra JARs and compiled classes to the class path ### `--extra-compile-only-jars` diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 48d2e871e5..38f782e0c3 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -648,9 +648,9 @@ Show help for scalac. This is an alias for --scalac-option -help ### `--extra-jars` -Aliases: `--jar`, `--jars`, `--extra-jar` +Aliases: `--jar`, `--jars`, `--extra-jar`, `--class`, `--extra-class`, `--classes`, `--extra-classes`, `-classpath`, `--classpath`, `--class-path`, `--extra-class-path` -Add extra JARs in the class path +Add extra JARs and compiled classes to the class path ### `--extra-compile-only-jars` From 5b598343dac7644489f3618d9f009202f83f0cb7 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 9 Sep 2022 11:58:03 +0200 Subject: [PATCH 4/5] Make `-O -classpath -O path/to/classpath` default to `-classpath path/to/classpath` --- .../scala/cli/commands/ScalacOptions.scala | 3 ++- .../cli/commands/util/ScalacOptionsUtil.scala | 19 +++++++++++++++ .../cli/commands/util/SharedOptionsUtil.scala | 18 +++++++++------ .../cli/integration/RunTestDefinitions.scala | 13 +++++++++++ .../scala/build/options/ShadowingSeq.scala | 23 +++++++++++++++++++ website/docs/reference/cli-options.md | 2 +- .../reference/scala-command/cli-options.md | 2 +- 7 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala index fa338553ea..5fe0dbaf34 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala @@ -13,6 +13,7 @@ final case class ScalacOptions( @HelpMessage("Add a scalac option") @ValueDescription("option") @Name("scala-opt") + @Name("scala-option") @Name("O") scalacOption: List[String] = Nil ) @@ -21,7 +22,7 @@ final case class ScalacOptions( object ScalacOptions { private val scalacOptionsArg = Arg("scalacOption") - .withExtraNames(Seq(Name("scala-opt"), Name("O"))) + .withExtraNames(Seq(Name("scala-opt"), Name("O"), Name("scala-option"))) .withValueDescription(Some(ValueDescription("option"))) .withHelpMessage(Some(HelpMessage( "Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt`, `-P`, `-target`, `-V`, `-W`, `-X`, and `-Y` are assumed to be Scala compiler options and don't require to be passed after `-O` or `--scalac-option`." diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala b/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala new file mode 100644 index 0000000000..1b24c447d3 --- /dev/null +++ b/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala @@ -0,0 +1,19 @@ +package scala.cli.commands.util + +import scala.build.options.{ScalacOpt, ShadowingSeq} +import scala.cli.commands.ScalacOptions + +object ScalacOptionsUtil { + extension (opts: List[String]) { + def toScalacOptShadowingSeq: ShadowingSeq[ScalacOpt] = + ShadowingSeq.from(opts.filter(_.nonEmpty).map(ScalacOpt(_))) + } + + extension (opts: ShadowingSeq[ScalacOpt]) { + def getScalacOption(key: String): Option[String] = + opts.get(ScalacOpt(key)).headOption.map(_.value) + + def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] = + opts.filterKeys(_.key.exists(f)) + } +} diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala b/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala index 38321dd188..3dd4e87ed2 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala @@ -22,6 +22,7 @@ import scala.build.options as bo import scala.cli.ScalaCli import scala.cli.commands.ScalaJsOptions import scala.cli.commands.util.CommonOps.* +import scala.cli.commands.util.ScalacOptionsUtil.* import scala.cli.commands.util.SharedCompilationServerOptionsUtil.* import scala.cli.config.{ConfigDb, Keys} import scala.concurrent.ExecutionContextExecutorService @@ -169,12 +170,12 @@ object SharedOptionsUtil extends CommandHelpers { scalaBinaryVersion = scalaBinaryVersion.map(_.trim).filter(_.nonEmpty), addScalaLibrary = scalaLibrary.orElse(java.map(!_)), generateSemanticDbs = semanticDb, - scalacOptions = ShadowingSeq.from( - scalac.scalacOption - .filter(_.nonEmpty) - .map(ScalacOpt(_)) - .map(Positioned.commandLine) - ), + scalacOptions = scalac + .scalacOption + .toScalacOptShadowingSeq + // -O -classpath should be redirected as --extra-jars instead + .filterScalacOptionKeys(key => key != "-classpath") + .map(Positioned.commandLine), compilerPlugins = SharedOptionsUtil.parseDependencies( dependencies.compilerPlugin.map(Positioned.none), @@ -199,7 +200,7 @@ object SharedOptionsUtil extends CommandHelpers { runJmh = if (enableJmh) Some(true) else None ), classPathOptions = bo.ClassPathOptions( - extraClassPath = extraJars + extraClassPath = extraJarsAndClasspath .flatMap(_.split(File.pathSeparator).toSeq) .filter(_.nonEmpty) .map(os.Path(_, os.pwd)), @@ -228,6 +229,9 @@ object SharedOptionsUtil extends CommandHelpers { ) } + def extraJarsAndClasspath: List[String] = + extraJars ++ scalac.scalacOption.toScalacOptShadowingSeq.getScalacOption("-classpath") + def globalInteractiveWasSuggested: Option[Boolean] = configDb.getOrNone(Keys.globalInteractiveWasSuggested, logger) 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 2baa83e880..430d902ee3 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -2251,6 +2251,19 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) extraOptions ).call(cwd = root / runDir) expect(runRes.out.trim == expectedOutput) + + // ensure the same behaviour can be expected when passing -classpath with -O + val runRes2 = os.proc( + TestUtil.cli, + "run", + mainInput, + "-O", + "-classpath", + "-O", + (os.rel / os.up / preCompileDir / preCompileOutputDir).toString, + extraOptions + ).call(cwd = root / runDir) + expect(runRes2.out.trim == expectedOutput) } } diff --git a/modules/options/src/main/scala/scala/build/options/ShadowingSeq.scala b/modules/options/src/main/scala/scala/build/options/ShadowingSeq.scala index 8ea35c5402..bddbcc1d7b 100644 --- a/modules/options/src/main/scala/scala/build/options/ShadowingSeq.scala +++ b/modules/options/src/main/scala/scala/build/options/ShadowingSeq.scala @@ -9,6 +9,17 @@ final case class ShadowingSeq[T] private (values: Seq[Seq[T]]) { lazy val toSeq: Seq[T] = values.flatten def map[U](f: T => U)(implicit key: ShadowingSeq.KeyOf[U]): ShadowingSeq[U] = ShadowingSeq.empty[U] ++ toSeq.map(f) + def mapSubSeq[U](f: Seq[T] => Seq[U]): ShadowingSeq[U] = + ShadowingSeq[U](values.map(f)) + def filter(f: T => Boolean)(implicit key: ShadowingSeq.KeyOf[T]): ShadowingSeq[T] = + ShadowingSeq.empty ++ toSeq.filter(f) + def filterSubSeq(f: Seq[T] => Boolean): ShadowingSeq[T] = + ShadowingSeq(values.filter(f)) + def filterKeys(f: T => Boolean): ShadowingSeq[T] = + filterSubSeq { + case Seq(head, _*) => f(head) + case _ => true + } def ++(other: Seq[T])(implicit key: ShadowingSeq.KeyOf[T]): ShadowingSeq[T] = addGroups(ShadowingSeq.groups(other, key.groups(other))) private def addGroups(other: Seq[Seq[T]])(implicit key: ShadowingSeq.KeyOf[T]): ShadowingSeq[T] = @@ -29,6 +40,18 @@ final case class ShadowingSeq[T] private (values: Seq[Seq[T]]) { ShadowingSeq(l.toList) } + def keyValueMap: Map[T, Seq[T]] = + values + .flatMap { + case Seq(head, tail*) => Some(head -> tail) + case _ => None + } + .toMap + + def get(key: T): Seq[T] = + keyValueMap + .get(key) + .toSeq.flatten } object ShadowingSeq { diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 0cd82c169c..2964e29af6 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -1172,7 +1172,7 @@ Available in commands: ### `--scalac-option` -Aliases: `--scala-opt`, `-O` +Aliases: `--scala-opt`, `-O`, `--scala-option` Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt`, `-P`, `-target`, `-V`, `-W`, `-X`, and `-Y` are assumed to be Scala compiler options and don't require to be passed after `-O` or `--scalac-option`. diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 38f782e0c3..0676dbcc82 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -615,7 +615,7 @@ Available in commands: ### `--scalac-option` -Aliases: `--scala-opt`, `-O` +Aliases: `--scala-opt`, `-O`, `--scala-option` Add a `scalac` option. Note that options starting with `-g`, `-language`, `-opt`, `-P`, `-target`, `-V`, `-W`, `-X`, and `-Y` are assumed to be Scala compiler options and don't require to be passed after `-O` or `--scalac-option`. From d1c0f284da6c91a83c561811db3f16a3bc50ce6c Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 9 Sep 2022 13:27:57 +0200 Subject: [PATCH 5/5] Move compile --output to SharedOptions and rename it to `--compile-output`; prevent the `-d` underlying compiler flag from being set repeatedly and default it to --compile-output --- .../scala/cli/commands/CompileOptions.scala | 7 ---- .../scala/cli/commands/ScalacOptions.scala | 6 +++ .../scala/cli/commands/SharedOptions.scala | 9 +++++ .../scala/scala/cli/commands/Compile.scala | 9 ++--- .../scala/scala/cli/commands/Package.scala | 2 + .../main/scala/scala/cli/commands/Run.scala | 2 + .../commands/util/BuildCommandHelpers.scala | 15 ++++++- .../cli/commands/util/ScalacOptionsUtil.scala | 8 ++-- .../cli/commands/util/SharedOptionsUtil.scala | 3 +- .../integration/CompileTestDefinitions.scala | 6 ++- .../cli/integration/RunTestDefinitions.scala | 40 ++++++++++++++++--- website/docs/reference/cli-options.md | 12 +++--- .../reference/scala-command/cli-options.md | 12 +++--- 13 files changed, 91 insertions(+), 40 deletions(-) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala index 6bf7df2799..9741fd0b8b 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/CompileOptions.scala @@ -18,13 +18,6 @@ final case class CompileOptions( @HelpMessage("Print the resulting class path") printClassPath: Boolean = false, - @Name("output-directory") - @Name("d") - @Name("destination") - @HelpMessage("Copy compilation results to output directory using either relative or absolute path") - @ValueDescription("/example/path") - output: Option[String] = None, - @HelpMessage("Compile test scope") test: Boolean = false ) diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala index 5fe0dbaf34..5ef37ea85a 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/ScalacOptions.scala @@ -41,6 +41,12 @@ object ScalacOptions { val ScalacPrintOptions: Set[String] = scalacOptionsPurePrefixes ++ Set("-help", "-Xshow-phases", "-Vphases") + /** This includes all the scalac options which are redirected to native Scala CLI options. */ + val ScalaCliRedirectedOptions = Set( + "-classpath", // redirected to --extra-jars + "-d" // redirected to --compilation-output + ) + private val scalacOptionsArgument: Argument[List[String]] = new Argument[List[String]] { diff --git a/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala b/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala index 8ab9e6ced6..83ff0bfa25 100644 --- a/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala +++ b/modules/cli-options/src/main/scala/scala/cli/commands/SharedOptions.scala @@ -117,6 +117,15 @@ final case class SharedOptions( @Hidden strictBloopJsonCheck: Option[Boolean] = None, + + @Name("output-directory") + @Name("d") + @Name("destination") + @Name("compileOutput") + @Name("compileOut") + @HelpMessage("Copy compilation results to output directory using either relative or absolute path") + @ValueDescription("/example/path") + compilationOutput: Option[String] = None, ) // format: on 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 dd3770f070..14fc5df782 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Compile.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Compile.scala @@ -7,17 +7,15 @@ import java.io.File import scala.build.options.Scope import scala.build.{Build, BuildThreads, Builds, Os} import scala.cli.CurrentParams +import scala.cli.commands.util.BuildCommandHelpers import scala.cli.commands.util.CommonOps.SharedDirectoriesOptionsOps import scala.cli.commands.util.SharedOptionsUtil._ import scala.cli.config.{ConfigDb, Keys} -object Compile extends ScalaCommand[CompileOptions] { +object Compile extends ScalaCommand[CompileOptions] with BuildCommandHelpers { override def group = "Main" override def sharedOptions(options: CompileOptions): Option[SharedOptions] = Some(options.shared) - def outputPath(options: CompileOptions): Option[os.Path] = - options.output.filter(_.nonEmpty).map(p => os.Path(p, Os.pwd)) - def run(options: CompileOptions, args: RemainingArgs): Unit = { maybePrintGroupHelp(options) maybePrintSimpleScalacOutput(options, options.shared.buildOptions()) @@ -71,8 +69,7 @@ object Compile extends ScalaCommand[CompileOptions] { val cp = s.fullClassPath.map(_.toString).mkString(File.pathSeparator) println(cp) } - for (output <- outputPath(options); s <- successulBuildOpt) - os.copy.over(s.output, output) + successulBuildOpt.foreach(_.copyOutput(options.shared)) } } 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 93e5ffb975..ba0e280703 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Package.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Package.scala @@ -85,6 +85,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { ) { res => res.orReport(logger).map(_.main).foreach { case s: Build.Successful => + s.copyOutput(options.shared) val mtimeDestPath = doPackage( logger = logger, outputOpt = options.output.filter(_.nonEmpty), @@ -124,6 +125,7 @@ object Package extends ScalaCommand[PackageOptions] with BuildCommandHelpers { .orExit(logger) builds.main match { case s: Build.Successful => + s.copyOutput(options.shared) val res0 = doPackage( logger = logger, outputOpt = options.output.filter(_.nonEmpty), 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 c4f3793112..d1fa5d1d7a 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Run.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Run.scala @@ -223,6 +223,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers { ) .orReport(logger) .flatten + s.copyOutput(options.shared) if (options.sharedRun.watch.restart) processOpt = maybeProcess else @@ -251,6 +252,7 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers { .orExit(logger) builds.main match { case s: Build.Successful => + s.copyOutput(options.shared) val res = maybeRun( s, allowTerminate = true, diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/BuildCommandHelpers.scala b/modules/cli/src/main/scala/scala/cli/commands/util/BuildCommandHelpers.scala index 2974601319..cdd3828022 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/util/BuildCommandHelpers.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/util/BuildCommandHelpers.scala @@ -1,10 +1,11 @@ package scala.cli.commands.util import scala.build.errors.MainClassError -import scala.build.{Build, Logger} -import scala.cli.commands.ScalaCommand +import scala.build.{Build, Logger, Os} +import scala.cli.commands.{ScalaCommand, SharedOptions} trait BuildCommandHelpers { self: ScalaCommand[_] => + import scala.cli.commands.util.ScalacOptionsUtil.* extension (successfulBuild: Build.Successful) { def retainedMainClass( logger: Logger, @@ -15,5 +16,15 @@ trait BuildCommandHelpers { self: ScalaCommand[_] => self.argvOpt.map(_.mkString(" ")).getOrElse(actualFullCommand), logger ) + + /** -O -d defaults to --compile-output; if both are defined, --compile-output takes precedence + */ + def copyOutput(sharedOptions: SharedOptions): Unit = + sharedOptions.compilationOutput.filter(_.nonEmpty) + .orElse(sharedOptions.scalac.scalacOption.toScalacOptShadowingSeq.getScalacOption("-d")) + .filter(_.nonEmpty) + .map(os.Path(_, Os.pwd)).foreach(output => + os.copy.over(successfulBuild.output, output, createFolders = true) + ) } } diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala b/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala index 1b24c447d3..d578bae474 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/util/ScalacOptionsUtil.scala @@ -7,13 +7,15 @@ object ScalacOptionsUtil { extension (opts: List[String]) { def toScalacOptShadowingSeq: ShadowingSeq[ScalacOpt] = ShadowingSeq.from(opts.filter(_.nonEmpty).map(ScalacOpt(_))) + } extension (opts: ShadowingSeq[ScalacOpt]) { - def getScalacOption(key: String): Option[String] = - opts.get(ScalacOpt(key)).headOption.map(_.value) - def filterScalacOptionKeys(f: String => Boolean): ShadowingSeq[ScalacOpt] = opts.filterKeys(_.key.exists(f)) + def filterNonRedirected: ShadowingSeq[ScalacOpt] = + opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_)) + def getScalacOption(key: String): Option[String] = + opts.get(ScalacOpt(key)).headOption.map(_.value) } } diff --git a/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala b/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala index 3dd4e87ed2..b9887e7dd4 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala @@ -173,8 +173,7 @@ object SharedOptionsUtil extends CommandHelpers { scalacOptions = scalac .scalacOption .toScalacOptShadowingSeq - // -O -classpath should be redirected as --extra-jars instead - .filterScalacOptionKeys(key => key != "-classpath") + .filterNonRedirected .map(Positioned.commandLine), compilerPlugins = SharedOptionsUtil.parseDependencies( 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 c061c30e06..7f6e5c2979 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/CompileTestDefinitions.scala @@ -94,7 +94,9 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) test("copy compile output") { mainAndTestInputs.fromRoot { root => val tempOutput = root / "output" - os.proc(TestUtil.cli, "compile", "--output", tempOutput, extraOptions, ".").call(cwd = root) + os.proc(TestUtil.cli, "compile", "--compile-output", tempOutput, extraOptions, ".").call(cwd = + root + ) checkIfCompileOutputIsCopied("Main", tempOutput) } } @@ -107,7 +109,7 @@ abstract class CompileTestDefinitions(val scalaVersionOpt: Option[String]) TestUtil.cli, "compile", "--test", - "--output", + "--compile-output", tempOutput, "--print-class-path", extraOptions, 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 430d902ee3..d56f5596c9 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -2229,7 +2229,7 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) os.rel / preCompileDir / preCompiledInput -> "case class Message(value: String)", os.rel / runDir / mainInput -> s"""object Main extends App { println(Message("$expectedOutput").value) }""" ).fromRoot { (root: os.Path) => - val preCompileOutputDir = "out" + val preCompileOutputDir = os.rel / "outParentDir" / "out" // first, precompile to an explicitly specified output directory with -d os.proc( @@ -2237,7 +2237,7 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) "compile", preCompiledInput, "-d", - preCompileOutputDir, + preCompileOutputDir.toString, extraOptions ).call(cwd = root / preCompileDir) @@ -2251,9 +2251,36 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) extraOptions ).call(cwd = root / runDir) expect(runRes.out.trim == expectedOutput) + } + } + + test("-O -classpath allows to run with scala-cli compile -O -d option pre-compiled classes") { + val preCompileDir = "PreCompileDir" + val preCompiledInput = "Message.scala" + val runDir = "RunDir" + val mainInput = "Main.scala" + val expectedOutput = "Hello" + TestInputs( + os.rel / preCompileDir / preCompiledInput -> "case class Message(value: String)", + os.rel / runDir / mainInput -> s"""object Main extends App { println(Message("$expectedOutput").value) }""" + ).fromRoot { (root: os.Path) => + val preCompileOutputDir = os.rel / "outParentDir" / "out" - // ensure the same behaviour can be expected when passing -classpath with -O - val runRes2 = os.proc( + // first, precompile to an explicitly specified output directory with -O -d + val compileRes = os.proc( + TestUtil.cli, + "compile", + preCompiledInput, + "-O", + "-d", + "-O", + preCompileOutputDir.toString, + extraOptions + ).call(cwd = root / preCompileDir, stderr = os.Pipe) + expect(!compileRes.err.trim.contains("Warning: Flag -d set repeatedly")) + + // next, run while relying on the pre-compiled class, specifying the path with -O -classpath + val runRes = os.proc( TestUtil.cli, "run", mainInput, @@ -2262,8 +2289,9 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) "-O", (os.rel / os.up / preCompileDir / preCompileOutputDir).toString, extraOptions - ).call(cwd = root / runDir) - expect(runRes2.out.trim == expectedOutput) + ).call(cwd = root / runDir, stderr = os.Pipe) + expect(!runRes.err.trim.contains("Warning: Flag -classpath set repeatedly")) + expect(runRes.out.trim == expectedOutput) } } diff --git a/website/docs/reference/cli-options.md b/website/docs/reference/cli-options.md index 2964e29af6..6930e324ae 100644 --- a/website/docs/reference/cli-options.md +++ b/website/docs/reference/cli-options.md @@ -119,12 +119,6 @@ Aliases: `-p`, `--print-classpath` Print the resulting class path -### `--output` - -Aliases: `--output-directory`, `-d`, `--destination` - -Copy compilation results to output directory using either relative or absolute path - ### `--test` Compile test scope @@ -1286,6 +1280,12 @@ Add dependency for stubs needed to make $ivy and $dep imports to work. ### `--strict-bloop-json-check` [Internal] +### `--compilation-output` + +Aliases: `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out` + +Copy compilation results to output directory using either relative or absolute path + ## Snippet options Available in commands: diff --git a/website/docs/reference/scala-command/cli-options.md b/website/docs/reference/scala-command/cli-options.md index 0676dbcc82..af0eab1b37 100644 --- a/website/docs/reference/scala-command/cli-options.md +++ b/website/docs/reference/scala-command/cli-options.md @@ -103,12 +103,6 @@ Aliases: `-p`, `--print-classpath` Print the resulting class path -### `--output` - -Aliases: `--output-directory`, `-d`, `--destination` - -Copy compilation results to output directory using either relative or absolute path - ### `--test` Compile test scope @@ -696,6 +690,12 @@ Add dependency for stubs needed to make $ivy and $dep imports to work. ### `--strict-bloop-json-check` [Internal] +### `--compilation-output` + +Aliases: `--output-directory`, `-d`, `--destination`, `--compile-output`, `--compile-out` + +Copy compilation results to output directory using either relative or absolute path + ## Snippet options Available in commands: