Skip to content

Commit

Permalink
Comparison Documentation Review (#3767)
Browse files Browse the repository at this point in the history
  • Loading branch information
lihaoyi authored Oct 18, 2024
1 parent 79958e3 commit 50eb241
Show file tree
Hide file tree
Showing 54 changed files with 299 additions and 83 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
259 changes: 218 additions & 41 deletions docs/modules/ROOT/pages/comparisons/gradle.adoc

Large diffs are not rendered by default.

64 changes: 43 additions & 21 deletions docs/modules/ROOT/pages/comparisons/maven.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Mill's performance compares to Maven:
| <<Parallel Clean Compile All>> | 1m 16.45s | 0m 09.95s | 7.7x
| <<Clean Compile Single-Module>> | 0m 19.62s | 0m 02.17s | 9.0x
| <<Incremental Compile Single-Module>> | 0m 21.10s | 0m 00.54s | 39.1x
| <<No-Op Compile Single-Module>> | 0m 17.34s | 0m 00.47s | 39.1x
| <<No-Op Compile Single-Module>> | 0m 17.34s | 0m 00.47s | 36.9x
|===

The column on the right shows the speedups of how much faster Mill is compared to the
Expand All @@ -75,7 +75,7 @@ $ time ./mvnw -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install
2m 27.58s
2m 31.12s

$ ./mill clean; time ./mill __.compile
$ ./mill clean; time ./mill -j1 __.compile
0m 29.14s
0m 22.19s
0m 20.79s
Expand All @@ -85,7 +85,8 @@ This benchmark exercises the simple "build everything from scratch" workflow, wi
artifacts already in the local cache. The actual files
being compiled are the same in either case (as mentioned in the <<Completeness>> section).
I have explicitly disabled the various linters and tests for the Maven build, to just focus
on the compilation of Java source code making it an apples-to-apples comparison.
on the compilation of Java source code making it an apples-to-apples comparison. As Mill
runs tasks in parallel by default, I have disabled parallelism explicitly via `-j1`

As a point of reference, Java typically compiles at 10,000-50,000 lines per second on a
single thread, and the Netty codebase is ~500,000 lines of code, so we would expect compile
Expand Down Expand Up @@ -151,12 +152,12 @@ $ time ./mvnw -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install
2m 27.58s
2m 31.12s

$ ./mill clean; time ./mill __.compile
$ ./mill clean; time ./mill -j1 __.compile
0m 29.14s
0m 22.19s
0m 20.79s

$ ./mill clean; time ./mill __.jar
$ ./mill clean; time ./mill -j1 __.jar
0m 32.58s
0m 24.90s
0m 23.35s
Expand All @@ -171,19 +172,19 @@ whereas Mill directly uses the classfiles generated on disk to bypass all that w
=== Parallel Clean Compile All

```bash
$ time ./mvnw -T 4 -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install
$ time ./mvnw -T 10 -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install
1m 19.58s
1m 16.34s
1m 16.45s

$ ./mill clean; time ./mill -j 4 __.compile
$ ./mill clean; time ./mill __.compile
0m 14.80s
0m 09.95s
0m 08.83s
```

This example compares Maven v.s. Mill, when performing the clean build on 4 threads.
Both build tools support parallelism (`-T 4` in Maven and `-j 4` in Mill), and both
This example compares Maven v.s. Mill, when performing the clean build on 10 threads.
Both build tools support parallelism (`-T 10` in Maven, by default in Mill), and both
tools see a similar ~2x speedup for building the Netty project using 4 threads. Again,
this tests a clean build using `./mvnw clean` or `./mill clean`.

Expand All @@ -197,7 +198,7 @@ when performing a clean build of the Netty repository.
$ time ./mvnw -pl common -DskipTests -Dcheckstyle.skip -Denforcer.skip=true clean install
0m 19.62s
0m 20.52s
0:19:50
0m 19.50s

$ ./mill clean common; time ./mill common.test.compile
0m 04.94s
Expand Down Expand Up @@ -350,13 +351,13 @@ we saw with Maven, but due to Mill's minimal overhead, in the end the command
finishes in less than half a second.


== Extensibility
== Extensibility & IDE Experience

Even though Maven is designed to be declarative, in many real-world codebases you end
up needing to run ad-hoc scripts and logic. This section will explore two such scenarios,
so you can see how Mill differs from Maven in the handling of these requirements.

=== Groovy
=== JVM Libraries: Groovy

The Maven build for the `common/` subproject
uses a Groovy script for code generation. This is configured via:
Expand Down Expand Up @@ -433,13 +434,26 @@ object common extends NettyModule{
While the number of lines of code _written_ is not that different, the Mill configuration
is a lot more direct: rather than writing 35 lines of XML to configure an opaque third-party
plugin, we instead write 25 lines of code to directly do what we want: import `groovy`,
configure a `GroovyShell`, and use it to evaluate our `codegen.groovy` script.
configure a `GroovyShell`, and use it to evaluate our `codegen.groovy` script. Although
you may not be familiar with the Scala language that Mill builds are written in, you could
probably skim the snippet above and guess what it is doing, and guess correctly.

This direct control means you are not beholden to third party plugins: rather than being
limited to what an existing plugin _allows_ you to do, Mill allows you to directly write
the code necessary to do what _you need to do_.
the code necessary to do what _you need to do_. In this case, if we need to invoke
https://github.com/apache/groovy[Groovy] and
https://github.com/groovy/groovy-core/blob/4c05980922a927b32691e4c3eba5633825cc01e3/subprojects/groovy-ant/src/spec/doc/groovy-ant-task.adoc[Groovy-Ant],
Mill allows us to direct xref:extending/import-ivy-plugins.adoc[import $ivy] the relevant
JVM artifacts from Maven Central and begin using them in our build code in a safe,
strongly-typed fashion, with full autocomplete and code assistance:

=== Calling Make
image::comparisons/IntellijNettyAutocomplete.png[]

Mill gives you the full power of the JVM ecosystem to use in your build: any Java library
on Maven central is just an `import $ivy` away, and can be used with the full IDE support
and tooling experience you are used to in the JVM ecosystem.

=== Subprocesses: Make

The Maven build for the `transport-native-unix-common/` subproject needs to call
`make` in order to compile its C code to modules that can be loaded into Java applications
Expand Down Expand Up @@ -583,12 +597,19 @@ In Mill, we define the `makefile`, `cSources`, `cHeaders`, and `make` tasks. The
of the logic is in `def make`, which prepares the `makefile` and C sources,
resolves the `netty-jni-util` source jar and unpacks it with `jar xf`, and calls `make`
with the given environment variables. Both `cHeaders` and the output of `make` are used
in downstream modules.
in downstream modules. In this case, `make` is a command-line utility rather than a JVM
library, so rather than importing it from Maven Central we use `os.proc.call` to invoke it.

Again, the Maven XML and Mill code contains exactly the same logic, and neither is
much more concise or verbose than the other. Rather, what is interesting is that
it is much easier to work with this kind of _build logic_ via _concise type-checked code_,
rather than configuring a bunch of third-party plugins to try and achieve what you want.
With Mill, you get your full IDE experience working with your build: autocomplete, code
assistance, navigation, and so on. Although working with the `os.proc.call` subprocess API
is not as right as working with the JVM libraries we saw earlier, it is still a much
richer experience than you typically get configuring XML files:

image::comparisons/IntellijNettyPeekDocs.png[]


== Debugging Tooling
Expand All @@ -597,13 +618,14 @@ Another area that Mill does better than Maven is providing builtin tools for you
what your build is doing. For example, the Netty project build discussed has 47 submodules
and associated test suites, but how do these different modules depend on each other? With
Mill, you can run `./mill visualize __.compile`, and it will show you how the
`compile` task of each module depends on the others:
`compile` task of each module depends on the others (right-click open-image-in-new-tab to see
at full size):

image::comparisons/NettyCompileGraph.svg[]

Apart from the static dependency graph, another thing of interest may be the performance
profile and timeline: where the time is spent when you actually compile everything. With
Mill, when you run a compilation using `./mill -j 10 __.compile`, you automatically get a
Mill, when you run a compilation using `./mill __.compile`, you automatically get a
`out/mill-chrome-profile.json` file that you can load into your `chrome://tracing` page and
visualize where your build is spending time and where the performance bottlenecks are:

Expand Down Expand Up @@ -639,13 +661,13 @@ C code or run `make` or Groovy.
Mill doesn't try to do _more_ than Maven does, but it
tries to do it _better_: faster compiles, shorter and easier to read configs, easier
extensibility via libraries (e.g. `org.codehaus.groovy:groovy`) and subprocesses
(e.g. `make`).
(e.g. `make`), better IDE support for working with your build.

Again, the Mill build used in this comparison is for demonstration purposes, and more
work would be necessary to make the Mill build production ready: compatibility with
different operating system architectures, Java versions, and so on. However, hopefully
it demonstrates the potential value: improved performance, conciseness of the build logic,
and easy extensibility so you can fine-tune your build logic to your requirements.
it demonstrates the potential value: greatly improved performance, easy extensibility,
and a much better IDE experience for working with your build.
Mill provides builtin tools to help you navigate,
visualize, and understand your build, turning a normally opaque "build config" into
something that's transparent and easily understandable.
4 changes: 0 additions & 4 deletions docs/modules/ROOT/pages/javalib/build-examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ example projects.

include::partial$example/javalib/builds/1-nested-modules.adoc[]

== Maven-Compatible Modules

include::partial$example/javalib/builds/2-compat-modules.adoc[]


== Realistic Java Example Project

Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/javalib/builtin-commands.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
:language-small: java


include::partial$example/javalib/basic/4-builtin-commands.adoc[]
include::partial$example/javalib/basic/5-builtin-commands.adoc[]
4 changes: 4 additions & 0 deletions docs/modules/ROOT/pages/javalib/intro.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ include::partial$example/javalib/basic/2-custom-build-logic.adoc[]
include::partial$example/javalib/basic/3-multi-module.adoc[]

include::partial$Intro_to_Mill_Footer.adoc[]

== Maven-Compatible Modules

include::partial$example/javalib/basic/4-compat-modules.adoc[]
4 changes: 0 additions & 4 deletions docs/modules/ROOT/pages/kotlinlib/build-examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ example projects.

include::partial$example/kotlinlib/builds/1-nested-modules.adoc[]

== Maven-Compatible Modules

include::partial$example/kotlinlib/builds/2-compat-modules.adoc[]


== Realistic Kotlin Example Project

Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/kotlinlib/builtin-commands.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
= Built-in Commands
:page-aliases: Kotlin_Builtin_Commands.adoc

include::partial$example/kotlinlib/basic/4-builtin-commands.adoc[]
include::partial$example/kotlinlib/basic/5-builtin-commands.adoc[]
4 changes: 4 additions & 0 deletions docs/modules/ROOT/pages/kotlinlib/intro.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ include::partial$example/kotlinlib/basic/2-custom-build-logic.adoc[]
include::partial$example/kotlinlib/basic/3-multi-module.adoc[]

include::partial$Intro_to_Mill_Footer.adoc[]

== Maven-Compatible Modules

include::partial$example/kotlinlib/basic/4-compat-modules.adoc[]
5 changes: 0 additions & 5 deletions docs/modules/ROOT/pages/scalalib/build-examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ example projects.
include::partial$example/scalalib/builds/1-nested-modules.adoc[]


== SBT-Compatible Modules

include::partial$example/scalalib/builds/2-compat-modules.adoc[]


== Cross-Scala-Version Modules

include::partial$example/scalalib/builds/3-cross-scala-version.adoc[]
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/scalalib/builtin-commands.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
:language-small: scala


include::partial$example/scalalib/basic/4-builtin-commands.adoc[]
include::partial$example/scalalib/basic/5-builtin-commands.adoc[]
6 changes: 6 additions & 0 deletions docs/modules/ROOT/pages/scalalib/intro.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,9 @@ include::partial$example/scalalib/basic/2-custom-build-logic.adoc[]
include::partial$example/scalalib/basic/3-multi-module.adoc[]

include::partial$Intro_to_Mill_Footer.adoc[]


== SBT-Compatible Modules

include::partial$example/scalalib/basic/4-compat-modules.adoc[]

Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
//// SNIPPET:ALL
// Mill's default folder layout of `foo/src/` and `foo/test/src` differs from that
// of Maven or Gradle's `foo/src/main/java/` and `foo/src/test/java/`. If you are
// migrating an existing codebase, you can use Mill's `MavenModule` and
// `MavenTests` as shown below to preserve filesystem compatibility with an existing
// Maven or Gradle build:

package build
import mill._, javalib._

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
//// SNIPPET:ALL
// Mill's default folder layout of `foo/src/` and `foo/test/src` differs from that
// of Maven or Gradle's `foo/src/main/kotlin/` and `foo/src/test/kotlin/`. If you are
// migrating an existing codebase, you can use Mill's `KotlinMavenModule` and
// `KotlinMavenTests` as shown below to preserve filesystem compatibility with an existing
// Maven or Gradle build:

package build
import mill._, kotlinlib._

object foo extends KotlinModule with KotlinMavenModule {

def kotlinVersion = "1.9.24"

object test extends KotlinMavenModuleTests with TestModule.Junit5 {
object test extends KotlinMavenTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
Expand Down
2 changes: 0 additions & 2 deletions example/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ object `package` extends RootModule with Module {

trait ExampleCrossModuleKotlin extends ExampleCrossModuleJava {
override def lineTransform(line: String) = this.millModuleSegments.parts.last match {
case "4-builtin-commands" =>
line.replace("compile.dest/zinc", "compile.dest/kotlin.analysis.dummy")
case "1-test-suite" => line
.replace("mill bar.test bar.BarTests.hello", "kotest_filter_tests='hello' kotest_filter_specs='bar.BarTests' ./mill bar.test")
.replace("compiling 1 ... source...", "Compiling 1 ... source...")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
//// SNIPPET:ALL
// Mill's default folder layout of `foo/src/` and `foo/test/src` differs from that
// of SBT's `foo/src/main/scala/` and `foo/src/test/scala/`. If you are
// migrating an existing codebase from SBT, you can use Mill's `SbtModule` and
// `SbtTests` as shown below to preserve filesystem compatibility with an existing
// SBT build:

package build
import mill._, scalalib._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Inputs:

> mill show foo.compile
{
"analysisFile": ".../out/foo/compile.dest/zinc",
"analysisFile": ".../out/foo/compile.dest/...",
"classes": ".../out/foo/compile.dest/classes"
}
*/
Expand Down
2 changes: 1 addition & 1 deletion kotlinlib/src/mill/kotlinlib/KotlinMavenModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ trait KotlinMavenModule extends KotlinModule with MavenModule {
millSourcePath / "src/main/resources"
}

trait KotlinMavenModuleTests extends KotlinTests with MavenTests {
trait KotlinMavenTests extends KotlinTests with MavenTests {
override def intellijModulePath: os.Path = millSourcePath / "src/test"

override def sources = T.sources(
Expand Down

0 comments on commit 50eb241

Please sign in to comment.