Cross Builds

Mill handles cross-building of all sorts via the Cross[T] module.

Defining Cross Modules

You can use this as follows:

object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12")
class FooModule(crossVersion: String) extends Module {
  def suffix = T { crossVersion }
  def bigSuffix = T { suffix().toUpperCase() }
}

This defines three copies of FooModule: "210", "211" and "212", each of which has their own suffix target. You can then run them via

mill show foo[2.10].suffix
mill show foo[2.10].bigSuffix
mill show foo[2.11].suffix
mill show foo[2.11].bigSuffix
mill show foo[2.12].suffix
mill show foo[2.12].bigSuffix

Please be aware that some shells like zsh interpret square brackets differently, so quoting or masking might be needed.

mill show foo\[2.10\].suffix
mill show 'foo[2.10].suffix'
mill show "foo[2.10].suffix"

The modules each also have a millSourcePath of

foo/2.10
foo/2.11
foo/2.12

And the suffix targets will have the corresponding output paths for their metadata and files:

foo/2.10/suffix
foo/2.10/bigSuffix
foo/2.11/suffix
foo/2.11/bigSuffix
foo/2.12/suffix
foo/2.12/bigSuffix

You can also have a cross-build with multiple inputs:

val crossMatrix = for {
  crossVersion <- Seq("210", "211", "212")
  platform <- Seq("jvm", "js", "native")
  if !(platform == "native" && crossVersion != "212")
} yield (crossVersion, platform)

object foo extends mill.Cross[FooModule](crossMatrix:_*)
class FooModule(crossVersion: String, platform: String) extends Module {
  def suffix = T { crossVersion + "_" + platform }
}

Here, we define our cross-values programmatically using a for-loop that spits out tuples instead of individual values. Our FooModule template class then takes two parameters instead of one. This creates the following modules each with their own suffix target:

mill show foo[210,jvm].suffix
mill show foo[211,jvm].suffix
mill show foo[212,jvm].suffix
mill show foo[210,js].suffix
mill show foo[211,js].suffix
mill show foo[212,js].suffix
mill show foo[212,native].suffix

Using Cross Modules from Outside

You can refer to targets defined in cross-modules as follows:

object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12")
class FooModule(crossVersion: String) extends Module {
  def suffix = T { crossVersion }
}

def bar = T { "hello " + foo("2.10").suffix }

Here, foo("2.10") references the "2.10" instance of FooModule. You can refer to whatever versions of the cross-module you want, even using multiple versions of the cross-module in the same target:

object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12")
class FooModule(crossVersion: String) extends Module {
  def suffix = T { crossVersion }
}

def bar = T { "hello " + foo("2.10").suffix + " world " + foo("2.12").suffix }

Using Cross Modules from other Cross Modules

Targets in cross-modules can depend on one another the same way that external targets:

object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12")
class FooModule(crossVersion: String) extends Module {
  def suffix = T { crossVersion }
}

object bar extends mill.Cross[BarModule]("2.10", "2.11", "2.12")
class BarModule(crossVersion: String) extends Module {
  def bigSuffix = T { foo(crossVersion).suffix().toUpperCase() }
}

Here, you can run:

mill show foo[2.10].suffix
mill show foo[2.11].suffix
mill show foo[2.12].suffix
mill show bar[2.10].bigSuffix
mill show bar[2.11].bigSuffix
mill show bar[2.12].bigSuffix

Cross Resolvers

You can define an implicit mill.define.Cross.Resolver within your cross-modules, which would let you use a shorthand foo() syntax when referring to other cross-modules with an identical set of cross values:

trait MyModule extends Module {
  def crossVersion: String
  implicit object resolver extends mill.define.Cross.Resolver[MyModule] {
    def resolve[V <: MyModule](c: Cross[V]): V = c.itemMap(List(crossVersion))
  }
}

object foo extends mill.Cross[FooModule]("2.10", "2.11", "2.12")
class FooModule(val crossVersion: String) extends MyModule {
  def suffix = T { crossVersion }
}

object bar extends mill.Cross[BarModule]("2.10", "2.11", "2.12")
class BarModule(val crossVersion: String) extends MyModule {
  def longSuffix = T { "_" + foo().suffix() }
}

While the example resolver simply looks up the target Cross value for the cross-module instance with the same crossVersion, you can make the resolver arbitrarily complex. E.g. the resolver for mill.scalalib.CrossSbtModule looks for a cross-module instance whose scalaVersion is binary compatible (e.g. 2.10.5 is compatible with 2.10.3) with the current cross-module.