Extending Mill
There are different ways of extending Mill, depending on how much customization and flexibility you need. This page will go through your options from the easiest/least-flexible to the hardest/most-flexible.
Custom Targets & Commands
The simplest way of adding custom functionality to Mill is to define a custom Target or Command:
def foo = T { ... }
def bar(x: Int, s: String) = T.command { ... }
These can depend on other Targets, contain arbitrary code, and be placed top-level or within any module.
If you have something you just want to do that isn’t covered by the built-in ScalaModule
s/ScalaJSModule
s, simply write a custom Target (for cached computations) or Command (for un-cached actions) and you’re done.
For subprocess/filesystem operations, you can use the
os-lib library that comes bundled with Mill, or even plain java.nio
/java.lang.Process
.
Each target gets its own
T.dest folder that you can use to place files without worrying about colliding with other targets.
This covers use cases like:
Compile some Javascript with Webpack and put it in your runtime classpath:
def doWebpackStuff(sources: Seq[PathRef]): PathRef = ???
def javascriptSources = T.sources { millSourcePath / "js" }
def compiledJavascript = T { doWebpackStuff(javascriptSources()) }
object foo extends ScalaModule {
def runClasspath = T { super.runClasspath() ++ compiledJavascript() }
}
Custom Workers
Custom Targets & Commands are re-computed from scratch each time; sometimes you want to keep values around in-memory when using
--watch
or the Build REPL.
In such a case, you can use a Worker
. Workers keep their state between multiple runs. Workers are created with the T.worker
macro.
def webpackWorker = T.worker {
// Spawn a process using java.lang.Process and return it
}
def javascriptSources = T.sources { millSourcePath / "js" }
def doWebpackStuff(webpackProcess: Process, sources: Seq[PathRef]): PathRef =
???
def compiledJavascript = T {
doWebpackStuff(webpackWorker(), javascriptSources())
}
Mill itself uses T.worker
s for its built-in Scala support: we keep the Scala compiler in memory between compilations, rather than discarding it each time, in order to improve performance.
Custom Modules
trait FooModule extends mill.Module {
def bar = T { "hello" }
def baz = T { "world" }
}
Custom modules are useful if you have a common set of tasks that you want to re-use across different parts of your build.
You simply define a trait
inheriting from mill.Module
, and then use that trait
as many times as you want in various object
s:
object foo1 extends FooModule
object foo2 extends FooModule {
def qux = T { "I am Cow" }
}
You can also define a trait
extending the built-in ScalaModule
if you have common configuration you want to apply to all your ScalaModule
s:
trait FooModule extends ScalaModule {
def scalaVersion = "2.11.11"
object test extends Tests with TestModule.ScalaTest {
def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.4")
}
}
In fact, the above example of a configuration trait is so convenient, that it is found in almost any non-trivial Mill project, in once form or another.
import $file
If you want to define some functionality that can be used both inside and outside the build, you can create a new foo.sc
file next to your build.sc
,
import $file.foo
, and use it in your build.sc
file:
foo.sc
def fooValue() = 31337
build.sc
import $file.foo
def printFoo() = T.command { println(foo.fooValue()) }
Mill’s import $file
syntax supports the full functionality of
Ammonite Scripts
import $ivy
If you want to pull in artifacts from the public repositories (e.g. Maven Central) for use in your build, you can simply use import $ivy
.
build.sc
: Using scalatags
library to generate HTMLimport $ivy.`com.lihaoyi::scalatags:0.6.2` (1)
def generatedHtml = T {
import scalatags.Text.all._ (2)
html(
head(),
body(
h1("Hello"),
p("World")
)
).render
}
1 | Import the scalatags library from Mavel Central repository. |
2 | Creates the generatedHtml target which is used here to generate a simple Hello World HTML document. It can be used however you would like: written to a file, further processed, etc. |
Please also read the section Using Plugins.
For more information about this special import
syntax, read the
Ammonite Documentation for Ivy Dependencies.
Evaluator Commands (experimental)
Evaluator Command are experimental and suspected to change. See issue #502 for details.
You can define a command that takes in the current Evaluator
as an argument, which you can use to inspect the entire build, or run arbitrary tasks.
For example, here is the mill.scalalib.GenIdea/idea
command which uses this to traverse the module-tree and generate an Intellij project config for your build.
def idea(ev: Evaluator) = T.command {
mill.scalalib.GenIdea(
implicitly,
ev.rootModule,
ev.discover
)
}