summaryrefslogtreecommitdiff
path: root/docs/pages/7 - Extending Mill.md
blob: 70ac24dad2b4c2197e849b2275be7f9192dfd162 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
There are many 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:

```scala
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
[Ammonite-Ops](http://ammonite.io/#Ammonite-Ops) library that comes bundled with
Mill, or even plain `java.nio`/`java.lang.Process`. Each target gets its own
[T.ctx().dest](http://www.lihaoyi.com/mill/page/tasks#millutilctxdestctx) 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:

```scala
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() }
}
```

### Deploy your compiled assembly to AWS

```scala
object foo extends ScalaModule {

}

def deploy(assembly: PathRef, credentials: String) = ???

def deployFoo(credentials: String) = T.command { deployFoo(foo.assembly()) }
```


## Custom Workers

[Custom Targets & Commands](#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:

```scala
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

```scala
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:

```scala
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:

```scala
trait FooModule extends ScalaModule {
  def scalaVersion = "2.11.11"
  object test extends Tests {
    def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.4")
    def testFrameworks = Seq("org.scalatest.tools.Framework")
  }
}
```

## 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:

```scala
// foo.sc
def fooValue() = 31337 
```
```scala
// build.sc
import $file.foo
def printFoo() = T.command { println(foo.fooValue()) }
```

Mill's `import $file` syntax supports the full functionality of
[Ammonite Scripts](http://ammonite.io/#ScalaScripts)

## 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`:

```scala
// build.sc
import $ivy.`com.lihaoyi::scalatags:0.6.2`

def generatedHtml = T {
  import scalatags.Text.all._
  html(
    head(),
    body(
      h1("Hello"),
      p("World")
    )
  ).render  
}
```

This creates the `generatedHtml` target which can then be used however you would
like: written to a file, further processed, etc.

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.

For more information, see Ammonite's
[Ivy Dependencies documentation](http://ammonite.io/#import$ivy).

## Evaluator Commands (experimental)

_Evaluator Command are experimental and suspected to change. See [issue #502](https://github.com/lihaoyi/mill/issues/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.

```scala
def idea(ev: Evaluator) = T.command {
  mill.scalalib.GenIdea(
    implicitly,
    ev.rootModule,
    ev.discover
  )
}
```

Many built-in tools are implemented as custom evaluator commands:
[all](http://www.lihaoyi.com/mill/#all), [inspect](http://www.lihaoyi.com/mill/#inspect),
[resolve](http://www.lihaoyi.com/mill/#resolve), [show](http://www.lihaoyi.com/mill/#show).
If you want a way to run Mill commands and programmatically manipulate the tasks and outputs, you do so with your own evaluator command.