Scala 3 Odyssey: Part 3 — Empowering Scala Development with SCALA-CLI
A new article is here, fresh off the press, focusing on scala-cli, a powerful tool designed to enhance Scala development.
If you missed my first articles in this mini-series, you can find at
They are a gently introduction to scala programming languages and the main tools helping (easing) the development of scala projects.
In this article I am focused on the scala-cli, a tool that every scala developer must know about and use.
Scala-cli
Introduction
It is well known the pleasure brought by scala (version 3 in particular) to scala development around the world.
But history unveiled to us that that one of the major pain points was the tooling support.
This is where scala-cli comes into play and hugely diminish this problem.
It’s a tool that aims to empower scala development by providing a set of options that can be used to create, build, test and run scala projects.
Another big step towards improving the tooling support is the fact that scala-cli is going to be builtin from scala 3.5 onward.
Moreover, Metals along with VS Code (or neovim) streamline the development process (but this is a topic for another article).
Without further ado let’s dive into the scala-cli world.
Installation
There are quite some ways to install scala-cli.
The most common ones are:
- using sdkman tool, via
sdk install scalacli
or
sdk install scalacli 1.3.2
if you want to install a specific version.
- using coursier CLI, by issuing the following command
cs install scala-cli
Needless to say the (macOS) omnipresent homebrew tool can be used to install scala-cli, via
brew install scala-cli
But, for a scala developer this is not at all a common way to install scala-cli.
Regardless the installation method, the success of the installation can be checked by issuing the following command:
scala-cli — version
Noteworthy features
Scala-cli has tons of features, but I am going to mention some of the most important ones:
- compile code,
- run code (*.scala files, *.sc scripts or a SBT scala project),
- run tests,
- package the project (fatjar, native with GraalVM or scala native, etc),
- manage dependencies using directives or command line arguments,
- manage java and scala versions using directives or command line arguments,
- use ammonite REPL via scala-cli,
- allow java VM options to be passed to the running project or file,
- use scalaftm formatting support,
- generate documentation…
Usage
The simple hello world example.
- Create a file named hello.sc and add the following code:
println("Hello scala-cli amazing world")
- Run the code by issuing the following command
scala-cli run hello.scala
*.sc are scala scripts, which means that they run all the code in the file.
This is like the *.py files in python, or *.js files in javascript.
On the other hand we might have *.scala files with main entry-point, and we’d like to run the main method in the file.
For this case, create a file Hello.scala and edit it as per below
@main def hello() =
println("Hello scala-cli amazing world!")
Run this scala file by using the same above command
scala-cli run Hello.scala
The output might look, more of less, as per below
$ scala-cli run Hello.scala
Compiled project (Scala 3.4.2, JVM (17))
Hello scala-cli amazing world!
Notes:
- As a keen-eyed reader, you might have noticed that the output contains additional information along with the application output — in our case, the Scala version,
This superfluous information in console might be removed by adding the following argument -q (or — quiet)
scala-cli run -q Hello.scala
- The command scala-cli run is so widely used that it has a shortcut, like not passing run option at all, like in
scala-cli Hello.scala
Use case to highlight the power of scala-cli
Let’s suppose we have a project with a peculiar behavior for a very specific combination of java, scala and scalatest versions.
Those versions supposedly are:
- java 11,
- scala 3.3.3,
- scalatest-shouldmatchers 3.2.18
The hypothetical case is that should contain matcher doesn’t work in this combination of versions.
So that we put in place the following code in the scalatestcase.sc file:
//> using jvm 11
//> using scala 3.3.3
//> using dep org.scalatest::scalatest-shouldmatchers:3.2.18
import org.scalatest.matchers.should.Matchers.*
List(1, 2, 3) should contain (10)
and run it
scala-cli run scalatestcase.sc
scala-cli takes care of downloading the specified versions of java, scala and scalatest-shouldmatchers, and run the code, as can be seen in the following output.
We were not on the right path with our assumption, but scala-cli easy this kind of testing when different versions of libraries, scala and java are involved.
Next use case- create a simple CLI and package it as a native image
Given the following code in the greetcli.scala file:
@main
def main(name: String, count: Int) =
for i <- 1 to count do
println(s"Hello, $name!")
and after testing it using
$ scala-cli run greetcli.scala — Tony 3
Compiling project (Scala 3.4.1, JVM (17))
Hello, Tony!
Hello, Tony!
Hello, Tony!
we might want to package it as a native image (with scala-native).
It might be hard to find an easy way of packaging the CLI script as a native image, but in scala-cli is just a matter of running the following command
scala-cli — power package — native greetcli.scala — scala-version 3.4.2 -o greet
After a few seconds necessary to download the scala native dependencies, and compile the code, the native image is created.
The native image (our CLI) can be run by executing the following command:
$ time ./greet Tony 3
Hello, Tony!
Hello, Tony!
Hello, Tony!
./greet Tony 3 0.00s user 0.00s system 66% cpu 0.007 total
Notes:
- I’ve wrapped the command in the time command to show the execution time of the native image.
- The size of the obtained native image is 1.5 MB, and it runs instantaneously, which is quite impressive.
This might be an amazing feature for those who want to create a CLI tools.
Types of artifacts that can be created by scala-cli are:
- lightweight launcher JARs,
- standard library JARs,
- so called “assemblies” or “fat JARs”,
- docker container,
- JavaScript files for Scala.js code,
- GraalVM native image executables,
- native executables for Scala Native code,
- OS-specific formats, such as deb or rpm (Linux), pkg (macOS), or MSI (Windows).
Many other scala-cli features are available, such as:
- a command option to install scala-cli autocomplete in the shell
scala-cli install-completions
- scala-cli can be used for simple java files, as can be seen below
Hello.java file
public class Hello {
public static void main(String[] args) {
System.out.println("Hello scala-cli amazing world!");
}
}
and by running the command
scala-cli run Hello.java
the output will obviously be:
Hello scala-cli amazing world!
Ammonite integration
There have been many explanations of scala-cli’s capabilities, but I believe that showcasing the amazing Scala companion, Ammonite REPL, completes the picture of scala-cli’s new frontier.
Ammonite facilitates a seamless programming by experimenting, directly in your preferred terminal (console).
This way the usage of an IDE and build tools might be avoided, with all the burden that comes with them, at least for those unfamiliar with them (or probably to power users).
A sneak peak into this ammonite related scala-cli feature is shown by running the following command:
scala-cli repl — ammonite — power
We get jump into the ammonite REPL, with all the its power, such as code completion, syntax highlight, scala API signatures, output, compilation errors and exceptions pretty printing and many more.
References
- scala-cli
- scala-cli packaging
- ammonite
- scala-cli in scala 3.5+
Closing thoughts
My fingers got tired of typing, but I hope the reader got a glimpse of scala-cli power.
I really hope you enjoyed the article at least as much as I enjoyed writing it.
Scala-cli is a powerful tool that can be used to streamline the scala development process.
And I consider that along with Visual Studio Code and the other tools I’ve touched briefly in first articles (and going to be described in the next ones), it lays down the foundation for the most of the next articles in the series.
Until next time, happy coding, and if you like my articles don’t forget to clap!
Thank you, and stay tuned for more!