From b41a09426c03167a883cfb8a3ac3165d99aac056 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 10 Apr 2018 12:11:33 -0700 Subject: update changelog for 0.2.0 --- docs/pages/7 - Extending Mill.md | 183 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 docs/pages/7 - Extending Mill.md (limited to 'docs/pages/7 - Extending Mill.md') diff --git a/docs/pages/7 - Extending Mill.md b/docs/pages/7 - Extending Mill.md new file mode 100644 index 00000000..75b7643a --- /dev/null +++ b/docs/pages/7 - Extending Mill.md @@ -0,0 +1,183 @@ +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 it's 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 it's 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 simple 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 + +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[Any]) = T.command{ + mill.scalalib.GenIdea( + implicitly, + ev.rootModule, + ev.discover + ) +} +``` + +Many built-in tools are implemented as custom evaluator commands: +[all](intro.html#all), [inspect](intro.html#inspect), +[resolve](intro.html#resolve), [show](intro.html#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. -- cgit v1.2.3