Build CLI application with Scala using airframe-launcher and sbt-pack

Naoki Takezoe
3 min readDec 17, 2019

I use Scala for both my work and hobby daily basis. Usually I use Scala for building web applications or middlewares, but sometimes I want to use Scala for CLI applications too when they require complex programming or we can use existing Java/Scala libraries for them.

Airframe is a set of Scala libraries based on DI container and it contains airframe-launcher which is a useful library for CLI applications. Also, sbt-pack which is a sbt plugin for the application packaging enables us to package and install any Scala applications as executable CLI applications. Let me show you how to build CLI applications with Scala using them in this article.

Firstly, add the following dependency to your build.sbt:

libraryDependencies ++= Seq(
"org.wvlet.airframe" %% "airframe-launcher" % "19.11.1"
)

As a first example, let’s create a simple application which shows a message in the terminal. We can use @option for command line options, @command annotation for sub commands. If the sub command is not specified at runtime, the default command which is annotated with isDefault = true is called.

package com.github.takezoe

import wvlet.airframe.launcher.{Launcher, command, option}

class Hello(
@option(prefix = "-n,--name", description = "your name")
name: String,
@option(prefix = "-h,--help", description = "show help", isHelp = true)
displayHelp: Boolean
) {
@command(isDefault = true)
def default(): Unit = {
println(s"Hello ${name}!")
}
}

object Main extends App {
Launcher.execute[Hello](args)
}

Next, package this application using sbt-pack. Add the following line to your project/plugin.sbt:

addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.11")

Also, you need to add packaging configuration to build.sbt which enables PackPlugin and defines the mappings of the command name and the main class as follows:

enablePlugins(PackPlugin)
packMain := Map("hello" -> "com.github.takezoe.Hello")

Then, try to run sbt pack. It will generate a set of files for CLI applications under target/pack directory. In addition, sbt packInstall installs your CLI application into $HOME/local/bin. I recommend to add this directory to PATH so that we can use installed commands easily.

Let’s test the installed command.

$ hello -n Naoki
Hello Naoki!

It worked. As a next step, let’s add a sub command to this CLI application. Add a method below to Hello class. We can define sub command specific options as arguments of a sub command method as we’ve saw in the constructor:

@command(description = "display a message repeatedly")
def repeat(
@option(prefix = "-c,--count", description = "repeat count")
count: Int
): Unit = {
for(_ <- 0 to count){
println(s"Hello ${name}!")
}
}

Install and test the command again:

$ hello repeat -n Naoki -c 3
Hello Naoki!
Hello Naoki!
Hello Naoki!

Perfect! Also, airframe-launcher can generate a help message automatically. If we run the command with an option which is annotated with isOption = true, a help message like below is generated:

$ hello --help
usage: hello [options] <command name>

[options]
-n, --name:[NAME] your name
-h, --help show help message

[commands]
repeat display a message repeatedly

Of course, this is one way to build CLI applications with Scala. Since Scala applications have a bootstrap time of JVM, it might not suitable simple CLI tools. However, when we have to create a large CLI application or we can leverage existing Java libraries, it would be worth to write in Scala. I hope that Scala will be used in wider fields.

--

--