diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 1dd014f43c4..66a0c2f2a29 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -1,6 +1,6 @@ = Contributing to Mill :link-github: https://github.com/com-lihaoyi/mill -:link-chat: https://discord.gg/xJCYRVMS + Thank you for considering contributing to Mill. @@ -16,6 +16,6 @@ Here are some direct links: * {link-github}/discussions[Discussion Forum on GitHub] - A place to ask question and discuss all kind of questions around Mill * {link-github}/issues[Issue Tracker on GitHub] - Our issue tracker for bugs and features * {link-github}/pulls[Pull Requests on GitHub] - All new features and bug fixes find their way into Mill via a pull request. You can also sketch new ideas by creating a draft pull requests. -{link-chat}[Discord Chat] - You can also join our chat room if you like more direct communication or to just say hello + To build docs locally, `mill docs.localPages`. The last line of the command tells you where to browse the generated pages. From there you can follow the breadcrumbs. diff --git a/docs/modules/ROOT/images/index/gradle.svg b/docs/modules/ROOT/images/index/gradle.svg new file mode 100644 index 00000000000..8e7431e8230 --- /dev/null +++ b/docs/modules/ROOT/images/index/gradle.svg @@ -0,0 +1,19 @@ + + + diff --git a/docs/modules/ROOT/images/index/iconscout-java.svg b/docs/modules/ROOT/images/index/iconscout-java.svg new file mode 100644 index 00000000000..acf25c47343 --- /dev/null +++ b/docs/modules/ROOT/images/index/iconscout-java.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/modules/ROOT/images/index/iconscout-kotlin.svg b/docs/modules/ROOT/images/index/iconscout-kotlin.svg new file mode 100644 index 00000000000..48ff1667158 --- /dev/null +++ b/docs/modules/ROOT/images/index/iconscout-kotlin.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/images/index/iconscout-scala.svg b/docs/modules/ROOT/images/index/iconscout-scala.svg new file mode 100644 index 00000000000..279070c5eec --- /dev/null +++ b/docs/modules/ROOT/images/index/iconscout-scala.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/images/index/maven.png b/docs/modules/ROOT/images/index/maven.png new file mode 100644 index 00000000000..5b57ab516c9 Binary files /dev/null and b/docs/modules/ROOT/images/index/maven.png differ diff --git a/docs/modules/ROOT/images/index/sbt.png b/docs/modules/ROOT/images/index/sbt.png new file mode 100644 index 00000000000..d684bbca7d1 Binary files /dev/null and b/docs/modules/ROOT/images/index/sbt.png differ diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 3cbe43df906..648c969b58f 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -3,108 +3,99 @@ // but we intentionally skim over them and do not go into depth: the focus is // on end user goals and how to achieve them. -.xref:index.adoc[] - -.xref:javalib/intro.adoc[] -* xref:javalib/builtin-commands.adoc[] -* xref:javalib/module-config.adoc[] -* xref:javalib/dependencies.adoc[] -* xref:javalib/testing.adoc[] -* xref:javalib/linting.adoc[] -* xref:javalib/publishing.adoc[] -* xref:javalib/build-examples.adoc[] -* xref:javalib/web-examples.adoc[] -* xref:javalib/android-examples.adoc[] - -.xref:scalalib/intro.adoc[] -* xref:scalalib/builtin-commands.adoc[] -* xref:scalalib/module-config.adoc[] -* xref:scalalib/dependencies.adoc[] -* xref:scalalib/testing.adoc[] -* xref:scalalib/linting.adoc[] -* xref:scalalib/publishing.adoc[] -* xref:scalalib/build-examples.adoc[] -* xref:scalalib/web-examples.adoc[] - -.xref:kotlinlib/intro.adoc[] -* xref:kotlinlib/builtin-commands.adoc[] -* xref:kotlinlib/module-config.adoc[] -* xref:kotlinlib/dependencies.adoc[] -* xref:kotlinlib/testing.adoc[] -* xref:kotlinlib/linting.adoc[] -* xref:kotlinlib/publishing.adoc[] -// * xref:kotlinlib/build-examples.adoc[] -* xref:kotlinlib/web-examples.adoc[] -* xref:kotlinlib/android-examples.adoc[] - -.Build Tool Comparisons -* xref:comparisons/maven.adoc[] -* xref:comparisons/gradle.adoc[] -* xref:comparisons/sbt.adoc[] - -.The Mill CLI -* xref:cli/installation-ide.adoc[] -* xref:cli/flags.adoc[] -* xref:cli/alternate-installation.adoc[] - +* xref:why-mill.adoc[] +* xref:javalib/intro.adoc[] +** xref:javalib/builtin-commands.adoc[] +** xref:javalib/module-config.adoc[] +** xref:javalib/dependencies.adoc[] +** xref:javalib/testing.adoc[] +** xref:javalib/linting.adoc[] +** xref:javalib/publishing.adoc[] +** xref:javalib/build-examples.adoc[] +** xref:javalib/web-examples.adoc[] +** xref:javalib/android-examples.adoc[] +* xref:scalalib/intro.adoc[] +** xref:scalalib/builtin-commands.adoc[] +** xref:scalalib/module-config.adoc[] +** xref:scalalib/dependencies.adoc[] +** xref:scalalib/testing.adoc[] +** xref:scalalib/linting.adoc[] +** xref:scalalib/publishing.adoc[] +** xref:scalalib/build-examples.adoc[] +** xref:scalalib/web-examples.adoc[] +* xref:kotlinlib/intro.adoc[] +** xref:kotlinlib/builtin-commands.adoc[] +** xref:kotlinlib/module-config.adoc[] +** xref:kotlinlib/dependencies.adoc[] +** xref:kotlinlib/testing.adoc[] +** xref:kotlinlib/linting.adoc[] +** xref:kotlinlib/publishing.adoc[] +// ** xref:kotlinlib/build-examples.adoc[] +** xref:kotlinlib/web-examples.adoc[] +** xref:kotlinlib/android-examples.adoc[] +* Build Tool Comparisons +** xref:comparisons/maven.adoc[] +** xref:comparisons/gradle.adoc[] +** xref:comparisons/sbt.adoc[] +* The Mill CLI +** xref:cli/installation-ide.adoc[] +** xref:cli/flags.adoc[] +** xref:cli/alternate-installation.adoc[] // This section gives a tour of the various user-facing features of Mill: // library deps, out folder, queries, tasks, etc.. These are things that // every Mill user will likely encounter, and are touched upon in the various // language-specific sections, but here we go into a deeper language-agnostic // discussion of what these Mill features ar and how they work -.Mill Fundamentals -* xref:fundamentals/tasks.adoc[] -* xref:fundamentals/modules.adoc[] -* xref:fundamentals/out-dir.adoc[] -* xref:fundamentals/query-syntax.adoc[] -* xref:fundamentals/library-deps.adoc[] -* xref:fundamentals/cross-builds.adoc[] -* xref:fundamentals/bundled-libraries.adoc[] - +* Mill Fundamentals +** xref:fundamentals/tasks.adoc[] +** xref:fundamentals/modules.adoc[] +** xref:fundamentals/out-dir.adoc[] +** xref:fundamentals/query-syntax.adoc[] +** xref:fundamentals/library-deps.adoc[] +** xref:fundamentals/cross-builds.adoc[] +** xref:fundamentals/bundled-libraries.adoc[] // This section talks about Mill plugins. While it could theoretically fit in // either section above, it is probably an important enough topic it is worth // breaking out on its own -.Extending Mill -* xref:extending/import-ivy-plugins.adoc[] -* xref:extending/contrib-plugins.adoc[] +* Extending Mill +** xref:extending/import-ivy-plugins.adoc[] +** xref:extending/contrib-plugins.adoc[] // See also the list in Contrib_Plugins.adoc -** xref:contrib/artifactory.adoc[] -** xref:contrib/bintray.adoc[] -** xref:contrib/bloop.adoc[] -** xref:contrib/buildinfo.adoc[] -** xref:contrib/codeartifact.adoc[] -** xref:contrib/docker.adoc[] -** xref:contrib/flyway.adoc[] -** xref:contrib/gitlab.adoc[] -** xref:contrib/jmh.adoc[] -** xref:contrib/playlib.adoc[] -** xref:contrib/proguard.adoc[] -** xref:contrib/scalapblib.adoc[] -** xref:contrib/scoverage.adoc[] -** xref:contrib/sonatypecentral.adoc[] -** xref:contrib/testng.adoc[] -** xref:contrib/twirllib.adoc[] -** xref:contrib/versionfile.adoc[] -* xref:extending/thirdparty-plugins.adoc[] -* xref:extending/running-jvm-code.adoc[] -* xref:extending/writing-plugins.adoc[] -* xref:extending/meta-build.adoc[] -* xref:extending/new-language.adoc[] - +*** xref:contrib/artifactory.adoc[] +*** xref:contrib/bintray.adoc[] +*** xref:contrib/bloop.adoc[] +*** xref:contrib/buildinfo.adoc[] +*** xref:contrib/codeartifact.adoc[] +*** xref:contrib/docker.adoc[] +*** xref:contrib/flyway.adoc[] +*** xref:contrib/gitlab.adoc[] +*** xref:contrib/jmh.adoc[] +*** xref:contrib/playlib.adoc[] +*** xref:contrib/proguard.adoc[] +*** xref:contrib/scalapblib.adoc[] +*** xref:contrib/scoverage.adoc[] +*** xref:contrib/sonatypecentral.adoc[] +*** xref:contrib/testng.adoc[] +*** xref:contrib/twirllib.adoc[] +*** xref:contrib/versionfile.adoc[] +** xref:extending/thirdparty-plugins.adoc[] +** xref:extending/running-jvm-code.adoc[] +** xref:extending/writing-plugins.adoc[] +** xref:extending/meta-build.adoc[] +** xref:extending/new-language.adoc[] // This section focuses on diving into deeper, more advanced topics for Mill. // These are things that most Mill developers would not encounter day to day, // but people developing Mill plugins or working on particularly large or // sophisticated Mill builds will need to understand. -.Mill In Depth -* xref:depth/large-builds.adoc[] -* xref:depth/sandboxing.adoc[] -* xref:depth/evaluation-model.adoc[] -* xref:depth/design-principles.adoc[] -* xref:depth/why-scala.adoc[] - +* Mill In Depth +** xref:depth/large-builds.adoc[] +** xref:depth/sandboxing.adoc[] +** xref:depth/evaluation-model.adoc[] +** xref:depth/design-principles.adoc[] +** xref:depth/why-scala.adoc[] // Reference pages that a typical user would not typically read top-to-bottom, // but may need to look up once in a while, and thus should be written down // *somewhere*. -.Reference -* {mill-doc-url}/api/latest/mill/index.html[Mill Scaladoc] -* xref:reference/changelog.adoc[] +* Reference +** {mill-doc-url}/api/latest/mill/index.html[Mill Scaladoc] +** xref:reference/changelog.adoc[] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index ac3570125f5..e088bb05d6a 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -70,19 +70,27 @@ xref:extending/writing-plugins.adoc#_publishing[publish them] to Maven Central f To begin using Mill, check out the introductory documentation for each language: -* xref:javalib/intro.adoc[] -* xref:scalalib/intro.adoc[] -* xref:kotlinlib/intro.adoc[] -If you prefer a video introduction, see the following tech talk that was presented -at the Japan Java User Group Fall Conference Oct 2024: +[cols="1a,1a,1a"] +|=== +| xref:javalib/intro.adoc[image:index/iconscout-java.svg[java,32] Java with Mill] +| xref:scalalib/intro.adoc[image:index/iconscout-scala.svg[scala,20] Scala with Mill] +| xref:kotlinlib/intro.adoc[image:index/iconscout-kotlin.svg[kotlin,32] Kotlin with Mill] +|=== -* https://www.youtube.com/watch?v=Dry6wMRN6MI[Video: Better Java Builds with the Mill Build Tool] +For a quick introduction of why you may care about Mill, see the following page: + +* xref:why-mill.adoc[] + +Or if you prefer a video introduction: + +* https://www.youtube.com/watch?v=Dry6wMRN6MI[Video: Better Java Builds with the Mill Build Tool], + Japan Java User Group Fall Conference Oct 2024 Mill is used to build many real-world projects, such as the https://github.com/swaldman/c3p0[C3P0 JDBC Connection Pool], -https://github.com/coursier/coursier[Coursier JVM dependency resolver], +https://github.com/coursier/coursier[Coursier dependency resolver], https://github.com/com-lihaoyi/Ammonite[Ammonite REPL], and the https://github.com/SpinalHDL/SpinalHDL[SpinalHDL] and https://github.com/chipsalliance/chisel[Chisel] hardware design frameworks. @@ -95,9 +103,15 @@ https://gradle.org/[Gradle], https://bazel.build/[Bazel], but tries to learn fro strengths of each tool and improve on their weaknesses. For comparisons with existing build tools, check out these pages: -* xref:comparisons/maven.adoc[Mill vs Maven] -* xref:comparisons/gradle.adoc[Mill vs Gradle] -* xref:comparisons/sbt.adoc[Mill vs SBT] + +[cols="1a,1a,1a"] +|=== +| xref:comparisons/maven.adoc[image:index/maven.png[maven,24] Mill vs Maven] +| xref:comparisons/gradle.adoc[image:index/gradle.svg[gradle,32] Mill vs Gradle] +| xref:comparisons/sbt.adoc[image:index/sbt.png[sbt,32] Mill vs SBT] +|=== + + If you want to contribute to Mill, or are interested in the fundamental ideas behind Mill rather than the user-facing benefits discussed above, check out the page on @@ -107,3 +121,13 @@ they are interesting: * https://www.youtube.com/watch?v=UsXgCeU-ovI[Video: A Deep Dive into the Mill Build Tool] +To engage Mill with the community, you can use the channels below: + +* https://github.com/com-lihaoyi/mill/discussions[Mill Github Discussions] +* https://discord.com/invite/scala[Scala Discord], in the TOOLING#mill channel + +Mill maintains an open list of issue bounties below, with payouts ranging from +500USD to 3000USD per issue. Feel free to look through the list and try your +hand at any bounties that may interest you + +* https://github.com/orgs/com-lihaoyi/discussions/6[Open Issue Bounties] \ No newline at end of file diff --git a/docs/modules/ROOT/pages/javalib/intro.adoc b/docs/modules/ROOT/pages/javalib/intro.adoc index 9d96e594466..bd497c00ac5 100644 --- a/docs/modules/ROOT/pages/javalib/intro.adoc +++ b/docs/modules/ROOT/pages/javalib/intro.adoc @@ -12,7 +12,7 @@ // what Mill is, hopefully have downloaded an example to try out, and be // interested in learning more about the Mill build tool -= Building Java Projects with Mill += Building Java with Mill :page-aliases: Intro_to_Mill.adoc, Intro_to_Mill_for_Java.adoc, Java_Intro_to_Mill.adoc include::partial$gtag-config.adoc[] diff --git a/docs/modules/ROOT/pages/scalalib/intro.adoc b/docs/modules/ROOT/pages/scalalib/intro.adoc index a1fb3bbb050..7b6373b188b 100644 --- a/docs/modules/ROOT/pages/scalalib/intro.adoc +++ b/docs/modules/ROOT/pages/scalalib/intro.adoc @@ -11,7 +11,7 @@ // what Mill is, hopefully have downloaded an example to try out, and be // interested in learning more about the Mill build tool -= Building Scala Projects with Mill += Building Scala with Mill :page-aliases: Intro_to_Mill_for_Scala.adoc, Scala_Intro_to_Mill.adoc include::partial$gtag-config.adoc[] diff --git a/docs/modules/ROOT/pages/why-mill.adoc b/docs/modules/ROOT/pages/why-mill.adoc new file mode 100644 index 00000000000..1a083e1580a --- /dev/null +++ b/docs/modules/ROOT/pages/why-mill.adoc @@ -0,0 +1,527 @@ += Why Mill? + +Mill is a fast build tool for Java, Scala, and Kotlin. Although the Java +compiler is very fast and the Java language is easy to learn, JVM build tools are +known to be slow and hard to use. Mill tries to offer a better alternative: 2-10x faster +than Maven or Gradle, better IDE support, and extensibility without needing plugins. +This results in time savings due to less time waiting for your build tool, as well as less +time struggling with your build tool, and more time to focus on the actual work you need to do. + +At a first glance, Mill looks like any other build tool. You have build files, you configure +dependencies, you can compile, run, or test your project: + +```scala +// build.mill +package build +import mill._, javalib._ + +object foo extends JavaModule { + def ivyDeps = Agg( + ivy"net.sourceforge.argparse4j:argparse4j:0.9.0", + ivy"org.thymeleaf:thymeleaf:3.1.1.RELEASE" + ) + + object test extends JavaTests with TestModule.Junit4 +} +``` +```bash +> /mill foo.compile +compiling 1 Java source... + +> /mill foo.run --text hello +

hello

+ +> ./mill foo.test +Test foo.FooTest.testEscaping finished, ... +Test foo.FooTest.testSimple finished, ... +0 failed, 0 ignored, 2 total, ... +``` + +Beyond the basics, Mill provides 3 major advantages over other build tools. The comparison pages +for the respective build tool go into more detail (for xref:comparisons/maven.adoc[Maven], +xref:comparisons/gradle.adoc[Gradle], and xref:comparisons/sbt.adoc[SBT]), but at a high level +these advantages are: + +1. *Performance*: Mill offers a 2-10x speedup means less time waiting for your build tool, + meaning less time waiting for your build and more time doing useful work + +2. *Ease of Use*: Mill has better IDE support in IntelliJ and VSCode and richer + visualization tools that other tools, to help understand your build and what it is doing + +3. *Extensibility*: Mill lets you write code or use any published JVM library in your build, + customizing it to your needs without being limited by third-party plugins + +We will discuss each one in turn. + +## Performance + +### Maven + +Overall across our benchmarks, Mill is 5-10x faster than Maven for clean compiles, +both parallel and sequential, and for many modules or for a single module: + +|=== +| Benchmark | Maven | Mill | Speedup +| xref:comparisons/maven.adoc#_sequential_clean_compile_all[Sequential Clean Compile All] | 2m 31.12s | 0m 22.19s | 6.8x +| xref:comparisons/maven.adoc#_parallel_clean_compile_all[Parallel Clean Compile All] | 1m 16.45s | 0m 09.95s | 7.7x +| xref:comparisons/maven.adoc#_clean_compile_single_module[Clean Compile Single Module] | 0m 19.62s | 0m 02.17s | 9.0x +| xref:comparisons/maven.adoc#_incremental_compile_single_module[Incremental Compile Single Module] | 0m 21.10s | 0m 00.54s | 39.1x +| xref:comparisons/maven.adoc#_no_op_compile_single_module[No-Op Compile Single Module] | 0m 17.34s | 0m 00.47s | 36.9x +|=== + +For the purposes of this page, we will focus on two benchmarks + +#### Parallel Clean Compile All + +|=== +| Benchmark | Maven | Mill | Speedup +| xref:comparisons/maven.adoc#_parallel_clean_compile_all[Parallel Clean Compile All] | 1m 16.45s | 0m 09.95s | 7.7x +|=== + + +```bash +> time ./mvnw -T 10 -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install + +> ./mill clean; time ./mill __.compile +``` + + +This benchmark involves running `clean` to delete all generated files and re-compiling +everything in parallel. + +* For Maven, parallelism is opt-in via `-T 10`, while for Mill it is enabled by default. +* For Maven, tests and linters are opt-out via the `-D` properties, while in Mill + tests an linters are opt-in + +Mill sees a significant ~8x speedup over Maven for this benchmark. + +#### Incremental Compile Single-Module + +|=== +| Benchmark | Maven | Mill | Speedup +| xref:comparisons/maven.adoc#_incremental_compile_single_module[Incremental Compile Single Module] | 0m 21.10s | 0m 00.54s | 39.1x +|=== + +```bash +$ echo "" >> common/src/main/java/io/netty/util/AbstractConstant.java +$ time ./mvnw -pl common -DskipTests -Dcheckstyle.skip -Denforcer.skip=true install +Compiling 174 source files to /Users/lihaoyi/Github/netty/common/target/classes +Compiling 60 source files to /Users/lihaoyi/Github/netty/common/target/test-classes + + +$ echo "" >> common/src/main/java/io/netty/util/AbstractConstant.java +$ time ./mill common.test.compile +compiling 1 Java source to /Users/lihaoyi/Github/netty/out/common/compile.dest/classes ... +``` + +This benchmark involves making a single edit to a single already-compiled file in `common` - +adding a single newline to the end of the file - and re-compiling `common` and `common.test`. + +Mill sees a huge ~39x speedup for this benchmark, because Mill's incremental compiler +(https://github.com/sbt/zinc[Zinc]) is able to detect that only one file in one module +has changed, and that the change is small enough +to not require other files to re-compile. In contrast, Maven re-compiles all files in both +modules, even though only one file was touched and the change was trivial. + + +### Gradle + +The comparison with Gradle is less stark, but still significant. Mill is 3-4x faster than Gradle +across the various workflows: + + +|=== +| Benchmark | Gradle | Mill | Speedup +| xref:comparisons/maven.adoc#_sequential_clean_compile_all[Sequential Clean Compile All] | 17.6s | 5.40s | 3.3x +| xref:comparisons/maven.adoc#_parallel_clean_compile_all[Parallel Clean Compile All] | 12.3s | 3.57s | 3.4x +| xref:comparisons/maven.adoc#_clean_compile_single_module[Clean Compile Single Module] | 4.41s | 1.20s | 3.7x +| xref:comparisons/maven.adoc#_incremental_compile_single_module[Incremental Compile Single Module] | 1.37s | 0.51s | 2.7x +| xref:comparisons/maven.adoc#_no_op_compile_single_module[No-Op Compile Single Module] | 0.94s | 0.46s | 2.0x +|=== + +Again, for the purposes of this page, we will focus on two benchmarks + +#### Parallel Clean Compile All + +|=== +| Benchmark | Gradle | Mill | Speedup +| xref:comparisons/maven.adoc#_parallel_clean_compile_all[Parallel Clean Compile All] | 12.3s | 3.57s | 3.4x +|=== + +```bash +$ ./gradlew clean; time ./gradlew classes testClasses --no-build-cache + +$ ./mill clean; time ./mill __.compile +``` + +Here we only run compilation for classes and test classes, without linting or testing or anything else. +Both Mill and Gradle are parallel by default, with 1 thread per core. For Gradle we disabled the global +build cache to ensure we can benchmark the actual compilation time. + +We measure Mill being ~3.4x faster than Gradle for this benchmark. + +#### Incremental Compile Single-Module + +|=== +| Benchmark | Gradle | Mill | Speedup +| xref:comparisons/maven.adoc#_incremental_compile_single_module[Incremental Compile Single Module] | 1.37s | 0.51s | 2.7x +|=== + +```bash +$ echo "" >> src/main/java/org/mockito/BDDMockito.java; time ./gradlew :classes + +$ echo "" >> src/main/java/org/mockito/BDDMockito.java; time ./mill compile +compiling 1 Java source to /Users/lihaoyi/Github/netty/out/common/compile.dest/classes ... +``` + + +Again, this benchmark involves making a single edit to a single already-compiled file in the +root module - adding a single newline to the end of the file - and re-compiling it along with +its tests. + +Both Gradle and Mill appear to do a good job limiting the compilation to only the changed +file, but Mill has less fixed overhead than Gradle does, finishing in about ~0.5s +rather than ~1.5 seconds. + + +## Ease of Use + +The second area that Mill does well compared to tools like Maven or Gradle is in its ease +of use.This is not just in superficial things like the build file or command-line syntax, +but also in how Mill exposes how your build works and what your build is doing so you can +understand it and confidently make changes. We will consider three cases: the Mill Chrome +Profile, Mill Visualize, and Mill's IDE support + +### Chrome Profiles + +All Mill runs generate some debugging metadata files in `out/mill-*`. One of these +is `out/mill-chrome-profile.json`, which is a file following the Chrome Profiling format. +It can be loaded into any Chrome browser's built in `chrome://tracing` UI, to let you +interactively explore what Mill was doing during its last run. e.g. when performing a +clean compile on the Netty codebase, the profile ends up looking like this: + +image::comparisons/NettyCompileProfile.png[] + +The Chrome profile shows what task each Mill thread was executing throughout the run. +The Chrome profiling UI is interactive, so you can zoom in and out, or click on individual +tasks to show the exact duration and other metadata. + +But the real benefit of the Chrome profile isn't the low-level data it provides, but the +high-level view: + +* In the profile above, it is clear that for the first ~700ms, Mill is able + to use all cores on 10 cores on my laptop to do useful work. +* But after that, utilization is + much more sparse: `common.compile`, `buffer.compile`, `transport.compile`, `codec.compile`, + appear to wait for one another and run sequentially one after another. + + +This waiting is likely due to dependencies between them, and they take long enough that all +the other tasks depending on them get held up. For example, when `codec.compile` finishes +above, we can see a number of downstream tasks immediately start running. + +This understanding of your build's performance profile is not just an academic exercise, but +provides actionable information: + +* If I wanted faster Netty clean compiles, speeding up `common.compile`, `buffer.compile`, + `transport.compile`, or `codec.compile` would make the most impact. + +* On the other hand, time speeding up the various `codec-*.compile` tasks would help not at all: + these tasks are already running at a time where the CPUs are mostly idle. + +Most build tools do provide some way of analyzing build performance, but none of them provide +it as easily as Mill does: any Mill run generates a profile automatically, and any computer +with Chrome on it is able to load and let you explore that profile. That is a powerful tool to +help engineers understand what the build is doing: any engineer who felt a build was slow +can trivially load it into their Chrome browser to analyze and figure out what. + +### Mill Visualize + +Apart from the Mill Chrome Profile, Mill also provides the `./mill visualize` command, which +is useful to show the logical dependency graph between tasks. For example, we can use +`./mill visualize __.compile` (double `__` means wildcard) to +show the dependency graph between the modules of the Netty build below: + +image::comparisons/NettyCompileGraph.svg[] + +(_Right-click open-image-in-new-tab to see full size_) + +In this graph, we can clearly see that `common.compile`, `buffer.compile`,`transport.compile`, +and `codec.compile` depend on each other in a linear fashion. This explains why they each must +wait for the prior task to complete before starting, and cannot run in parallel with one another. +Furthermore, we can again confirm that many of the `codec-*.compile` tasks depend on `codec.compile`, +which is in the profile why we saw them waiting for the upstream task to complete before starting. + +Although these are things we could have _guessed_ from looking at the Chrome Profile above, +`./mill visualize` gives you a separate angle from which to look at your build. Together these +tools can help give greater understanding of what your build is doing and why it is doing that: +something that can be hard to come by with build tools that are often considered confusing and +inscrutable. + +### IDE Support + +One area that Mill does better than Gradle is providing a seamless IDE experience. For example, +consider the snippet below where we are using Gradle to configure the javac compiler options. +Due to `.gradle` files being untyped Groovy, the autocomplete and code-assist experience working +with these files is hit-or-miss. In the example below, we can see that IntelliJ is able to identify +that `compileArgs` exists and has the type `List`: + +image::comparisons/IntellijMockitoGradleCompileOptions.png[] + +But if you try to jump to definition or find out anything else about it you hit a wall: + +image::comparisons/IntellijMockitoGradleCompileOptions2.png[] + +Often working with build configurations feels like hitting dead ends: if you don't have +`options.compilerArgs` memorized in your head, there is literally nothing you can do in your editor to +make progress to figure out what it is or what it is used for. That leaves you googling +for answers, which can be a frustrating experience that distracts you from the task at hand. + +The fundamental problem with tools like Gradle is that the code you write does not +actually perform the build: rather, you are just setting up some data structure that +is used to configure the _real_ build engine that runs later. Thus when you explore +the Gradle build in an IDE, the IDE can only explore the configuration logic (which +is usually un-interesting) and is unable to explore the actual build logic (which +is what you actually care about!) + +In comparison, Mill's `.mill` files are all statically typed, and as a result IntelliJ is easily able to +pull up the documentation for `def javacOptions`, even though it doesn't have any special support +for Mill built into the IDE: + +image::comparisons/IntellijMockitoMillJavacOptionsDocs.png[] + +Apart from static typing, the way Mill builds are structured also helps the IDE: Mill +code _actually performs your build_, rather than configuring some opaque build engine. +While that sounds academic, one concrete consequence is that IntelliJ is able to take +your `def javacOptions` override and +find the original definitions that were overridden, and show you where they are defined: + +image::comparisons/IntellijMockitoMillJavacOptionsParents.png[] + +image::comparisons/IntellijMockitoMillJavacOptionsDef.png[] + +Furthermore, because task dependencies in Mill are just normal method calls, IntelliJ is +able to _find usages_, showing you where the task is used. Below, we can see the method +call in the `def compile` task, which uses `javacOptions()` along with a number of other tasks: + +image::comparisons/IntellijMockitoMillCompile.png[] + +From there, if you are curious about any of the other tasks used alongside `javacOptions`, it's +easy for you to pull up _their_ documentation, jump to _their_ +definition, or find _their_ usages. For example we can pull up the docs of + +`compileClasspath()` below, jump to _its_ implementation, and continue +interactively exploring your build logic: + +image::comparisons/IntellijMockitoMillCompileClasspath.png[] + +Unlike most other build tools, Mill builds can be explored interactively in your +IDE. If you do not know what something does, it's documentation, definition, or usages is always +one click away in IntelliJ or VSCode. This isn't a new experience for Java developers, as it +is what you would be used to day-to-day in your application code! But Mill brings that same +polished experience to your build system - traditionally some that that has been opaque +and hard to understand - and does so in a way that no other build tool does. + + +## Extensibility + +Mill allows you to directly write code to configure your build, and even download libraries +from Maven Central. + +Most build tools need plugins to do anything: if you want to Foo you need a +Foo plugin, if you want to Bar you need a Bar plugin, for any possible Foo or Bar. These could +be simple tasks - zipping up files, pre-rendering web templates, preparing static assets for +deployment - but even a tasks that would be trivial to implement in a few lines of code requires +you to Google for third-party plugins, dig through their Github to see which one is best +maintained, and hope for the best when you include it in your build. And while you could +write plugins yourself, doing so is usually challenging and non-trivial. + +Mill is different. Although it does have plugins for more advanced integrations, for most +simple things you can directly write code to achieve what you want, using the bundled +filesystem, subprocess, and dependency-management libraries. And even if you need third-party +libraries from Maven Central to do Foo, you can directly import the "Foo" library and use it +directly, without having to find a "Foo build plugin" wrapper. + +### Simple Custom Tasks + +The following Mill build is a minimal Java module `foo`. It contains no custom configuration, and +so inherits all the defaults from `mill.javalib.JavaModule`: default source folder layout, default +assembly configuration, default compiler flags, and so on. + +```scala +package build +import mill._, javalib._ + +object foo extends JavaModule { +} +``` +```bash +> mill compile +Compiling 1 Java source... +``` + +If you want to add a custom task, this is as simple as defining a method e.g. +`def lineCount = Task { ... }`. The body of `Task` performs the action we want, and +can depend on other tasks such as `allSourceFiles()` below: + +```scala +package build +import mill._, javalib._ + +object foo extends JavaModule { + /** Total number of lines in module source files */ + def lineCount = Task { + allSourceFiles().map(f => os.read.lines(f.path).size).sum + } +} +``` + +Once we define a new task, we can immediately begin using it in our build. +`lineCount` is not used by any existing `JavaModule` tasks, but we can still +show its value via the Mill command line to force it to evaluate: + +```bash +> mill show foo.lineCount +17 +``` + + +### Overriding Tasks + +To wire up `lineCount` into our main `JavaModule` `compile`/`test`/`run` tasks, +one way is to take the line count value and write it to a file in `def resources`. +This file can then be read at runtime as a JVM resource. We do that below +by overriding `def resources` and making it depend on `lineCount`, in addition +to its existing value `super.resources()`: + +```scala +package build +import mill._, javalib._ + +object foo extends JavaModule { + /** Total number of lines in module source files */ + def lineCount = Task { + allSourceFiles().map(f => os.read.lines(f.path).size).sum + } + + /** Generate resources using lineCount of sources */ + override def resources = Task { + os.write(Task.dest / "line-count.txt", "" + lineCount()) + super.resources() ++ Seq(PathRef(Task.dest)) + } +} +``` + + +Because `override def resources` overrides the existing `resources` method used +in the rest of `JavaModule`, the downstream tasks automatically now use the new +override instead, as that is how overrides work. That means if you call `mill foo.run`, +it will automatically pick up the new `line-count.txt` file and make it available to +the application code to use e.g. below, where we just print it out: + +```bash +> mill foo.run +Line Count: 17 +``` + +Most developers do not need to embed the line-count of their codebase in a resource +file to look up at runtime, but nevertheless this example shows how easy it is to write +code to perform ad-hoc tasks without needing to pull in and configure some third-party +plugin. And we get full IDE support with autocomplete/navigation/documentation/etc. +while we are writing our custom task. + + +While most build tools do allow writing and wiring up custom tasks, none of them +have a workflow as simple as Mill. Next, we'll look at a more realistic example, +which includes usage of third-party libraries in the build. + +### Using Libraries from Maven Central in Tasks + +Earlier on we discussed possibly pre-rendering HTML pages in the build so they can be +served at runtime. The use case for this are obvious: if a page never changes, rendering +it on every request is wasteful, and even rendering it once and then caching it can impact +your application startup time. Thus, you may want to move some HTML rendering to build-time, +but with traditional build tools such a move is sufficiently inconvenient and complicated +that people do not do it. + +With Mill, pre-rendering HTML at build time is really easy, even if you need a third-party +library. Mill does not ship with a bundled HTML templating engine, but you can use the +`import $ivy` syntax to include one such as Thymeleaf, which would immediately make the +Thymeleaf classes available for you to import and use in your build as below: + + +```scala +package build +import mill._, javalib._ +import $ivy.`org.thymeleaf:thymeleaf:3.1.1.RELEASE` +import org.thymeleaf.TemplateEngine +import org.thymeleaf.context.Context +object foo extends JavaModule { + def htmlSnippet = Task { + val context = new Context() + context.setVariable("heading", "hello") + new TemplateEngine().process( + "

", + context + ) + } + def resources = Task.Sources{ + os.write(Task.dest / "snippet.txt", htmlSnippet()) + super.resources() ++ Seq(PathRef(Task.dest)) + } +} +``` + +Once we have run `import $ivy`, we can import `TemplateEngine`, `Context`, and replace our +`def lineCount` with a `def htmlSnippet` task that uses Thymeleaf to render HTML. Again, +we get full IDE support for working with the Thymeleaf Java API, the new `htmlSnippet` task +is inspectable from the Mill command line via `show`, and we wire it up into +`def resources` so it can be inspected and used at runtime by the application +(in this case just printed out): + + +```bash +> mill show foo.htmlSnippet +"

hello

" + +> mill foo.compile +compiling 1 Java source... +... + +> mill foo.run +generated snippet.txt resource:

hello

+``` + +Rendering HTML using the Thymeleaf templating engine is not rocket science, but what is +interesting here is what we did _not_ need to do: + +* We did _not_ need to find a Thymeleaf-Mill plugin in order to include Thymeleaf in our + build + +* We did _not_ need to learn a special API or framework for authoring build plugins ourselves + to write a plugin to include Thymeleaf in our build + +Instead, we could simply import Thymeleaf directly from Maven Central and use it just +like we would use it in any Java application, complete with full IDE support for +autocomplete and code navigation, with the same experience you probably are already +used to for your application code. This makes it an order of magnitude easier for +non-experts to configure their build to do exactly what they need, rather than be +limited by what some unmaintained third-party plugin might support. And although +the configuration is done in the Scala language, the syntax should be relatively +familiar ("Java without Semicolons" it is sometimes called) and the JVM libraries +and tools (e.g. Thymeleaf, IntelliJ, VSCode) are the exact same libraries and tools +you are already used to. + +## Conclusion + +To wrap up, Mill does all the same things that other build tools like Maven or Gradle do, +but aims to do them better: faster, easier to use, and easier to extend. +This means both time saved waiting for your build tool to run in day-to-day work, as well +as time saved when you inevitably need to evolve or adjust your build system to accommodate +changing requirements. + +With Mill, you can therefore spend less time waiting for or fighting with your build tool, +and more time on the actual work you are trying to accomplish. And while traditionally build +systems were often mysterious black boxes that only experts could work with, Mill's ease +of use and ease of extension democratize the build system so any developer can figure out +what its doing or extend it to do exactly what they need. diff --git a/docs/package.mill b/docs/package.mill index e43370f5272..baa449acf7d 100644 --- a/docs/package.mill +++ b/docs/package.mill @@ -3,7 +3,7 @@ import org.jsoup._ import mill.util.Jvm import mill._, scalalib._ import de.tobiasroeser.mill.vcs.version.VcsVersion -import collection.JavaConverters._ +import scala.jdk.CollectionConverters._ /** Generates the mill documentation with Antora. */ object `package` extends RootModule { @@ -28,6 +28,7 @@ object `package` extends RootModule { Jvm.runSubprocess( commandArgs = Seq( npmExe, + "--no-audit", "install", "@antora/cli@3.1.9", "@antora/site-generator-default@3.1.9", @@ -192,6 +193,7 @@ object `package` extends RootModule { val taggedSources = for (path <- extraSources) yield { s""" - url: ${build.baseDir} | start_path: ${path.relativeTo(build.baseDir)} + | edit_url: false |""".stripMargin } s"""site: @@ -206,6 +208,7 @@ object `package` extends RootModule { | - url: ${if (authorMode) build.baseDir else build.Settings.projectUrl} | branches: [] | tags: ${build.Settings.legacyDocTags.filter(_ => !authorMode).map("'" + _ + "'").mkString("[", ",", "]")} + | edit_url: false | start_path: docs/antora | |${taggedSources.mkString("\n\n")} @@ -237,7 +240,7 @@ object `package` extends RootModule { |""".stripMargin } - def oldDocSources = Task { + def oldDocSources: T[Seq[PathRef]] = Task { for (oldVersion <- build.Settings.docTags) yield { val checkout = T.dest / oldVersion os.proc("git", "clone", T.workspace / ".git", checkout).call(stdout = os.Inherit) @@ -254,21 +257,22 @@ object `package` extends RootModule { generatePages(authorMode = false)().apply(oldDocSources().map(_.path)) } - def localPages = Task { + def localPages: T[PathRef] = Task { val pages = generatePages(authorMode = true)().apply(oldDocSources().map(_.path)) T.log.outputStream.println( - s"You can browse the local pages at: ${(pages.path / "index.html").toNIO.toUri()}" + s"You can browse the pages at: ${(pages.path / "index.html").toNIO.toUri()}" ) + pages } - def fastPages = Task { + def fastPages: T[PathRef] = Task { val pages = generatePages(authorMode = true)().apply(Nil) T.log.outputStream.println( - s"You can browse the local pages at: ${(pages.path / "index.html").toNIO.toUri()}" + s"You can browse the pages at: ${(pages.path / "index.html").toNIO.toUri()}" ) pages } - def generatePages(authorMode: Boolean) = T.task { extraSources: Seq[os.Path] => + def generatePages(authorMode: Boolean) = Task.Anon { extraSources: Seq[os.Path] => T.log.errorStream.println("Creating Antora playbook ...") // dependency to sources source() diff --git a/docs/supplemental-ui/partials/header-content.hbs b/docs/supplemental-ui/partials/header-content.hbs index 5f134d0e22b..e3a6630f666 100644 --- a/docs/supplemental-ui/partials/header-content.hbs +++ b/docs/supplemental-ui/partials/header-content.hbs @@ -20,7 +20,7 @@ API Issues Discuss - Chat +