Skip to content

Commit

Permalink
Prepare changelog for 0.12.2 (#3934)
Browse files Browse the repository at this point in the history
CC @lefou FYI
  • Loading branch information
lihaoyi authored Nov 10, 2024
1 parent 3f6a03c commit a3e677e
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 126 deletions.
198 changes: 72 additions & 126 deletions docs/modules/ROOT/pages/comparisons/why-mill.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
= Why Mill?
= Why Use 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
Expand Down Expand Up @@ -69,54 +69,15 @@ both parallel and sequential, and for many modules or for a single module:
| xref:comparisons/maven.adoc#_no_op_compile_single_module[No-Op Compile Single Module] | 0m 05.25s | 0m 00.47s | 11.2x
|===

For this article, 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] | 0m 48.92s | 0m 08.79s | 5.6x
|===


```bash
> time ./mvnw -T 10 -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install

> ./mill clean; time ./mill __.compile
```


First, let's look at *Parallel Clean Compile All*.
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 and linters are opt-in

Mill sees a significant ~6x 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 06.82s | 0m 00.54s | 12.6x
|===

```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 ...
```
everything in parallel. Mill sees a significant ~6x speedup over Maven for this benchmark.
You can click on the link above to see a more detailed discussion of how this benchmark was
run.

The second benchmark worth noting is *Incremental Compile Single Module*.
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 ~12x 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
Expand All @@ -126,7 +87,7 @@ 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
The comparison with Gradle is less stark, but still significant. Mill is 2-4x faster than Gradle
across the various workflows:


Expand All @@ -139,49 +100,19 @@ across the various workflows:
| 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 article, we will focus on two benchmarks
Mill's various "clean compile" workflows 3-4x faster than Gradle's, while it's incremental
and no-op compile workflows are 2x faster. 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.

#### Parallel Clean Compile All
In general, these benchmarks don't show Mill doing anything that Maven or Gradle do not:
these are equivalent builds for the same projects (https://github.com/netty/netty[Netty] and
https://github.com/mockito/mockito[Mockito] respectively), compiling the same number of files
using the same Java compiler, in the same module structure and passing the same suite of tests.
Rather, what we are seeing is Mill simply having less build-tool overhead than Maven or Gradle,
so the performance of the underlying JVM and Java compiler (which is actually pretty fast!) can
really shine through.

|===
| 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
Expand Down Expand Up @@ -246,7 +177,7 @@ 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`,
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`,
Expand Down Expand Up @@ -280,9 +211,9 @@ for answers, which can be a frustrating experience that distracts you from the t
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!)
the Gradle build in an IDE, the IDE can only explore the configuration logic (the
`getCompilerArgs` method above) and is unable to explore the actual build logic (how
`getCompilerArgs` _actually gets used in Gradle_)

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
Expand All @@ -298,6 +229,8 @@ find the original definitions that were overridden, and show you where they are

image::comparisons/IntellijMockitoMillJavacOptionsParents.png[]

You can jump to any of the overriden `def`s quickly and precisely:

image::comparisons/IntellijMockitoMillJavacOptionsDef.png[]

Furthermore, because task dependencies in Mill are just normal method calls, IntelliJ is
Expand All @@ -309,9 +242,8 @@ 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:
interactively exploring your build logic from there:

image::comparisons/IntellijMockitoMillCompileClasspath.png[]

Expand All @@ -334,7 +266,7 @@ be simple tasks - zipping up files, pre-rendering web templates, preparing stati
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.
write plugins yourself, doing so is usually 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
Expand Down Expand Up @@ -418,26 +350,18 @@ object foo extends JavaModule {
```


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:
Because our `def resources` overrides the existing `resources` method inherited from `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 `resources`
including the generated `line-count.txt` file and make it available to
the application code to use e.g. to print it out at runtime:

```bash
> mill foo.run
Line Count: 18
```

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,
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
Expand Down Expand Up @@ -506,27 +430,49 @@ interesting here is what we did _not_ need to do:
* 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

* We did _not_ need to add fragile shell scripts to augment our build logic and
implement the functionality we need.


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.
like we would use it in any Java application, with IDE support, typechecking,
and automatic parallelism and caching.

'''


Most real projects require some kind of ad-hoc build tasks: you may be pre-processing static
assets for web deployment, embedding build metadata for runtime debugging, or pre-rendering
HTML pages to optimize performance at runtime. With most build tools, you often needed to pull
in some poorly-maintained plugin off of Github, write your own using a complicated plugin
framework, or even wrap your build tool in ad-hoc shell scripts. With most other build tools,
caching and parallelism are things that the build author needs to use manually, meaning nobody
gets it right and your build performance is never as good as it could be.

In contrast, Mill makes it easy it is to write concise type-checked code to perform ad-hoc tasks
to do whatever you need to do. You get full IDE support, automatic caching and
parallelism, and access to the huge JVM library ecosystem on Maven Central.
Rather than grabbing unmaintained plugins off of Github or augmenting your build
with fragile shell scripts, Mill allows your own custom logic to be implemented
in a way that is flexible, performant, and safe, such that anyone can configure their
build correctly and achieve maximum performance even without being a build tool expert.

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

Build systems have traditionally been mysterious black boxes that only experts could work
with: slow for unknown reasons, with cargo-culted configuration and usage commands,
and challenging for normal application developers to contribute improvements to.
Mill flips this on its head, democratizing your build system such that even non-experts
are able to contribute, and can do so safely and easily such that your build workflows
achieve their maximum possible performance.

The rest of this doc-site contains more Mill build tool comparisons
(with xref:comparisons/maven.adoc[Maven], xref:comparisons/gradle.adoc[Gradle],
xref:comparisons/sbt.adoc[SBT]), with getting started instructions
for using Mill with xref:javalib/intro.adoc[Java], with xref:scalalib/intro.adoc[Scala],
or with xref:kotlinlib/intro.adoc[Kotlin], and detailed documentation for how Mill
works. Please try it out and let us know in the
https://github.com/com-lihaoyi/mill/discussions[discussions forum] how it goes!
27 changes: 27 additions & 0 deletions readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,33 @@ endif::[]
// find-replace-regex: https://github.com/com-lihaoyi/mill/pull/(\d*) -> {link-pr}/$1[#$1]


[#0-12-2]
=== 0.12.2 - 2024-11-11

* Show correct build file name in progress prompt {link-pr}/3847[#3847]
* Fix target name when reporting cycles in `runModuleDeps` {link-pr}/3860[#3860]
* Properly escape imported submodule names when the name is a Scala identifier {link-pr}/3865[#3865]
* Report an error when module dependency cycles are detected {link-pr}/3878[#3878]
* Drop incremental compilation state for Mill modules when version changes to try and mitigate cache invalidation issues {link-pr}/3884[#3884]
* Share profile loggers between nested evaluations to avoid ensure commands like `show` now have a proper profile {link-pr}/3885[#3885]
* Add `nativeMultithreading` flag in ScalaNativeModule {link-pr}/3896[#3896]
* Add nice error when non-exclusive task depends on exclusive task {link-pr}/3887[#3887]
* Filter out anonymous classes during test discovery to mitigate spurious test reports {link-pr}/3911[#3911]
* Fix `MavenTests` deprecation message {link-pr}/3915[#3915]
* Improve `inspect` command to show more detailed metadata on modules and tasks {link-pr}/3916[#3916]
* Fix prompt updating logic to ensure the timestamp updates while tasks are running {link-pr}/3933[#3933]
* Fix scoverage report generation for Scala 3 {link-pr}/3936[#3936]
* Tons of documentation improvements: autoformatted all https://github.com/com-lihaoyi/mill/pull/3919[Java]
and https://github.com/com-lihaoyi/mill/pull/3903[Scala] example files,
[Scala-Native example builds]https://github.com/com-lihaoyi/mill/pull/3657,
https://github.com/com-lihaoyi/mill/pull/3897[Scala-JS WASM example build],
https://github.com/com-lihaoyi/mill/pull/3918[Re-run Maven comparison using proper flags]
added https://mill-build.org/mill/comparisons/why-mill.html[Why Use Mill?] and
https://mill-build.org/mill/comparisons/unique.html[What Makes Mill Unique?] Sections




[#0-12-1]
=== 0.12.1 - 2024-10-27

Expand Down

0 comments on commit a3e677e

Please sign in to comment.