Mill Build Tool
repository·main·Indexed 25 days ago
https://github.com/com-lihaoyi/millA build tool for Scala and Java projects featuring remote caching (1.2.0+), declarative module definitions via YAML (1.1.0+), and support for single-file scripts. It includes a playlib plugin for Play Framework applications and provides extensive examples for cross-building, web development, and complex multi-module Scala project structures.
What's inside Mill
- Mill is a build tool designed for the JVM ecosystem (Java, Scala, and Kotlin) that focuses on simplicity, programmability, and performance. It aims to provide a better developer experience compared to Maven and Gradle by using declarative configuration and aggressive caching/parallelism to achieve faster build times.
Use bundled uPickle for JSON parsing
mainMill includesuPickleas a bundled library. It is a fast, easy-to-use JSON library for Scala that supports automatic derivation of readers and writers for case classes.Understand Mill's Direct-Style build design
mainMill uses a "direct-style" design inspired by React.js. Instead of registering callbacks that mutate the filesystem (the traditional approach in tools like Gradle or Maven), you write normal functions that return the desired metadata or files.
Mill automatically handles the complex build logic—such as caching, parallelization, invalidation, and dependency tracking—based on the function calls and data flow you define. This allows you to write "naive" code that looks like a simple script, while Mill ensures it runs efficiently as a high-performance build pipeline.
Why Mill runs on the JVM
mainMill runs on the Java Virtual Machine (JVM) to take advantage of several key capabilities:
- Dynamic Classloading: Mill uses the JVM's ability to dynamically build, load, and run code. This enables features like running dynamic JVM code, meta-builds, and importing Maven plugins without needing a hard-coded plugin architecture.
- Extensive Tooling Ecosystem: Mill benefits from the massive JVM ecosystem, including:
- IDEs: Deep support in IntelliJ and VSCode.
- Profiling/Debugging: Access to tools like
jstack, JProfiler, and YourKit to diagnose stuck or slow builds. - Library Access: The ability to seamlessly import any JVM artifact from Maven Central or other repositories.
- Robust Publishing Infrastructure: Mill leverages the established JVM publishing standards (like Maven Central), providing a predictable and well-designed experience for distributing plugins and artifacts.
Understand Mill's bytecode callgraph analysis for task invalidation
mainMill uses JVM bytecode callgraph analysis to perform fine-grained task invalidation. Instead of aggressively invalidating all caches on every change, Mill analyzes the
.classfiles generated by the Scala compiler (frombuild.millorpackage.mill) to determine the impact of a change.How it works
- Post-processing: Mill runs a post-processing step on the
.classfiles. - Hash Computation: It computes a hash for every method, representing its own implementation and that of transitively called methods.
- Signature Storage: These hashes are saved to a
methodCodeHashSignatures.jsonfile. - Runtime Integration: The Mill evaluator includes these signatures in Task cache keys at runtime to decide if a task needs to be re-run.
Key Characteristics
- Conservative Correctness: The analysis is designed to be conservatively correct. If it is imprecise, it may cause a task to re-evaluate spuriously (a
- Post-processing: Mill runs a post-processing step on the
Understand Coursier's role in Mill
mainMill uses Coursier as its underlying engine for all third-party artifact resolution and management. Coursier handles the downloading and caching of Scala and Java dependencies (JVM languages) used by your projects.Relevant Mill Modules for Android Kotlin Projects
mainWhen building Android applications with Mill, the following modules are the primary building blocks:
mill.androidlib.AndroidSdkModule: Manages the Android SDK and essential build tools (e.g.,aapt,d8,zipalign,apksigner).mill.kotlinlib.android.AndroidAppKotlinModule: Provides the framework and workflow for building Android applications.mill.kotlinlib.KotlinModule: Provides general Kotlin build tasks such as compiling Kotlin code and creating JAR files.
Explore Java Web Project Examples with Mill
mainMill supports building various Java web applications and backend servers. You can use Mill to manage dependencies and build processes for several popular frameworks, including:
- Simple Web Example: A basic web setup.
- Spring Boot: Integration with the Spring Boot framework.
- Jetty: Running a 'Hello World' application using the Jetty server.
- Micronaut: Includes 'Hello World', 'TodoMvc', and 'Native (GraalVM)' compilation examples.
- Quarkus: Includes 'Getting Started' and 'TodoMVC' examples.
Understand Selective Testing approaches
mainSelective testing is a technique used in large codebases or monorepos to run only the tests necessary to validate a change, rather than the entire suite. The article outlines four main approaches:
- No Selective Testing: Running every test on every pull-request. This scales linearly $O(n)$ with codebase size and becomes a bottleneck as projects grow.
- Folder-based Selective Testing: Running tests only for folders containing changed files. This is simple but fails to catch breakages in downstream modules that depend on the changed code.
- Dependency-based Selective Testing: Running tests for the changed module and all its downstream dependencies. This is more robust but can be limited by module granularity and redundant testing of shared code.
- Heuristic-based Selective Testing: Using ad-hoc rules to optimize test runs, such as:
- Limiting Dependency Depth: Only running tests for a fixed number of downstream levels ($N$).
- Hard-coding Dependency Relationships: Manually defining specific test paths to bypass coarse-grained modules.
- Machine-Learning-based Selection: Using models trained on historical commit data to predict affected tests.
Understand Mill filesystem sandboxing
mainMill uses rudimentary filesystem sandboxing to prevent different tasks and modules from interfering with each other. It attempts to ensure tasks only read and write from their designated
.dest/folders, which are unique to each task to prevent collisions during parallel execution.Important Notes:
- Sandboxing is intended as a set of guardrails to encourage best practices, not as a fully hermetic environment.
- It is not designed to block intentional misbehavior; tasks can still traverse the filesystem.
- Mill's redirection of
os.pwddoes not apply tojava.ioorjava.nioAPIs, as these are outside of Mill's control.
Understand Mill's default JVM behavior
mainBy default, Mill uses the same JVM that it is running on to compile, test, and run Java, Scala, and Kotlin modules. Mill manages these JVMs automatically by downloading and caching them on demand, meaning you generally do not need to install a JVM globally to work with a Mill project.Understand the purpose of mill.Module
mainIn Mill,
mill.Moduleserves two primary roles:- As
objects: They act as namespaces to group relatedTasks together for organization. - As
traits: They act as reusable templates to replicate groups of relatedTasks and sub-Modules while allowing for customization.
Mill provides built-in modules like
mill.scalalib.ScalaModuleandmill.scalalib.CrossSbtModule, but you can define your own to extend Mill's functionality.- As