Mill: your shiny new Scala build tool

Hi all! I just published the 0.1.0 release of Mill:

Mill is a new build tool that can serve as a replacement for SBT. It aims to be fast, simple, and friendly.

The linked docs give an introduction to Mill, what it is and how to use it. There are example builds for a bunch of open source projects linked from the documentation (Acyclic, Ammonite, Better-Files, Jawn, Upickle) so you can see how Mill is used in real situations to build real code.

Mill is under active development, but is already very usable and can be used as a substitute for SBT in many real projects. If you are interested in trying out a new build tool, give Mill a shot!


How does it compare to cbt? Judging by the example on the page it looks like you’re using normal Scala inheritance to express dependencies, which is also the big simplification that cbt claims over sbt.

Mill is basically a cross between the best parts of CBT and Bazel

On top of CBT, Mill adds

  • An introspectable/reified task graph (lets you query dependencies/etc about tasks without actually running them, makes parallelization easier in future)
  • Aggressive caching (tasks themselves need not care about this at all; it’s handled automatically
  • Dedicated per-task working directories, to avoid collisions when tasks work with things in disk
  • Automatic generation of command-line entrypoints (using Ammonite’s @main method logic)
  • A flexible query syntax for running multiple targets at the same time
  • Non-classpath plugin support: Zinc, Scalajs-linker, and your own compilers can all be resolved/built like normal targets and do not need to live in the build file classpath, and so you can e.g. have multiple versions of scala.js building in the same build
1 Like

Dependencies are not expressed using inheritance at all.

IIRC, in CBT there isn’t really such a thing as task dependencies. You just call methods like normal Scala code.

In Mill dependencies work more like in SBT: Even though it looks like you’re referring to a Scala value directly, it’s actually a macro that analyzes your dependencies statically. Unlike SBT tasks aren’t monadic but applicative, though.

Most people I would never just run a "sudo curl -L -o /usr/bin/local… "
command for… But for you Haoyi, anything.

Just taking it out for a short spin, Mill looks really nice so far!

Brian Maso

I’m extremely impressed so far, even the IntelliJ project it generated seems to work correctly, which is more than I can say for IntelliJ’s SBT import. I spent some time today trying to port the ginormous multi module build with code generation that I’m using at my current employer and my initial impressions were

  • The build file is a lot easier to understand without specific knowledge of the build tool
  • Startup time is way better and the overhead of running the tool itself seems subjectively to be lower
  • So far everything has just worked out of the box apart from some niggles with resolution that were solved by blasting my Coursier, Ivy and Maven caches - I think this was a Coursier bug rather than Mill
  • The build is just as discoverable as SBT
  • The caching seems to work well and is very welcome for the code generation, where we had written our own ad hoc caching mechanism in the SBT build

This sounds awesome! Could you say something about the future of Mill? To begin with, what do you think remains to be done for a 1.0 release? How far off do you think that might be? How does the inner circle of the Scala universe view your efforts – might they put their weight behind this someday, or will the “official” build tool always be SBT?

1 Like

Hey @lihaoyi,
It is not clear to me what’s the idiomatic way to customize an existing task with Mill. In comparison, sbt promotes the usage of scoped keys:

artifactPath in fastOptJS := …

What’s the recommended way to achieve this in Mill?

If you want to replace the existing task, just def over it to override it. You can call super if you want the old value.

If you want to define a new task and keep the old task around, then def the new task with a new name and keep the old task around

If you want a whole new set of tasks which are the same as the old set but with one or two customize, put those tasks in a trait which you instantiate multiple times and each time override the thing you want to customize

Ditto the previous sentiment.

For future reference, in the forum, selecting text + reply auto-inserts some weird quote syntax.

First, thanks again for the amazing work on yet another amazing tool!

Just a suggestion; if Mill is really basing many of its principles on CBT,
I think some credit should be given in the docs at e.g.

Not just for giving some credit where credit is due (though that never
hurts), but also to outline some of the ideas described above.
Additionally, it would be great to be as unbiased as possible: are there
cases where someone might prefer CBT to Mill, and if so, why. This last one
may not need to go directly in the docs, but as someone who has been
enthusiastic about CBT for some time, I’d really like to know. Based on the
discussion above, I can’t think of anything definite, since it sounds like
the rigidity imposed by the task graph may be relatively easy to sidestep
(please correct me if I’m wrong). However, one point is just that a task
graph may behave in undesired ways (or at least, non-obvious ways): you
can’t see what is going on, as easily as you could if it were coded up in
simple Scala. Again, I haven’t used either tool extensively, and Mill not
at all as yet.

1 Like