You can configure your Mill build in a number of ways:
## Compilation & Execution Flags
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
def scalacOptions = Seq("-Ydelambdafy:inline")
def forkArgs = Seq("-Xmx4g")
def forkEnv = Map("HELLO_MY_ENV_VAR" -> "WORLD")
}
```
You can pass flags to the Scala compiler via `scalacOptions`. By default,
`foo.run` runs the compiled code in a subprocess, and you can pass in JVM flags
via `forkArgs` or environment-variables via `forkEnv`.
You can also run your code via
```bash
mill foo.runLocal
```
Which runs it in-process within an isolated classloader. This may be faster
since you avoid the JVM startup, but does not support `forkArgs` or `forkEnv`.
## Adding Ivy Dependencies
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
def ivyDeps = Agg(
ivy"com.lihaoyi::upickle:0.5.1",
ivy"com.lihaoyi::pprint:0.5.2",
ivy"com.lihaoyi::fansi:0.2.4"
)
}
```
You can define the `ivyDeps` field to add ivy dependencies to your module. The
`ivy"com.lihaoyi::upickle:0.5.1"` syntax (with `::`) represents Scala
dependencies; for Java dependencies you would use a single `:` e.g.
`ivy"com.lihaoyi:upickle:0.5.1"`.
By default these are resolved from maven central, but you can add your own
resolvers by overriding the `repositories` definition in the module:
```scala
def repositories = super.repositories ++ Seq(
MavenRepository("https://oss.sonatype.org/content/repositories/releases")
)
```
## Adding a Test Suite
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
object test extends Tests{
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0")
def testFramework = "mill.UTestFramework"
}
}
```
You can define a test suite by creating a nested module extending `Tests`, and
specifying the ivy coordinates and name of your test framework. This expects the
tests to be laid out as follows:
```
build.sc
foo/
src/
Main.scala
resources/
...
test/
src/
MainTest.scala
resources/
...
out/
foo/
...
test/
...
```
The above example can be run via
```bash
mill foo.test
```
By default, tests are run in a subprocess, and `forkArg` and `forkEnv` can be
overriden to pass JVM flags & environment variables. You can also use
```bash
mill foo.test.testLocal
```
To run tests in-process in an isolated classloader.
You can define multiple test suites if you want, e.g.:
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
object test extends Tests{
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0")
def testFramework = "mill.UTestFramework"
}
object integration extends Tests{
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0")
def testFramework = "mill.UTestFramework"
}
}
```
Each of which will expect their sources to be in their respective `foo/test` and
`foo/integration` folder.
`Tests` modules are `ScalaModule`s like any other, and all the same
configuration options apply.
## Scala Compiler Plugins
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
def compileIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7")
def scalacOptions = Seq("-P:acyclic:force")
def scalacPluginIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7")
}
```
You can use Scala compiler plugins by setting `scalacPluginIvyDeps`. The above
example also adds the plugin to `compileIvyDeps`, since that plugin's artifact
is needed on the compilation classpath (though not at runtime).
## Common Configuration
```scala
import mill._
import mill.scalalib._
trait CommonModule extends ScalaModule{
def scalaVersion = "2.12.4"
}
object foo extends CommonModule
object bar extends CommonModule {
def moduleDeps = Seq(foo)
}
```
You can extract out configuration common to multiple modules into a `trait` that
those modules extend. This is useful for providing convenience & ensuring
consistent configuration: every module often has the same scala-version, uses
the same testing framework, etc. and all that can be extracted out into the
`trait`.
## Custom Tasks
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
}
def lineCount = T{
import ammonite.ops._
foo.sources().flatMap(ref => ls.rec(ref.path)).flatMap(read.lines).size
}
def printLineCount() = T.command{
println(lineCount())
}
```
You can define new cached Targets using the `T{...}` syntax, depending on
existing Targets e.g. `foo.sources` via the `foo.sources()` syntax to extract
their current value, as shown in `lineCount` above. The return-type of a Target
has to be JSON-serializable (using
[uPickle](https://github.com/lihaoyi/upickle)) and the Target is cached when
first run until it's inputs change (in this case, if someone edits the
`foo.sources` files which live in `foo/src`. Cached Targets cannot take
parameters.
You can print the value of your custom target using `show`, e.g.
```bash
mill run show lineCount
```
You can define new un-cached Commands using the `T.command{...}` syntax. These
are un-cached and re-evaluate every time you run them, but can take parameters.
Their return type needs to be JSON-writable as well, or `(): Unit` if you want
to return nothing.
## Custom Modules
```scala
import mill._
import mill.scalalib._
object qux extends Module{
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
}
object bar extends ScalaModule {
def moduleDeps = Seq(foo)
def scalaVersion = "2.12.4"
}
}
```
Not every Module needs to be a `ScalaModule`; sometimes you just want to group
things together for neatness. In the above example, you can run `foo` and `bar`
namespaced inside `qux`:
```bash
mill qux.foo.compile
mill qux.bar.run
```
You can also define your own module traits, with their own set of custom tasks,
to represent other things e.g. Javascript bundles, docker image building,:
```scala
trait MySpecialModule extends Module{
...
}
object foo extends MySpecialModule
object bar extends MySpecialModule
```
## Overriding Tasks
```scala
import mill._
import mill.scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.12.4"
def compile = T{
println("Compiling...")
super.compile()
}
def run(args: String*) = T.command{
println("Running... + args.mkString(" "))
super.run(args:_*)
}
}
```
You can re-define targets and commands to override them, and use `super` if you
want to refer to the originally defined task. The above example shows how to
override `compile` and `run` to add additional logging messages.
In Mill builds the `override` keyword is optional.