Graal Native Launchers by Default
Mill 1.0.0 defaults to using a Graal-Native Image launchers by default. This shaves significant overhead off of Mill command line invocations, meaning running ./mill commands in the terminal can often complete in as little as 100ms:
Slow startups and long warmup times have always been a pain point working in the JVM ecosystem, and this has always applied to JVM command-line tooling as well. Mill had previously used a long-lived background daemons to try and mitigate the problem, but even if the heavyweight background daemon is long-lived, the lightweight launcher/client process still adds a substantial overhead. With Mill’s Graal Native Image launcher, Mill’s command-line experience feels as snappy as any native binary, because it is one!
Apart from being a Graal Native Image itself, Mill also can be used to build your own Java, Scala, and Kotlin apps into native images. See Building Native Image Binaries with Graal VM for more details.
JVM-free Installation and Bootstrapping
One consequence of using a Graal native launcher is that Mill’s launcher no longer needs a JVM pre-installed in order to run. The launcher binary is self-contained, and is able to download and cache any JVM it needs in order to start itself or other Java processes.
This means that Mill is able to manage your JVM installations completely on your behalf, without you needing to first install a JVM ahead of time, external tools like JEnv or SDKMan, or package managers like Homebrew. Mill can manage the JVM necessary for Mill itself to run, any JVM necessary for your project modules to run, as well as any third-party libraries used in both scenarios.
With this change, Mill becomes one of the few CLI tools that is truly zero-install: by using Mill’s bootstrap script (an idea borrowed from Gradle’s ./gradlew and Maven’s ./mvnw) you can run ./mill on any clean Mac, Linux, or Windows or development machine or CI worker and have it bootstrap everything necessary with zero installation up front. That’s pretty unique among the wide variety of CLI tools out there, and should hopefully make it convenient for Mill users to build their JVM projects on the wide variety of different environments out there in the wild.
Bash/Zsh Tab-Completion
Once set up, you can then use <TAB> to auto-complete Mill modules and tasks from the command-line:
While this is not rocket science, we expect that this will be a significant quality-of-life improvement to everyone using Mill. Nobody memorizes the names of every module and task within a large project, and using ./mill resolve to try and list things or opening your IDE to rely on in-code autocomplete can be tedious and annoying. Mill’s Bash/Zsh tab-completion relies on the same underlying infrastructure that powers ./mill resolve and other existing functionality, but integrates it nicely into your shell so you have a smooth experience exploring and interacting with your build from the command line
Task Filesystem Sandbox Enforcement
Like Gradle, Mill build files contain arbitrary JVM code. And like Gradle, writing arbitrary code means you have a chance to make a big mess of your Mill build files. For example, although Mill relies on the assumption that tasks would only write to their destination folder (available as Task.dest within any task body), and that module initialization did not write to the filesystem, there was never enforcement of these expectations:
This meant it was possible to write code that violated Mill’s internal expectations, which could cause all sorts of things to go wrong: cache invalidation issues, race conditions, etc. So onboarding new developers to use Mill often resulted in a long process to teach them these conventions since the tool itself would not give any feedback until things loudly (or worse, silently!) misbehaved.
In Mill 1.0.0, most common filesystem read/write APIs have been instrumented to check that they are not misused. That means code that violates Mill’s conventions raises an immediate error:
These sandboxes are not intended to be fully hermetic: there are escape hatches (e.g. BuildCtx.withFilesystemCheckerDisabled{ … }), not all APIs are instrumented (e.g. direct use of java.io./java.nio), and they are not intended to be a security boundary against malicious code. Rather, they are meant to be simple guardrails to nudge developers in the right direction to follow Mill’s conventions and coding style, so that developers will naturally fall into configuring Mill the "right" way that leads to the fewest surprises.
Mill now supports a build header comment syntax for "early" configuration that needs to be processed before the main build.mill file is compiled and evaluated, and contains configuration like the mill-version, mill-jvm-version, the build.mill file’s mvnDeps (separate from your application code’s mvnDeps!) in a single //|-prefixed comment block. This is reminiscent of Jekyll FrontMatter or Python’s PEP723 Inline script metadata:
build.mill
Previously, Mill had a wide variety of ways these things were configured:
-
A .mill-version file to configure your Mill version
-
A .mill-jvm-version file to configure the JVM used to run the Mill process
-
.config/mill-version and .config/mill-jvm-version flavors of these config files
-
import $ivy for configuring dependencies for your build.mill
-
import $repo for configuring maven repositories used to resolve dependencies for compiling your build.mill
These are all configuration values that need to be used early on in the Mill bootstrapping process, and thus we couldn’t rely on them being configured in the "main" build.mill config-as-code. For example, before even compiling your build.mill, Mill already needs to know what version of Mill you want to use and what JVM to run it on!
With Mill’s YAML build headers, we can consolidate this zoo of different configuration styles into a single compact block at the top of every build.mill. While the older configuration styles continue to be supported for migration-compatibility, using Mill’s build headers is the recommended approach for configuring these values going forward.
Lastly, build headers are expected to future-proof this "early" configuration and allow all sorts of interesting use cases in future. For example, we can extend this format to support running self-contained Java/Scala/Kotlin scripts that contain both their dependency configuration and code, similar to uv scripts in Python.
Mill Support for Kotlin Builds is now Stable
Mill 1.0.0 includes substantial improvements for building Kotlin projects with Mill:
These PRs really flesh out the previously-experimental support for Kotlin projects in Mill: main class discovery, compiler plugins, BuildInfo support, etc.. Other PRs upstream in the Coursier add support for resolving Kotlin Multiplatform dependencies, supporting Kotlin-JS and Kotlin-Android projects.
With 1.0.0, Kotlin support in Mill is no longer experimental, and we are enabling binary-compatibility enforcement for mill.kotlinlib just as we already have for mill.javalib and mill.scalalib. We hope that you will try out Mill in your Kotlin projects, and let us know how it goes in Mill’s Github Discussions.
Mill Support for Android Builds
A huge amount of work went into 1.0.0 improving Mill’s support for Android builds. Android apps have traditionally only been buildable using Gradle, and Mill is one of the only other build tools that you can use as an alternative. While in 0.12.x Android support was a demo-quality integration, in 1.0.0 it has been fleshed out into a robust and complete framework that can build many of the sample apps available in the Android ecosystem.
This work was done by the folks VasLabs, who put in an immense amount of work:
Updating the Mill Android documentation is a work in progress. We will be fleshing out the Mill Android docs over the following weeks and months and writing up blog posts on our experience integrating Mill with the Android toolchain But if you are unsatisfied with Gradle and interested in trying out an alternate Android build tool, you should definitely take a look and try it out and let us know how it goes in Mill’s Github Discussions.