Java Build Examples
On this page, we will explore the Mill build tool via a series of simple Java example projects.
Nesting Modules
package build
import mill._, javalib._
trait MyModule extends JavaModule {
def ivyDeps = Agg(
ivy"net.sourceforge.argparse4j:argparse4j:0.9.0",
ivy"org.apache.commons:commons-text:1.12.0"
)
}
object foo extends MyModule {
def moduleDeps = Seq(bar, qux)
object bar extends MyModule
object qux extends MyModule {
def moduleDeps = Seq(bar)
}
}
object baz extends MyModule {
def moduleDeps = Seq(foo.bar, foo.qux, foo)
}
Modules can be nested arbitrarily deeply within each other. The outer module
can be the same kind of module as the ones within, or it can be a plain
Module
if we just need a wrapper to put the modules in without any tasks
defined on the wrapper.
The outer module can also depend on the inner module (as shown above), and vice versa, but not both at the same.
Running tasks on the nested modules requires the full module path
foo.bar.run
> mill resolve __.run
foo.bar.run
foo.qux.run
baz.run
> mill foo.run --bar-text hello --qux-text world --foo-text today
Bar.value: <h1>hello</h1>
Qux.value: <p>world</p>
Foo.value: <p>today</p>
> mill baz.run --bar-text hello --qux-text world --foo-text today --baz-text yay
Bar.value: <h1>hello</h1>
Qux.value: <p>world</p>
Foo.value: <p>today</p>
Baz.value: <p>yay</p>
> mill foo.qux.run --bar-text hello --qux-text world
Bar.value: <h1>hello</h1>
Qux.value: <p>world</p>
Maven-Compatible Modules
package build
import mill._, javalib._
object foo extends MavenModule {
object test extends MavenTests with TestModule.Junit4
}
MavenModule
is a variant of JavaModule
that uses the more verbose folder layout of Maven, SBT, and other tools:
-
foo/src/main/java
-
foo/src/test/java
Rather than Mill’s
-
foo/src
-
foo/test/src
This is especially useful if you are migrating from Maven to Mill (or vice versa), during which a particular module may be built using both Maven and Mill at the same time
> mill foo.compile
compiling 1 Java source...
> mill foo.test.compile
compiling 1 Java source...
> mill foo.test.test
...foo.FooTests.hello ...
> mill foo.test
...foo.FooTests.hello ...
Realistic Java Example Project
package build
import mill._, javalib._, publish._
trait MyModule extends JavaModule with PublishModule {
def publishVersion = "0.0.1"
def pomSettings = PomSettings(
description = "Hello",
organization = "com.lihaoyi",
url = "https://github.com/lihaoyi/example",
licenses = Seq(License.MIT),
versionControl = VersionControl.github("lihaoyi", "example"),
developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
)
def ivyDeps = Agg(ivy"org.apache.commons:commons-text:1.12.0")
object test extends JavaTests with TestModule.Junit4
}
object foo extends MyModule {
def moduleDeps = Seq(bar, qux)
def generatedSources = Task {
os.write(
Task.dest / "Version.java",
s"""
package foo;
public class Version {
public static String value() {
return "${publishVersion()}";
}
}
""".stripMargin
)
Seq(PathRef(Task.dest))
}
}
object bar extends MyModule {
def moduleDeps = Seq(qux)
}
object qux extends MyModule
A semi-realistic build setup, combining all the individual Mill concepts:
-
Three
JavaModule
s that depend on each other -
With unit testing and publishing set up
-
With generated sources to include the
publishVersion
as a string in the code, so it can be printed at runtime
Note that for multi-module builds like this, using queries to run tasks on multiple modules at once can be very convenient:
__.test __.publishLocal
Also note how you can use trait
s to bundle together common combinations of
modules: MyModule
not only defines a JavaModule
with some common
configuration, but it also defines a object test
module within it with its
own configuration. This is a very useful technique for managing the often
repetitive module structure in a typical project
> mill resolve __.run
bar.run
bar.test.run
foo.run
foo.test.run
qux.run
> mill foo.run
foo version 0.0.1
Foo.value: <h1>hello</h1>
Bar.value: <p>world</p>
Qux.value: 31337
> mill bar.test
...bar.BarTests.test ...
> mill qux.run
Qux.value: 31337
> mill __.compile
> mill __.test
...bar.BarTests.test ...
...foo.FooTests.test ...
> mill __.publishLocal
Publishing Artifact(com.lihaoyi,foo,0.0.1) to ivy repo...
Publishing Artifact(com.lihaoyi,bar,0.0.1) to ivy repo...
Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo...
...
> mill show foo.assembly # mac/linux
".../out/foo/assembly.dest/out.jar"
> ./out/foo/assembly.dest/out.jar # mac/linux
foo version 0.0.1
Foo.value: <h1>hello</h1>
Bar.value: <p>world</p>
Qux.value: 31337
Example Builds for Real Projects
Mill comes bundled with example builds for real-world open-source projects, demonstrating how Mill can be used to build code outside of tiny example codebases:
JimFS
package build
import mill._, javalib._, publish._
def sharedCompileIvyDeps = Task {
Agg(
ivy"com.google.auto.service:auto-service:1.0.1",
ivy"com.google.code.findbugs:jsr305:3.0.2",
ivy"org.checkerframework:checker-compat-qual:2.5.5",
ivy"com.ibm.icu:icu4j:73.1",
)
}
object jimfs extends PublishModule with MavenModule {
def publishVersion = "1.3.3.7"
def pomSettings = PomSettings(
description = artifactName(),
organization = "com.google",
url = "https://github.com/google/jimfs",
licenses = Seq(License.MIT),
versionControl = VersionControl.github(owner = "google", repo = "jimfs"),
developers = Nil
)
def ivyDeps = sharedCompileIvyDeps() ++ Agg(
ivy"com.google.guava:guava:31.1-android",
)
def javacOptions = Seq("-processor", "com.google.auto.service.processor.AutoServiceProcessor")
object test extends MavenTests {
def ivyDeps = sharedCompileIvyDeps() ++ Agg(
ivy"junit:junit:4.13.2",
ivy"com.google.guava:guava-testlib:31.1-android",
ivy"com.google.truth:truth:1.1.3",
ivy"com.github.sbt:junit-interface:0.13.2",
ivy"com.ibm.icu:icu4j:73.1",
)
def testFramework = "com.novocode.junit.JUnitFramework"
}
}
JimFS is a small Java library implementing an in-memory filesystem. It is commonly used in test suites to validate filesystem operations without needing to write to disk.
It has a relatively simple codebase structure, a single module and test suite.
It has a number of compile-time-only dependencies shared between the library and
test suite. One wrinkle is that it uses annotation processors as part of its build,
which Mill supports by providing the relevant ivyDeps
of the annotation processor
and providing javacOptions
to invoke it.
Project home: https://github.com/google/jimfs
> ./mill jimfs.test
Test run com.google.common.jimfs.FileTest started
Test run com.google.common.jimfs.FileTest finished: 0 failed, 0 ignored, 7 total...
...
Apache Commons IO
package build
import mill._, javalib._, publish._
import $ivy.`com.lihaoyi::mill-contrib-jmh:$MILL_VERSION`
import contrib.jmh.JmhModule
object `package` extends RootModule with PublishModule with MavenModule {
def publishVersion = "2.17.0-SNAPSHOT"
def pomSettings = PomSettings(
description = artifactName(),
organization = "org.apache.commons",
url = "https://github.com/apache/commons-io",
licenses = Seq(License.`Apache-2.0`),
versionControl = VersionControl.github(owner = "apache", repo = "commons-io"),
developers = Nil
)
object test extends MavenTests with TestModule.Junit5 with JmhModule{
def testSandboxWorkingDir = false
def jmhCoreVersion = "1.37"
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"org.junit.jupiter:junit-jupiter:5.10.3",
ivy"org.junit-pioneer:junit-pioneer:1.9.1",
ivy"net.bytebuddy:byte-buddy:1.14.18",
ivy"net.bytebuddy:byte-buddy-agent:1.14.18",
ivy"org.mockito:mockito-inline:4.11.0",
ivy"com.google.jimfs:jimfs:1.3.0",
ivy"org.apache.commons:commons-lang3:3.14.0",
ivy"commons-codec:commons-codec:1.17.1",
ivy"org.openjdk.jmh:jmh-core:1.37",
ivy"org.openjdk.jmh:jmh-generator-annprocess:1.37",
)
}
}
The Apache Commons IO library contains utility classes, stream implementations, file filters, file comparators, endian transformation classes, and much more.
The core library commonsio
is dependency-free, but the test suite commonsio.test
as a number of libraries included. It also ships with JMH benchmarks, which Mill
supports via the built in JMH plugin
Project home: https://github.com/apache/commons-io
> ./mill compile
compiling 254 Java sources...
...
> ./mill test.compile
compiling 261 Java sources...
...
> ./mill test.testOnly org.apache.commons.io.FileUtilsTest
Test org.apache.commons.io.FileUtilsTest#testCopyFile1() started
Test org.apache.commons.io.FileUtilsTest#testCopyFile1() finished, took ...
...
> ./mill test.testOnly org.apache.commons.io.FileSystemTest
Test org.apache.commons.io.FileSystemTest#testIsLegalName() started
Test org.apache.commons.io.FileSystemTest#testIsLegalName() finished, took ...
...
> ./mill test.runJmh '.*PathUtilsContentEqualsBenchmark' -bm SingleShotTime
Benchmark Mode Cnt ...
PathUtilsContentEqualsBenchmark.testCurrent_fileContentEquals ss 5 ...
PathUtilsContentEqualsBenchmark.testCurrent_fileContentEquals_Blackhole ss 5 ...
PathUtilsContentEqualsBenchmark.testProposal_contentEquals ss 5 ...
PathUtilsContentEqualsBenchmark.testProposal_contentEquals_Blackhole ss 5 ...
Real World Mill Builds
C3P0
C3P0 is a JDBC connection pooling library written in Java, built using the Mill build tool.