Pre-Compiled Modules
Pre-compiled modules allow you to define a package.mill.yaml or build.mill.yaml file
whose extends clause refers to a pre-compiled class (a subclass of mill.api.PrecompiledModule).
Pre-compiled modules are useful when you have a lot of similar modules, as is common in large
codebases. Rather than code-generating and compiling a separate build script for each module,
these identical modules can be compiled once in your mill-build/src or even compiled and
published earlier to Maven Central, and from there used throughout your project.
In this example, foo/package.mill.yaml and bar/package.mill.yaml both extend
millbuild.LineCountJavaModule, a custom PrecompiledModule subclass defined in
mill-build/src/. This module counts the lines of source code and generates a resource
file with the line count. It also has a nested test module containing the test suite
configuration.
package millbuild
import mill.*, javalib.*
class LineCountJavaModule(val scriptConfig: mill.api.PrecompiledModule.Config)
extends mill.javalib.JavaModule with mill.api.PrecompiledModule {
override lazy val millDiscover = mill.api.Discover[this.type]
object test extends JavaTests with mill.javalib.TestModule.Junit5
/** Total number of lines in module source files */
def lineCount: T[Int] = Task {
allSourceFiles().map(f => os.read.lines(f.path).size).sum
}
/** Generate resources using lineCount of sources */
override def resources: T[Seq[PathRef]] = Task {
os.write(Task.dest / "line-count.txt", "" + lineCount())
super.resources() ++ Seq(PathRef(Task.dest))
}
}
LineCountJavaModule is then inherited by foo and bar:
extends: millbuild.LineCountJavaModule
mill-experimental-precompiled-module: true
object test:
mvnDeps:
- org.hamcrest:hamcrest:2.2
extends: millbuild.LineCountJavaModule
mill-experimental-precompiled-module: true
moduleDeps: !append [foo]
object test:
mvnDeps:
- org.hamcrest:hamcrest:2.2
moduleDeps: !append [foo.test]
bar declares moduleDeps: !append [foo], so bar depends on foo and can use foo’s
classes. Similarly, `bar.test declares moduleDeps: !append [foo.test], so bar's
test module depends on `foo’s test module and can reuse its test utilities.
> ./mill resolve _
...
bar
...
foo
...
> ./mill resolve foo._
foo.test
...
foo.compile
...
foo.run
...
foo.lineCount
...
> ./mill resolve bar.test._
bar.test.compile
...
bar.test.testForked
...
> ./mill foo.run
Hello, Foo!
> ./mill bar.run
Hello, Bar!, Hello, Qux!
> ./mill show foo.lineCount
11
> ./mill show bar.lineCount
18
> ./mill foo.test
...testGreet...
> ./mill bar.test
...testGreetAll...
...testFooGreetFromBarTest...
Instead of generating Scala code from the YAML file, Mill instantiates the pre-compiled
module class reflectively at runtime, similar to script modules.
Pre-compiled modules are visible to resolve and can be referenced from other
pre-compiled modules and scripts, but not from .mill files since they are instantiated
at resolution time after .mill files are already compiled. They are enabled via the
mill-experimental-precompiled-module: true key.