Kotlin Build Examples

On this page, we will explore the Mill build tool via a series of simple Kotlin example projects.

Nesting Modules

build.mill (download, browse)
package build
import mill._, kotlinlib._

trait MyModule extends KotlinModule {

  def kotlinVersion = "1.9.24"

  def ivyDeps = Agg(
    ivy"com.github.ajalt.clikt:clikt-jvm:4.4.0",
    ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0"
  )
}

object foo extends MyModule {
  def moduleDeps = Seq(bar, qux)

  def mainClass = Some("foo.FooKt")

  object bar extends MyModule
  object qux extends MyModule {
    def moduleDeps = Seq(bar)

    def mainClass = Some("foo.qux.QuxKt")
  }
}

object baz extends MyModule {
  def moduleDeps = Seq(foo.bar, foo.qux, foo)

  def mainClass = Some("baz.BazKt")
}

Modules can be nested arbitrarily deeply within each other. The outer module can be the same kind of module as the ones within, or it can be a plain Module if we just need a wrapper to put the modules in without any tasks defined on the wrapper.

The outer module can also depend on the inner module (as shown above), and vice versa, but not both at the same.

Running tasks on the nested modules requires the full module path foo.bar.run

> mill resolve __.run
foo.bar.run
foo.qux.run
baz.run

> mill foo.run --bar-text hello --qux-text world --foo-text today
Bar.value: <h1>hello</h1>
Qux.value: <p>world</p>
Foo.value: <p>today</p>

> mill baz.run --bar-text hello --qux-text world --foo-text today --baz-text yay
Bar.value: <h1>hello</h1>
Qux.value: <p>world</p>
Foo.value: <p>today</p>
Baz.value: <p>yay</p>

> mill foo.qux.run --bar-text hello --qux-text world
Bar.value: <h1>hello</h1>
Qux.value: <p>world</p>

Maven-Compatible Modules

build.mill (download, browse)
package build
import mill._, kotlinlib._

object foo extends KotlinModule with KotlinMavenModule {

  def kotlinVersion = "1.9.24"

  object test extends KotlinMavenModuleTests with TestModule.Junit5 {
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
    )
  }
}

KotlinMavenModule is a variant of KotlinModule that uses the more verbose folder layout of Maven, SBT, and other tools:

  • foo/src/main/java

  • foo/src/main/kotlin

  • foo/src/test/java

  • foo/src/test/kotlin

Rather than Mill’s

  • foo/src

  • foo/test/src

This is especially useful if you are migrating from Maven to Mill (or vice versa), during which a particular module may be built using both Maven and Mill at the same time

> mill foo.compile
Compiling 1 Kotlin source...

> mill foo.test.compile
Compiling 1 Kotlin source...

> mill foo.test.test
...foo.FooTestshello ...

> mill foo.test
...foo.FooTestshello ...

Realistic Kotlin Example Project

build.mill (download, browse)
package build
import mill._, kotlinlib._, publish._

trait MyModule extends KotlinModule with PublishModule {
  def publishVersion = "0.0.1"

  def pomSettings = PomSettings(
    description = "Hello",
    organization = "com.lihaoyi",
    url = "https://github.com/lihaoyi/example",
    licenses = Seq(License.MIT),
    versionControl = VersionControl.github("lihaoyi", "example"),
    developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
  )

  def kotlinVersion = "1.9.24"

  def ivyDeps = Agg(ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")

  object test extends KotlinTests with TestModule.Junit5 {
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
    )
  }
}

object foo extends MyModule {
  def moduleDeps = Seq(bar, qux)

  def mainClass = Some("foo.FooKt")

  def generatedSources = Task {
    os.write(
      T.dest / "Version.kt",
      s"""
package foo

object Version {
  fun value() = "${publishVersion()}"
}
      """.stripMargin
    )
    Seq(PathRef(T.dest))
  }
}

object bar extends MyModule {
  def moduleDeps = Seq(qux)
}

object qux extends MyModule {
  def mainClass = Some("qux.QuxKt")
}

A semi-realistic build setup, combining all the individual Mill concepts:

  • Three `KotlinModule`s that depend on each other

  • With unit testing and publishing set up

  • With generated sources to include the publishVersion as a string in the code, so it can be printed at runtime

Note that for multi-module builds like this, using queries to run tasks on multiple modules at once can be very convenient:

__.test
__.publishLocal

Also note how you can use traits to bundle together common combinations of modules: MyModule not only defines a KotlinModule with some common configuration, but it also defines a object test module within it with its own configuration. This is a very useful technique for managing the often repetitive module structure in a typical project

> mill resolve __.run
bar.run
bar.test.run
foo.run
foo.test.run
qux.run

> mill foo.run
foo version 0.0.1
Foo.value: <h1>hello</h1>
Bar.value: <p>world</p>
Qux.value: 31337

> mill bar.test
...bar.BarTestsworld ...

> mill qux.run
Qux.value: 31337

> mill __.compile

> mill __.test
...bar.BarTestsworld ...
...foo.FooTestshello ...

> mill __.publishLocal
Publishing Artifact(com.lihaoyi,foo,0.0.1) to ivy repo...
Publishing Artifact(com.lihaoyi,bar,0.0.1) to ivy repo...
Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo...
...

> mill show foo.assembly # mac/linux
".../out/foo/assembly.dest/out.jar"

> ./out/foo/assembly.dest/out.jar # mac/linux
foo version 0.0.1
Foo.value: <h1>hello</h1>
Bar.value: <p>world</p>
Qux.value: 31337