Migrating from the old scala
runner
As of SIP-46, Scala CLI has been accepted as the new scala
command.
In that context, the purpose of this guide is to highlight the key differences between the old scala
script
and Scala CLI to make the migration as smooth as possible for users.
If you are looking for an overview of Scala CLI basics, refer to the Basics page. If you merely want to get started with Scala CLI, you might want to first look at the Getting started page.
How to start using Scala CLI as the new scala
command?
Refer to the official instructions for installing Scala.
Scala CLI is available as the scala
command alongside the Scala distribution in Scala 3.5.0 and later.
Can I still use the old scala
runner with Scala 3.5+?
Yes, even though its usage has been deprecated, it is still available under the scala_legacy
command.
However, it is likely to be dropped in a future version.
scala_legacy -version
# [warning] MainGenericRunner class is deprecated since Scala 3.5.0, and Scala CLI features will not work.
# [warning] Please be sure to update to the Scala CLI launcher to use the new features.
# [warning] Check the Scala 3.5.0 release notes to troubleshoot your installation.
# Scala code runner version 3.5.0 -- Copyright 2002-2024, LAMP/EPFL
How has the passing of arguments been changed from the old scala
runner to Scala CLI?
Let us take a closer look on how the old runner handled arguments when compared to Scala CLI.
The old ways
In the old runner, the first argument was treated as the input source, while the second and following arguments were considered program arguments.
@main def main(args: String*): Unit = println(args.mkString(" "))
scala_legacy Source.scala programArg1 programArg2
Since everything after the first argument had to be arbitrarily read as a program argument, regardless of format, all runner options had to be passed before the source input.
scala_legacy -save Source.scala programArg1 programArg2
The ways of Scala CLI
With Scala CLI's default way of handling arguments, inputs and program arguments have to be
divided by --
. There is no limit for the number of either.
def placeholder = println("Example extra source")
scala Source.scala Source2.scala -- programArg1 programArg2
Additionally, a Scala CLI sub-command can be passed before the inputs section.
For example, to call the above example specifying the run
sub-command explicitly, pass it like this:
scala run Source.scala Source2.scala -- programArg1 programArg2
More on sub-commands can be found here.
Runner options can be passed on whatever position in the inputs section (before --
).
For example, all the following examples are correct ways to specify the Scala version explicitly as 3.2
scala -S 3.2 Source.scala Source2.scala -- programArg1 programArg2
scala Source.scala -S 3.2 Source2.scala -- programArg1 programArg2
scala Source.scala Source2.scala -S 3.2 -- programArg1 programArg2
The exception to this rule are the launcher options, like --cli-version
or --cli-scala-version
.
Those have to be passed before the inputs section (before any source inputs).
For example, to explicitly specify the launcher should run Scala CLI v1.5.0
, pass it like this:
scala --cli-version 1.5.0 Source.scala Source2.scala -- programArg1 programArg2
Also, if a Scala CLI sub-command is being passed explicitly, all launcher options have to be passed before the sub-command.
For example, to call the package
sub-command using the nightly CLI version, do it like this:
scala --cli-version nightly package --help
The Scala CLI shebang
sub-command
To provide better support for shebang scripts, Scala CLI
has a dedicated shebang
sub-command, which handles arguments similarly to the old scala
script.
scala shebang Source.scala programArg1 programArg2
The purpose of the shebang
sub-command is essentially to only be used in a shebang header (more
details on that can be found in a later section of this guide or in the
separate shebang scripts' guide), but nothing is really stopping you from using it from the command
line, if you're used to how the old scala
runner handled arguments. Just bear in mind that it is not the intended user
experience.
How are the old scala
runner options supported?
For backwards compatibility's sake, Scala CLI accepts all the old scala
runner options, although many of them have
been deprecated and are no longer supported in the new runner. This includes accepting all the Scala 2.13.x
and 3.x
respective runners' specific options.
Fully supported old scala
runner options
The following old scala
runner options are fully supported by Scala CLI, meaning that they deliver similar or expanded
functionalities with backwards-compatible syntax:
-e
, which is an alias for Scala CLI's--execute-script
and a close synonym for--script-snippet
-v
/-verbose
/--verbose
, which can be passed multiple times with Scala CLI, increasing the verbosity-cp
/-classpath
/--class-path
, which adds compiled classes and jars to the class path-version
/--version
, which prints the currently run Scala CLI version information-with-compiler
, which adds the Scala compiler dependency to the Scala CLI project- Scala compiler options (with some requiring to be passed with
-O
, more info in the section below) -J<arg>
Java options-Dname=prop
Java properties
Old scala
runner options which have a different meaning in Scala CLI
The following old scala
runner options not only are not supported with their old functionalities, but have a different
meaning in Scala CLI:
-i
, which is now an alias for Scala CLI's--interactive
mode-h
/-help
- in the old Scala
2.13.x
scala
runner, it used to print the help of the runner - in the old Scala
3.x
scala
runner however, it used to print the Scala compiler help instead - Scala CLI takes an approach similar to the old Scala
2.13.x
runner, and it prints Scala CLI help - to view the Scala compiler help with Scala CLI, pass the --scalac-help option instead
- in the old Scala
Deprecated and unsupported old scala
runner options
The following old scala
runner options have been deprecated and even though they are accepted by Scala CLI (passing
them will not cause an error), they are ignored with an appropriate warning:
-save
, refer to thepackage
sub-command on how to package a Scala CLI project to a JAR-nosave
, a JAR file is now never saved unless thepackage
sub-command is called-howtorun
/--how-to-run
- Scala CLI assumes how a file is to be run based on its file extension (and optionally its shebang header). This
cannot be overridden with a command line option, so ensure your inputs use the correct file extension or have
the shebang header defined. This is sort of the equivalent of the
old
-howtorun guess
. - To run the
REPL
, refer to therepl
sub-command - This option has been largely replaced with Scala CLI's sub-commands
- Scala CLI assumes how a file is to be run based on its file extension (and optionally its shebang header). This
cannot be overridden with a command line option, so ensure your inputs use the correct file extension or have
the shebang header defined. This is sort of the equivalent of the
old
-I
, to preload the extra files for theREPL
, try passing them as inputs for the repl sub-command-nc
/nocompdaemon
, the underlying script runner class can no longer be picked explicitly, as with the oldscala
runner-run
- Scala CLI does not support explicitly forcing the old run mode. Just pass your sources as inputs and ensure they are in the correct format and extension.
Scala compiler options
All compiler options are supported when passed with the --scalac-option
flag (or the -O
alias for short).
However, many compiler options can also be passed directly.
For more information, refer
to the Scala compiler options section of the compile
sub-command doc.
How does Scala CLI detect if it's running a script or a main method?
To answer this question, some disambiguation is necessary.
The most important thing to note is that this has been handled differently by the 2 old scala
runners (for
Scala 2.13.x
and for 3.x
), so a
consistent behaviour hasn't really been established before Scala CLI.
The Scala 2.13.x
old scala
runner was the most flexible, automatically detecting if what is being run is a script or
an
object based on the source contents. This automatic detection was also possible to be overridden with the -howtorun
runner option (which has been
deprecated and is not supported in Scala CLI,
as noted in an earlier section).
This also means that the 2.13.x
old scala
runner did not really care about file extensions much.
In contrast, the Scala 3.x
old scala
runner always expects to find a main method, potentially but not necessarily
using the Scala 3 idiomatic @main
annotation.
This means that the Scala 3.x
runner respected main methods defined in .sc
files, but did not support script
syntax (top level definitions with no explicit main method).
Scala CLI's approach is perhaps the most restrictive here.
It accepts explicitly defined main methods in .scala
sources and script syntax in .sc
sources, without any
additional flexibility.
The only exception would be files with no file extension, but with a shebang header, ran with the shebang
sub-command.
Those are always treated as scripts (more details about this can be
found [in the shebang scripts' guide]).
Now, to give some examples.
Main class in a .scala
input
Of course, the simplest case is putting a main class into a .scala
source, which is supported by both of the old
runners and by Scala CLI.
object Main {
def main(args: Array[String]): Unit = println(args.mkString(" "))
}
scala_legacy Main.scala Hello world
scala Main.scala -- Hello world
Hello world
Main class in a .sc
input
object Main {
def main(args: Array[String]): Unit = println(args.mkString(" "))
}
This case has been supported by both of the old scala
runners, but is not supported by Scala CLI, which expects a
script in a .sc
input and wraps its contents in a main class of its own, not inspecting further for a nested one.
In other words, when explicitly declaring a main class when working with Scala CLI, you have to do it in a .scala
file.
scala main-in-script.sc -- Hello world
# no output will be printed
Running such an .sc
file will not fail by the way, but neither will it print any output, since the appropriate method
hasn't been called explicitly in the script.
Script syntax in an .sc
file
println(args.mkString(" "))
This syntax is supported by the old Scala 2.13.x
runner, but not by the old Scala 3.x
one.
The Scala 3.x
runner does not allow for top level definitions without an explicit main class.
However, it is supported by Scala CLI.
scala script.sc -- Hello world
Hello world
Script syntax in a .scala
file
Now for the inverted case, where script-style top level definitions are put in a .scala
input.
println(args.mkString(" "))
This has actually been supported by the old Scala 2.13.x
runner.
However, both the old Scala 3.x
runner as well as Scala CLI do not support it.
scala script.scala -- Hello world
[error] ./ScriptInScala.scala:1:1
[error] Illegal start of toplevel definition
[error] println(args.mkString(" "))
[error] ^^^^^^^
Error compiling project (Scala 3.2.2, JVM)
Compilation failed
Inputs with no extension
println(args.mkString(" "))
object Main {
def main(args: Array[String]): Unit = println(args.mkString(" "))
}
Files with no extensions have been supported in the 2.13.x
old runner, but not in 3.x
.
Script syntax in files with no extension (or with extensions not indicating other kinds of sources, like .java
) are
supported in Scala CLI via the shebang
sub-command (and not otherwise).
However, a shebang header is necessary. An example is given
in a later section of this guide.
How to migrate scripts with the old scala
runner in the shebang header to Scala CLI?
As described
in an earlier section of this guide,
the way the old scala
runner handles arguments differs from Scala CLI.
The old scala
script accepted arguments with syntax making it easy to use it in a shebang header.
That is, all arguments starting with the second were treated as program args, rather than input sources.
This is in contrast with the Scala CLI default way of handling arguments, where inputs and program arguments have to be
divided by --
.
scala Source.scala Source2.scala -- programArg1 programArg2
To better support shebang scripts, Scala CLI has a dedicated shebang
sub-command, which handles arguments similarly to
the old scala
script.
scala shebang Source.scala programArg1 programArg2
For more concrete examples on how to change the shebang header in your existing scripts, look below.
Example shebang script with the Scala 2.13.x
old scala
runner
This is how an example shebang script could have looked like for the old scala
runner with Scala 2.13.x
#!/usr/bin/env scala
println("Args: " + args.mkString(" "))
Example shebang script with the Scala 3.x
old scala
runner
This in turn is the Scala 3.x
equivalent for its own old scala
runner.
#!/usr/bin/env scala
@main def main(args: String*): Unit = println("Args: " + args.mkString(" "))
Example shebang script with Scala CLI
This is an example of how a Scala CLI script with a shebang header looks like.
#!/usr/bin/env -S scala-cli shebang
println("Args: " + args.mkString(" "))
The example above refers scala-cli
, as per the current default Scala CLI distribution.
If you have Scala CLI installed as scala
, then that should be changed to the following:
#!/usr/bin/env -S scala shebang
println("Args: " + args.mkString(" "))
For more information about the shebang
sub-command, refer to the appropriate doc.
For more details on how to use Scala CLI in shebang scripts, refer to the relevant guide.
How to run a main class from compiled sources with Scala CLI?
With the old scala
runner, running a main class from compiled sources was as simple as passing the main class name
as an argument. The old runner would then assume that the current working directory is to be added to the classpath and could
implicitly run any compiled class files it would find.
@main def hello = println("Hello")
This syntax has been dropped and is no longer supported with the new scala
runner.
scalac hello.scala
scala_legacy hello # NOTE: this syntax is not supported by Scala CLI
# Hello
With Scala CLI, all inputs have to be passed explicitly, so any compiled classes in the current working directory would be ignored unless passed explicitly.
scalac hello.scala
scala run -cp .
# Hello
If only the classpath is passed with -cp
, then the run
sub-command can't be skipped, as otherwise Scala CLI
will default to the REPL (as there are no explicit source file inputs present).
scalac hello.scala
scala -cp .
# Welcome to Scala 3.5.0 (17, Java OpenJDK 64-Bit Server VM).
# Type in expressions for evaluation. Or try :help.
#
# scala>
It is possible to explicitly specify the main class to be run (for example, if there are multiple main classes
in the build). The run
sub-command becomes optional then, as passing -M
indicates the intention to run something.
scalac hello.scala
scala -cp . -M hello
# Hello
If you want to compile your sources with a separate command, and then run them later, you can also do it
with the compile
sub-command, rather than the scalac
script.
You don't have to specify the class files location, Scala CLI won't recompile them if they are up to date.
scala compile hello.scala
scala hello.scala
# Hello
Alternatively, you can also specify the location for the compiled classes explicitly, and then add them
to the classpath, as you would with scalac
.
scala compile hello.scala -d compiled_classes
scala run -cp compiled_classes
# Hello