diff --git a/modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala b/modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala index ea0dc10186..42ccbf27ac 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala @@ -4,6 +4,7 @@ import ai.kien.python.Python import caseapp.* import caseapp.core.help.HelpFormat import coursier.cache.FileCache +import coursier.core.Version import coursier.error.{FetchError, ResolutionError} import dependency.* @@ -20,6 +21,7 @@ import scala.build.errors.{ } import scala.build.input.Inputs import scala.build.internal.{Constants, Runner} +import scala.build.options.ScalacOpt.noDashPrefixes import scala.build.options.{BuildOptions, JavaOpt, MaybeScalaVersion, Scope} import scala.cli.commands.publish.ConfigUtil.* import scala.cli.commands.run.Run.{ @@ -28,14 +30,14 @@ import scala.cli.commands.run.Run.{ pythonPathEnv } import scala.cli.commands.run.RunMode -import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, SharedOptions} +import scala.cli.commands.shared.{HelpCommandGroup, HelpGroup, ScalacOptions, SharedOptions} import scala.cli.commands.util.BuildCommandHelpers import scala.cli.commands.{ScalaCommand, WatchUtil} import scala.cli.config.{ConfigDb, Keys} import scala.cli.packaging.Library import scala.cli.util.ArgHelpers.* import scala.cli.util.ConfigDbUtils -import scala.cli.{CurrentParams, ScalaCli} +import scala.cli.{CurrentParams, ScalaCli, coursierVersion} import scala.jdk.CollectionConverters.* import scala.util.Properties @@ -390,11 +392,24 @@ object Repl extends ScalaCommand[ReplOptions] with BuildCommandHelpers { replArgs: Seq[String], extraEnv: Map[String, String] = Map.empty, extraProps: Map[String, String] = Map.empty - ): Unit = - if (dryRun) - logger.message("Dry run, not running REPL.") + ): Unit = { + val isAmmonite = replArtifacts.replMainClass.startsWith("ammonite") + if replArgs.exists(_.noDashPrefixes == ScalacOptions.replInitScript) then + scalaParams.scalaVersion match + case _ if isAmmonite => + logger.message( + "The '--repl-init-script' option is not supported with Ammonite. Did you mean to use '--ammonite-arg'?" + ) + case s + if s.coursierVersion < "3.6.4-RC1".coursierVersion && + s.coursierVersion < "3.6.4".coursierVersion && + s.coursierVersion < "3.6.4-RC1-bin-20250109-a50a1e4-NIGHTLY".coursierVersion => + logger.message( + "The '--repl-init-script option' is only supported starting with Scala 3.6.4 and onwards." + ) + case _ => () + if dryRun then logger.message("Dry run, not running REPL.") else { - val isAmmonite = replArtifacts.replMainClass.startsWith("ammonite") val depClassPathArgs: Seq[String] = if replArtifacts.depsClassPath.nonEmpty && !isAmmonite then Seq( @@ -422,6 +437,7 @@ object Repl extends ScalaCommand[ReplOptions] with BuildCommandHelpers { if (retCode != 0) value(Left(new ReplError(retCode))) } + } def defaultArtifacts(): Either[BuildException, ReplArtifacts] = either { value { diff --git a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala index e3bbd220a8..bb6eb324ad 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/shared/ScalacOptions.scala @@ -48,6 +48,8 @@ object ScalacOptions { val YScriptRunnerOption = "Yscriptrunner" private val scalacOptionsPurePrefixes = Set("V", "W", "X", "Y") private val scalacOptionsPrefixes = Set("P") ++ scalacOptionsPurePrefixes + val replInitScript = "repl-init-script" + private val replAliasedOptions = Set(replInitScript) private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg Set( "coverage-exclude-classlikes", @@ -61,7 +63,7 @@ object ScalacOptions { "target", "source", YScriptRunnerOption - ) + ) ++ replAliasedOptions private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg Set( "experimental", diff --git a/modules/cli/src/main/scala/scala/cli/package.scala b/modules/cli/src/main/scala/scala/cli/package.scala new file mode 100644 index 0000000000..96f0dc2a1c --- /dev/null +++ b/modules/cli/src/main/scala/scala/cli/package.scala @@ -0,0 +1,7 @@ +package scala + +import coursier.core.Version + +package object cli { + extension (s: String) def coursierVersion: Version = Version(s) +} diff --git a/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala index 8a435ac724..a2a08e81be 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ReplTestDefinitions.scala @@ -86,4 +86,26 @@ abstract class ReplTestDefinitions extends ScalaCliSuite with TestScalaVersionAr expect(res.exitCode == 0) } } + + test("--repl-init-script dry run") { + TestInputs.empty.fromRoot { root => + val r = os.proc( + TestUtil.cli, + "repl", + extraOptions, + "--repl-init-script", + "println(\"Hello\")", + "--repl-dry-run" + ) + .call(cwd = root, stderr = os.Pipe, check = false) + val warningText = + "The '--repl-init-script option' is only supported starting with Scala 3.6.4 and onwards." + val coursierScalaVersion = actualScalaVersion.coursierVersion + val shouldPrintWarning = coursierScalaVersion < "3.6.4".coursierVersion && + coursierScalaVersion < "3.6.4-RC1".coursierVersion && + coursierScalaVersion < "3.6.4-RC1-bin-20250109-a50a1e4-NIGHTLY".coursierVersion + if (shouldPrintWarning) expect(r.err.trim().contains(warningText)) + else expect(!r.err.trim().contains(warningText)) + } + } } diff --git a/website/docs/commands/repl.md b/website/docs/commands/repl.md index 20dce3e824..2601d1808f 100644 --- a/website/docs/commands/repl.md +++ b/website/docs/commands/repl.md @@ -80,6 +80,63 @@ scala> :quit +## Passing REPL options +It is also possible to manually pass REPL-specific options. +It can be done in a couple ways: +- after the `--` separator, as the REPL itself is the launched app, so its options are app arguments + + + +```bash ignore +scala repl -S 3.6.4-RC1 -- --repl-init-script 'println("Hello")' +``` + +``` +Hello +Welcome to Scala 3.6.4-RC1 (23.0.1, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> +``` + + + +- with the `-O`, effectively passing them as compiler options: + + + +```bash ignore +scala repl -S 3.6.4-RC1 -O --repl-init-script -O 'println("Hello")' +``` + +``` +Hello +Welcome to Scala 3.6.4-RC1 (23.0.1, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> +``` + + + +- directly, as a Scala CLI option (do note that newly added options from an RC version or a snapshot may not be supported this way just yet): + + + +```bash ignore +scala repl -S 3.6.4-RC1 --repl-init-script 'println("Hello")' +``` + +``` +Hello +Welcome to Scala 3.6.4-RC1 (23.0.1, Java OpenJDK 64-Bit Server VM). +Type in expressions for evaluation. Or try :help. + +scala> +``` + + + ## Using Toolkit in REPL It is also possible to start the scala-cli REPL with [toolkit](https://scala-cli.virtuslab.org/docs/guides/introduction/toolkit/) enabled