This is a somewhat controversial post because we got to use scala-cli outside its purpose, as a build-tool for a multi-module project.

scala-cli-is-not-a-build-tool

scala-cli: We do not call Scala CLI a build tool

So far, we are very happy with the outcome and we are keen to continue trying this approach in other simple projects.

Summary

I have been jealous from Rust ecosystem in the sense that cargo already handles many common tasks without the need to install any plugin, for example, besides compiling and running the app, it also executes the tests, formats the code, packages the application into a binary, etc.

Turns out that scala-cli let us do that and more, all of this with a fast startup time (no need to load 13k settings at startup, looking at you sbt).

Not only that, our Continuous Integration (CI) workflow runtime is consistently below 1 minute (check code format, run tests, package modules), the most expensive step being downloading dependencies (which is cached):

github-actions-runtime

38s github-actions workflow execution

Context

The project benefiting from this setup is codepreview.io, a tool to create preview environments from Pull Requests which is mainly built with Scala and shines with Scala projects.

The way that codepreview.io works is by defining a small Scala script that holds the custom environment requirements for each project, such a script gets packaged by scala-cli, and, it eventually get executed by a CI workflow that creates the preview environment.

Show me the code

I have prepared a demo repository for this.

This is the file structure:

├── common
│   ├── SharedCode.scala
│   └── SharedCode.test.scala
├── LICENSE
├── module-1
│   ├── App.scala
│   └── package.sh
├── module-2
│   ├── App.scala
│   └── package.sh
└── README.md

3 directories, 8 files

Let’s expand module-1/App.scala:

//> using file "../common/SharedCode.scala"

object App {
  def main(args: Array[String]): Unit = {
    println(
      renderText("module-2")
    )
  }
}

Show me the CI

The CI is compiling the code, checking its format, as well as packaging the module apps.

The first execution took 1m33s, most of the time is spent downloading and caching dependencies:

ci-demo-execution-1

The first execution took only 25s! This is an outstanding runtime for scala projects:

ci-demo-execution-2

Conclusion

While scala-cli does not advertise itself as a build-tool, we saw that it can be a viable option for small projects.

I’m keen to try it on bigger projects and see how far we can go, could it possible be as nice for a simple fullstack Scala application?

Thanks.