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.
E.g. you may want to keep a webpack process running so webpack’s own internal caches are hot and compilation is fast:
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-used 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")
}
}
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 next section Using Mill Plugins (import $ivy
)].
For more information, see Ammonite’s Ivy Dependencies documentation.
Using Mill Plugins (import $ivy
)
Mill plugins are ordinary jars and are loaded as any other external dependency with import $ivy
.
Mill plugins are typically bound to a specific version or version range of Mill. To ease the use of the correct versions and avoid runtime issues (caused by binary incompatible plugins, which are hard to debug) you can apply one of the following techniques:
Use the specific Mill Binary Platform notation
// for classic Scala dependencies
import $ivy.`<group>::<plugin>::<version>` (1)
// for dependencies specific to the exact Scala version
import $ivy.`<group>:::<plugin>::<version>` (2)
1 | This is equivalent to
|
2 | This is equivalent to
|
Use special placeholders in your import $ivy
$MILL_VERSION
-
to substitute the currently used Mill version. This is typical required for Mill contrib modules, which are developed in the Mill repository and highly bound to the current Mill version.
Example: Usemill-contrib-bloop
plugin matching the current Mill versionimport $ivy.`com.lihaoyi:mill-contrib-bloop:$MILL_VERSION`
There is the even more convenient option to leave the version completely empty. Mill will substitute it with its current version. But don’t forget to provide the trailing colon!
Example: Usemill-contrib-bloop
plugin matching the current Mill versionimport $ivy.`com.lihaoyi:mill-contrib-bloop:`
$MILL_BIN_PLATFORM
-
to substitute the currently used Mill binary platform.
Example: Usingmill-vcs-version
plugin matching the current Mill Binary Platfromimport $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version_mill$MILL_BIN_PLATFORM:0.1.2`
If you want to publish re-usable libraries that other people can use in their builds, simply publish your code as a library to maven central. |
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
)
}