Scala/Mill Build Tool: Step 1, Creating a new Mill project

I’m currently working on a small Scala project, so I thought I’d try Li Haoyi’s Mill build tool – currently at version 0.6.1 — for about a week and see how things go. In this tutorial I’ll share an example of how to use Mill on a little “Hello, world” example.

My Github project

If you want to follow along with the discussion that follows, I put my Mill “Hello, world” Github project here:

Installing Mill

To install Mill, see this page.

I initially installed Mill with Homebrew, but that involved Homebrew installing OpenJDK 13, which seemed like overkill for what I want to do, so I uninstalled that and then reinstalled Mill with the “Manual” approach.

Creating a Mill project

I’m used to working with the common/standard directory structures that Maven and SBT use, but Mill is a little different. Mill seems to promote the concept of potentially having multiple modules or sub-projects inside a main project, so its directory structure is different.

For a little Scala project, creating a Mill directory structure looks like this:

mkdir MillTest1
cd MillTest1

mkdir HelloWorld
mkdir -p HelloWorld/src/main/scala
touch build.sc

Visually that directory structure looks like this:

> tree .
.
├── HelloWorld
│   └── src
│       └── main
│           └── scala
└── build.sc

For the rest of this tutorial I’ll assume that you’re in the MillTest1 directory, with build.sc in the current directory and HelloWorld as a subdirectory.

Mill’s build.sc file

Where SBT uses a build.sbt build file, Mill’s configuration file is named build.sc file. With our project going under the HelloWorld directory, the simplest possible build.sc file looks like this:

import mill._, scalalib._

object HelloWorld extends ScalaModule {
    def scalaVersion = "2.12.11"
}

Notice that the name of the object here — HelloWorld — must match your subdirectory name. This makes sense when you think about having multiple modules. For instance, if you had modules named Foo and Bar in directories named Foo and Bar, your build file would look like this:

import mill._, scalalib._

object Foo extends ScalaModule {
    def scalaVersion = "2.12.11"
}

object Bar extends ScalaModule {
    def scalaVersion = "2.12.11"
}

Creating a HelloWorld example

To create a “Hello, world” example, I want to put my source code in a package named hello, so I first create a subdirectory under the src tree:

mkdir HelloWorld/src/main/scala/hello

Then I put these contents in a file at HelloWorld/src/main/scala/hello/Hello.scala:

package hello

object Hello extends App {
    println("Hello, world")
}

Running the example

To compile and run this example, run this mill command at your command line:

mill HelloWorld

The output of that command looks like this:

[27/37] HelloWorld.compile 
Compiling compiler interface...
warning: there were three deprecation warnings (since 2.12.0); re-run with -deprecation for details
warning: there were three feature warnings; re-run with -feature for details
two warnings found
[info] Compiling 1 Scala source to MillTest1/out/HelloWorld/compile/dest/classes ...
[info] Done compiling.
[37/37] HelloWorld.run 
Hello, world

If that worked, congratulations, you just compiled and ran your first Scala project with Mill.

It’s cool to see the “Hello, world” output, but ...

Deprecation and feature warnings?

It’s a little weird to see deprecation and feature warnings on a one-class project like this, so let’s see if we can figure out what’s going on.

From this example it looks like I should be able to specify the scalac deprecation and feature options like this:

import mill._, scalalib._

// HelloWorld must match the subdirectory name
object HelloWorld extends ScalaModule {
    def scalaVersion = "2.12.11"

    def scalacOptions = Seq(
        "-deprecation",
        "-feature"
    )
}

However, after running these commands:

mill clean
mill HelloWorld

I still see the same output. I also tried this and it didn’t work:

def scalacOptions = super.scalacOptions ++ Seq( "-deprecation", "-feature" )

so ... this item is TBD.

scalac works fine

Note: Just in case I missed something obvious, I came back and compiled my class manually using scalac 2.13.1, and did not see any warnings:

scalac HelloWorld/src/main/scala/hello/Hello.scala

So whatever those feature and deprecation warnings are about, they have nothing to do with my class.

Update: Day 3

For the first few days I worked on my project with a plain text editor, but today I started using VS Code. An interesting side effect of doing this is that VS Code recognizes that I’m working in a Mill workspace, and fires up the Metals and Bloop tooling, and some part of this eliminates the warning messages I write about here (and later in this tutorial).

This is what you see when you start VS Code inside my Mill project:

VS Code recognizes Mill build workspace

I’m not sure why this makes those initial warning messages go away, but I’ll try to look into that.

Other Mill commands

Skipping over that problem, it’s worth mentioning that there are several other Mill commands you can run. For instance, you compile your project like this:

mill HelloWorld.compile

You can also “continuously compile” your project — recompiling the project any time a file changes — with either of these “watch” commands:

mill --watch HelloWorld.compile
mill -w HelloWorld.compile

You can run your project with either of these commands:

mill HelloWorld
mill HelloWorld.run

I’ll show test-related commands in a future tutorial.

One other note: You can also run similar commands from inside the Mill REPL. The commands are a little different there, and I’ll show those in a future tutorial as well.

Mill output

Whenever you compile or run your project, Mill writes its output to the out directory inside your project. A potentially nice thing about this is that it writes a meta.json file to most of the directories inside that directory with information about the build. For instance, if you run a command like this on the “Hello, world” project:

find out | grep meta.json

you’ll see that there are 69 meta.json files under the out directory. I looked at a few of those files and didn’t see anything interesting, but the Mill documentation mentions that these files can be helpful when debugging problems.