Multi-File Builds
Mill allows you to break up your build.mill
file into smaller files by defining the
build-related logic for any particular subfolder as a package.mill
file in that subfolder.
This can be very useful to keep large Mill builds maintainable, as each folder’s build logic
gets co-located with the files that need to be built, and speeds up compilation of the
build logic since each build.mill
or package.mill
file can be compiled independently when
it is modified without re-compiling all the others.
This means that instead of one large build.mill
file at the root of your repo:
build.mill
You have a smaller build.mill
with the config for each sub-folder broken
out into that folder’s respective `package.mill
foo/ src/... package.mill bar/ package.mill qux/ mymodule/src/... package.mill build.mill
Example Project
package build
import mill._, scalalib._
trait MyModule extends ScalaModule {
def scalaVersion = "2.13.11"
}
package build.foo
import mill._, scalalib._
object `package` extends build.MyModule {
def moduleDeps = Seq(build.bar.qux.mymodule)
def mvnDeps = Seq(mvn"com.lihaoyi::mainargs:0.4.0")
}
package build.bar
package build.bar.qux
import mill._, scalalib._
object mymodule extends build.MyModule {
def mvnDeps = Seq(mvn"com.lihaoyi::scalatags:0.8.2")
}
In this example, the root build.mill
only contains the trait MyModule
, but it is
foo/package.mill
and bar/qux/package.mill
that define modules using it. The modules
defined in foo/package.mill
and bar/qux/package.mill
are automatically nested within
foo
and bar.qux
respectively, and can be referenced from the command line as below:
> ./mill resolve __
bar
...
bar.qux.mymodule
...
bar.qux.mymodule.compile
...
foo
...
foo.compile
> ./mill bar.qux.mymodule.compile
> ./mill foo.compile
> ./mill foo.run --foo-text hello --bar-qux-text world
Foo.value: hello
BarQux.value: <p>world</p>
Note that in this example, foo/package.mill
defines object package extends mill.Module
,
and so the name .package
does not need to be provided at the command line. In contrast,
bar/qux/package.mill
defines object mymodule
which is not named package
, and so
we need to explicitly reference it with a .mymodule
suffix.
package.mill
files are only discovered in direct subfolders of the root build.mill
or
subfolders of another folder containing a package.mill
; Hence in this example, we need
an bar/package.mill
to be present for bar/qux/package.mill
to be discovered, even
though bar/package.mill
is empty.
Helper Files
Apart from having package
files in subfolders to define modules, Mill
also allows you to have helper code in any *.mill
file in the same folder
as your build.mill
or a package.mill
.
package build
import mill._, scalalib._
object `package` extends MyModule {
def forkEnv = Map(
"MY_SCALA_VERSION" -> build.scalaVersion(),
"MY_PROJECT_VERSION" -> foo.myProjectVersion
)
}
package build
import mill._, scalalib._
def myScalaVersion = "2.13.14"
trait MyModule extends ScalaModule {
def scalaVersion = myScalaVersion
}
package build.foo
import mill._, scalalib._
object `package` extends build.MyModule {
def forkEnv = Map(
"MY_SCALA_VERSION" -> build.myScalaVersion,
"MY_PROJECT_VERSION" -> myProjectVersion
)
}
package build.foo
def myProjectVersion = "0.0.1"
Different helper scripts and build.mill
/package
files can all refer to
each other using the build
object, which marks the root object of your build.
In this example:
-
build.mill
,util.mill
and other adjacent files can be referred to as simplebuild
-
foo/package
,foo/versions.mill
, and other adjacent files can be referred to as simplebuild.foo
Helper files are very handy for you to put custom logic which you use in
your build.mill
or package.mill
module definitions. This can help keep
your build.mill
or package.mill
files concise and understandable.
> ./mill run
Main Env build.util.myScalaVersion: 2.13.14
Main Env build.foo.versions.myProjectVersion: 0.0.1
> ./mill foo.run
Foo Env build.util.myScalaVersion: 2.13.14
Foo Env build.foo.versions.myProjectVersion: 0.0.1