diff --git a/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala b/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala index df9b5923c7..ca180085d2 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala @@ -15,8 +15,9 @@ import scala.build.EitherCps.{either, value} import scala.build.compiler.SimpleScalaCompiler import scala.build.errors.BuildException import scala.build.internal.{Constants, Runner} -import scala.build.options.{BuildOptions, Scope} +import scala.build.options.{BuildOptions, ScalacOpt, Scope} import scala.build.{Artifacts, Logger, Positioned, ReplArtifacts} +import scala.cli.commands.default.LegacyScalaOptions import scala.cli.commands.shared.{HasLoggingOptions, ScalaCliHelp, ScalacOptions, SharedOptions} import scala.cli.commands.util.CommandHelpers import scala.cli.commands.util.ScalacOptionsUtil.* @@ -133,6 +134,18 @@ abstract class ScalaCommand[T <: HasLoggingOptions](implicit myParser: Parser[T] for (shared <- sharedOptions(options)) shared.helpGroups.maybePrintGroupHelp(help, helpFormat) + private def maybePrintWarnings(options: T): Unit = { + import scala.cli.commands.shared.ScalacOptions.YScriptRunnerOption + val logger = options.logging.logger + sharedOptions(options).foreach { so => + val scalacOpts = so.scalac.scalacOption.toScalacOptShadowingSeq + if scalacOpts.keys.contains(ScalacOpt(YScriptRunnerOption)) then + logger.message( + LegacyScalaOptions.yScriptRunnerWarning(scalacOpts.getOption(YScriptRunnerOption)) + ) + } + } + /** Print `scalac` output if passed options imply no inputs are necessary and raw `scalac` output * is required instead. (i.e. `--scalac-option -help`) * @param options @@ -265,6 +278,7 @@ abstract class ScalaCommand[T <: HasLoggingOptions](implicit myParser: Parser[T] */ final override def run(options: T, remainingArgs: RemainingArgs): Unit = { CurrentParams.verbosity = options.logging.verbosity + maybePrintWarnings(options) maybePrintGroupHelp(options) buildOptions(options).foreach { bo => maybePrintSimpleScalacOutput(options, bo) diff --git a/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala b/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala index d234265513..662bd19d77 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/default/LegacyScalaOptions.scala @@ -5,9 +5,11 @@ import caseapp.core.Indexed import scala.build.Logger import scala.cli.ScalaCli -import scala.cli.ScalaCli.fullRunnerName +import scala.cli.ScalaCli.{fullRunnerName, progName} +import scala.cli.commands.bloop.BloopExit import scala.cli.commands.default.LegacyScalaOptions.* import scala.cli.commands.package0.Package +import scala.cli.commands.shared.ScalacOptions.YScriptRunnerOption import scala.cli.commands.tags /** Options covering backwards compatibility with the old scala runner. @@ -164,4 +166,26 @@ object LegacyScalaOptions { private[default] lazy val PowerString = if ScalaCli.allowRestrictedFeatures then "" else "--power " + + def yScriptRunnerWarning(yScriptRunnerValue: Option[String]): String = { + val valueSpecificMsg = yScriptRunnerValue match { + case Some(v @ "default") => + s"scala.tools.nsc.DefaultScriptRunner (the $v script runner) is no longer available." + case Some(v @ "resident") => + s"scala.tools.nsc.fsc.ResidentScriptRunner (the $v script runner) is no longer available." + case Some(v @ "shutdown") => + val bloopExitCommandName = + BloopExit.names.headOption.map(_.mkString(" ")).getOrElse(BloopExit.name) + s"""scala.tools.nsc.fsc.DaemonKiller (the $v script runner) is no longer available. + |Did you want to stop the $fullRunnerName build server (Bloop) instead? + |If so, consider using the following command: + | ${Console.BOLD}$progName $PowerString$bloopExitCommandName${Console.RESET}""".stripMargin + case Some(className) => + s"Using $className as the script runner is no longer supported and will not be attempted." + case _ => "" + } + s"""Deprecated option '$YScriptRunnerOption' is ignored. + |The script runner can no longer be picked as before. + |$valueSpecificMsg""".stripMargin + } } 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 7add142178..06f842675f 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 @@ -35,12 +35,13 @@ object ScalacOptions { origin = Some("ScalacOptions") ) // .withIsFlag(true) // The scalac options we handle accept no value after the -… argument + val YScriptRunnerOption = "-Yscriptrunner" private val scalacOptionsPurePrefixes = Set("-V", "-W", "-X", "-Y") private val scalacOptionsPrefixes = Set("-g", "-language", "-opt", "-P", "-target", "-source") ++ scalacOptionsPurePrefixes private val scalacAliasedOptions = // these options don't require being passed after -O and accept an arg - Set("-encoding", "-release", "-color") + Set("-encoding", "-release", "-color", YScriptRunnerOption) private val scalacNoArgAliasedOptions = // these options don't require being passed after -O and don't accept an arg Set( "-nowarn", @@ -64,6 +65,9 @@ object ScalacOptions { "-classpath", // redirected to --extra-jars "-d" // redirected to --compilation-output ) + val ScalacDeprecatedOptions: Set[String] = Set( + YScriptRunnerOption // old 'scala' runner specific, no longer supported + ) private val scalacOptionsArgument: Argument[List[String]] = new Argument[List[String]] { @@ -81,7 +85,9 @@ object ScalacOptions { formatter: Formatter[Name] ): Either[(Error, List[String]), Option[(Option[List[String]], List[String])]] = args match { - case h :: t if scalacOptionsPrefixes.exists(h.startsWith) => + case h :: t + if scalacOptionsPrefixes.exists(h.startsWith) && + !ScalacDeprecatedOptions.contains(h) => Right(Some((Some(h :: acc.getOrElse(Nil)), t))) case h :: t if scalacNoArgAliasedOptions.contains(h) => Right(Some((Some(h :: acc.getOrElse(Nil)), t))) 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 29772606cd..3cb738cc21 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 @@ -300,6 +300,7 @@ final case class SharedOptions( .withScalacExtraOptions(scalacExtra) .toScalacOptShadowingSeq .filterNonRedirected + .filterNonDeprecated .map(Positioned.commandLine), compilerPlugins = SharedOptions.parseDependencies( 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 15bfa777c8..39aa41d0a4 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 @@ -1,6 +1,10 @@ package scala.cli.commands.util +import scala.build.Logger import scala.build.options.{ScalacOpt, ShadowingSeq} +import scala.cli.commands.bloop.BloopExit +import scala.cli.commands.default.LegacyScalaOptions +import scala.cli.commands.shared.ScalacOptions.YScriptRunnerOption import scala.cli.commands.shared.{ScalacExtraOptions, ScalacOptions} object ScalacOptionsUtil { @@ -31,6 +35,8 @@ object ScalacOptionsUtil { opts.filterKeys(_.key.exists(f)) def filterNonRedirected: ShadowingSeq[ScalacOpt] = opts.filterScalacOptionKeys(!ScalacOptions.ScalaCliRedirectedOptions.contains(_)) + def filterNonDeprecated: ShadowingSeq[ScalacOpt] = + opts.filterScalacOptionKeys(!ScalacOptions.ScalacDeprecatedOptions.contains(_)) def getOption(key: String): Option[String] = opts.get(ScalacOpt(key)).headOption.map(_.value) } diff --git a/modules/integration/src/test/scala/scala/cli/integration/LegacyScalaRunnerTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/LegacyScalaRunnerTestDefinitions.scala index e81a71ab89..8bd4579bba 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/LegacyScalaRunnerTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/LegacyScalaRunnerTestDefinitions.scala @@ -85,6 +85,26 @@ trait LegacyScalaRunnerTestDefinitions { _: DefaultTests => } } + test("ensure -Yscriptrunner works with the default command") { + legacyOptionBackwardsCompatTest("-Yscriptrunner") { + (legacyOption, inputFile, root) => + Seq("default", "resident", "shutdown", "scala.tools.nsc.fsc.ResidentScriptRunner").foreach { + legacyOptionValue => + val res = + os.proc( + TestUtil.cli, + legacyOption, + legacyOptionValue, + inputFile, + TestUtil.extraOptions + ) + .call(cwd = root, stderr = os.Pipe) + expect(res.err.trim().contains(deprecatedOptionWarning(legacyOption))) + expect(res.err.trim().contains(legacyOptionValue)) + } + } + } + private def simpleLegacyOptionBackwardsCompatTest(optionAliases: String*): Unit = abstractLegacyOptionBackwardsCompatTest(optionAliases) { (legacyOption, expectedMsg, _, root) => 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 bddbcc1d7b..35a3f14f16 100644 --- a/modules/options/src/main/scala/scala/build/options/ShadowingSeq.scala +++ b/modules/options/src/main/scala/scala/build/options/ShadowingSeq.scala @@ -20,6 +20,7 @@ final case class ShadowingSeq[T] private (values: Seq[Seq[T]]) { case Seq(head, _*) => f(head) case _ => true } + def keys: Seq[T] = values.map(_.head) 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] =