Simple Scala Modules
This page documents the usage of simple config-based Scala modules defined by
build.mill.yaml
and package.mill.yaml
files. These are less flexible but easier to get started with than the
full build.mill
or package.mill
build files, which makes them ideal for small
projects which do not need additional flexibility.
Common Configuration Overrides for Simple Modules
This example shows some of the common tasks you may want to override on a
ScalaModule
: specifying the mainClass
, adding additional
sources/resources, and setting compilation/run
options.
extends: [mill.scalalib.ScalaModule]
scalaVersion: 2.13.16
mvnDeps:
- com.lihaoyi::scalatags:0.13.1
- com.lihaoyi::os-lib:0.11.4
mainClass: "foo.Foo2"
sources: ["./src", "./custom-src"]
resources: ["./resources", "./custom-resources"]
forkArgs: ["-Dmy.custom.property=my-prop-value"]
forkEnv: { "MY_CUSTOM_ENV": "my-env-value" }
scalacOptions: ["-deprecation", "-Xfatal-warnings"]
> ./mill run
Foo2.value: <h1>hello2</h1>
Foo.value: <h1>hello</h1>
MyResource: My Resource Contents
MyOtherResource: My Other Resource Contents
my.custom.property: my-prop-value
MY_CUSTOM_ENV: my-env-value
> ./mill show assembly
".../out/assembly.dest/out.jar"
> ./out/assembly.dest/out.jar # mac/linux
Foo2.value: <h1>hello2</h1>
Foo.value: <h1>hello</h1>
MyResource: My Resource Contents
MyOtherResource: My Other Resource Contents
my.custom.property: my-prop-value
> sed -i.bak 's/Foo2 {/Foo2 { println(this + "hello")/g' custom-src/Foo2.scala
> ./mill compile # demonstrate -deprecation/-Xfatal-warnings flags
error: object Foo2 { println(this + "hello")
error: ^
error: ...Implicit injection of + is deprecated. Convert to String to call +...
Note that the .build.yaml
config files only let you set simple configuration keys,
and do not let you extend your build with code e.g. to generate sources. For more flexibility,
see Programmatic Module Configuration.
Packaging and Publishing Simple Modules
Mill Single-File Projects have the full flexibility of the Mill build tool, just configured
using the YAML build header syntax rather than a separate
build.mill
file. That means you can configure the JVM version,
compile flags or runtime flags, create
Executable Assemblies
or Graal Native Images,
Publishing to Maven Central
etc.
The example below shows a few of those options configured in the YAML build header of a single-file project, and then made of use from the command line:
extends: [mill.scalalib.ScalaModule, mill.scalalib.PublishModule, mill.scalalib.NativeImageModule]
scalaVersion: 3.7.3
jvmId: "graalvm-community:24"
nativeImageOptions: ["--no-fallback"]
publishVersion: "0.0.1"
artifactName: "example"
pomSettings:
description: "Example"
organization: "com.lihaoyi"
url: "https://github.com/com.lihaoyi/example"
licenses: ["MIT"]
versionControl: "https://github.com/com.lihaoyi/example"
developers: [{"name": "Li Haoyi", "email": "example@example.com"}]
> ./mill nativeImage
> out/nativeImage.dest/native-executable
Hello Graal Native: 24...
> ./mill publishLocal
Publishing Artifact(com.lihaoyi,example...,0.0.1) to ivy repo ...
Apart from publishing locally, you can also publish this single-file project to Sonatype Maven Central via:
> ./mill mill.javalib.SonatypeCentralPublishModule/
Most configuration def
s in Mill can be used to configure single-file projects, and
most tasks and commands can also be used as well. This gives you a lot of flexibility
in working with your single-file project until it becomes complex enough to need
a dedicated build.mill
file.
Custom Script Module Classes
By default, single-file Mill script modules inherit their behavior from the standard
mill.javalib.JavaModule
, mill.scalalib.ScalaModule
, or mill.scalalib.KotlinModule
.
However, you can also customize it to inherit from a custom Module
class that you define
as part of your meta-build in mill-build/src/
. For example, if we want to add a resource
file generated by processing the source file of the script, this can be done in a custom
LineCountScalaModule
as shown below:
extends: [millbuild.LineCountScalaModule]
scalaVersion: 3.7.3
package millbuild
import mill.*, scalalib.*
trait LineCountScalaModule extends mill.scalalib.ScalaModule {
/** Total number of lines in module source files */
def lineCount = Task {
allSourceFiles().map(f => os.read.lines(f.path).size).sum
}
/** Generate resources using lineCount of sources */
override def resources = Task {
os.write(Task.dest / "line-count.txt", "" + lineCount())
super.resources() ++ Seq(PathRef(Task.dest))
}
}
> ./mill run
...
Line Count: 17
> ./mill show lineCount
17
Your custom LineCountScalaModule
must be a class
take two parameters
val millScriptFile: os.Path
, and override val moduleDeps: Seq[JavaModule]
that will be
populated by Mill, and inherit from mill.simple.Scala.Base
or some other subclass of
mill.simple.SimpleModule
. This can then be used via //| extends: LineCountScalaModule
in the header of your script file.
Custom script module classes allows you to customize the semantics of your Java, Scala, or Kotlin
single-file script modules. If you have a large number of scripts with a similar configuration,
or you need customizations that cannot be done in the YAML build header, placing these
customizations in a custom script module class can let you centrally define the behavior
and standardize it across all scripts that inherit it via extends.
Depending on Programmatic Modules from Simple Modules
package build
import mill.*, scalalib.*
object bar extends ScalaModule {
def scalaVersion = "3.7.1"
def mvnDeps = Seq(mvn"com.lihaoyi::scalatags:0.13.1")
}
extends: [mill.scalalib.ScalaModule]
moduleDeps: [build.bar]
scalaVersion: 3.7.3
mvnDeps: [com.lihaoyi::mainargs:0.7.6]
package bar
import scalatags.Text.all.*
object Bar {
def generateHtml(text: String) = {
val value = h1(text)
value.toString
}
}
> ./mill foo.run --text hello
<h1>hello</h1>
Single-File Scripts
Mill also allows you to run single-file Scala programs (often referred to as scripts) easily from the command-line, even those that contain third-party dependencies or other such configuration. These can be useful as a replacement for Bash scripts, letting you write small scripts or programs in Scala with full access to third-party libraries and other build-tool features.
For example, given the Scala program below, it can be run directly using Mill:
//| mvnDeps:
//| - "com.lihaoyi::scalatags:0.13.1"
//| - "com.lihaoyi::mainargs:0.7.6"
import scalatags.Text.all.*
import mainargs.{main, ParserForMethods}
object Foo {
def generateHtml(text: String) = {
h1(text).toString
}
@main
def main(text: String) = {
println(generateHtml(text))
}
def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
}
> ./mill Foo.scala --text hello
compiling 1 Scala source to...
<h1>hello</h1>
The ./mill Foo.{language-ext}
syntax is shorthand for ./mill Foo.{language-ext}:run
.
You can also call other tasks on your script modules, such as Foo.{langauge-ext}:assembly
below:
> ./mill Foo.scala:run --text hello
<h1>hello</h1>
> ./mill show Foo.scala:assembly # show the output of the assembly task
".../out/Foo.scala/assembly.dest/out.jar"
> java -jar ./out/Foo.scala/assembly.dest/out.jar --text hello
<h1>hello</h1>
> ./out/Foo.scala/assembly.dest/out.jar --text hello # mac/linux
<h1>hello</h1>
Script files can have test suites, usually written in a separate test script. The test script
specifies what script it tests via moduleDeps
, and can have its own mvnDeps
in addition
to those of the upstream script. The test script can then exercise functions from the upstream
script as shown below:
//| moduleDeps: [Foo.scala]
//| mvnDeps:
//| - "com.google.guava:guava:33.3.0-jre"
import com.google.common.html.HtmlEscapers.htmlEscaper
object FooTests {
def main(args: Array[String]): Unit = {
val result = Foo.generateHtml("hello")
assert(result == "<h1>hello</h1>")
println(result)
val result2 = Foo.generateHtml("<hello>")
val expected2 = "<h1>" + htmlEscaper().escape("<hello>") + "</h1>"
assert(result2 == expected2)
println(result2)
}
}
> ./mill FooTests.scala
<h1>hello</h1>
<h1><hello></h1>
Again, you can pass the name of the task explicitly via :
, e.g. :run
below
> ./mill FooTests.scala:run # specifying the test task explicitly
<h1>hello</h1>
<h1><hello></h1>
For more details on Mill script files, see
Configuring Single-File Projects
The rest of the examples below discuss the Mill config for building larger projects
beyond the single-file project format discussed above, using a dedicated build.mill
file to provide additional flexibility in setting up your project.