Common Project Layouts
Earlier, we have shown how to work with the Mill default Scala module layout. Here we will explore some other common project layouts that you may want in your Scala build:
Java Project with Test Suite
build.sc
object core extends JavaModule {
object test extends Tests with TestModule.Junit4
}
object app extends JavaModule {
def moduleDeps = Seq(core)
object test extends Tests with TestModule.Junit4
}
This build is a two-module Java project with junit test suites. It expects the following filesystem layout:
build.sc
app/
src/hello/
Main.java
test/src/hello/
MyAppTests.java
core/
src/hello/
Core.java
test/src/hello/
MyCoreTests.java
You can then run the junit tests using mill app.test
or mill core.test
, and
configure which exact tests you want to run using the flags defined on the
JUnit Test Interface.
For a more more complex, real-world example of a Java build, check out our example build for the popular Caffeine project:
Cross Scala-Version Modules
build.sc
import mill._
import mill.scalalib._
object foo extends Cross[FooModule]("2.10.6", "2.11.11", "2.12.4")
class FooModule(val crossScalaVersion: String) extends CrossScalaModule {
...
object test extends Tests {
...
}
}
Mill provides a CrossScalaModule
template, which can be used with Cross
to
cross-build Scala modules across different versions of Scala. The default
configuration for CrossScalaModule
expects a filesystem layout as follows:
build.sc
foo/
src/
src-2.10/
src-2.11/
src-2.12/
test/
src/
src-2.10/
src-2.11/
src-2.12/
Code common to all Scala versions lives in src
, while code specific to one
version lives in src-x.y
.
Scala.js Modules
build.sc
import mill._
import mill.scalalib._
import mill.scalajslib._
import mill.scalajslib.api._
object foo extends ScalaJSModule {
def scalaVersion = "2.13.8"
def scalaJSVersion = "1.10.1"
def ivyDeps = Agg(ivy"org.scala-js::scalajs-dom::2.2.0")
def moduleKind = T { ModuleKind.CommonJSModule }
object test extends Tests with TestModule.Utest {
def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.0")
}
}
ScalaJSModule
is a variant of ScalaModule
that builds your code using
Scala.js. In addition to the standard foo.compile
and foo.run
commands (the
latter of which runs your code on Node.js, which must be pre-installed)
ScalaJSModule
also exposes the foo.fastLinkJS
and foo.fullLinkJS
tasks for
generating the optimized Javascript file.
ScalaJSModule requires declaring it’s dependencies as third party
libraries (don’t forget to use double-colons ::
between the library name and the version for Scala.js dependencies).
If you are following the Scala.js Tutorial, in the section where you test the code, it’s mentioned that you need to make the DOM available to Node.js. Install the jsdom
node library as instructed then add def jsEnvConfig = T(JsEnvConfig.JsDom())
to your build.sc
object. In Scala 3, the "scalajs-env-jsdom-nodejs" mentioned in the tutorial is no longer required.
Scala Native Modules
build.sc
import mill._, scalalib._, scalanativelib._, mill.scalanativelib.api._
object hello extends ScalaNativeModule {
def scalaVersion = "2.13.10"
def scalaNativeVersion = "0.4.9"
def logLevel = NativeLogLevel.Info // optional
def releaseMode = ReleaseMode.Debug // optional
}
.
├── build.sc
└── hello
├── src
│ └── hello
│ └── Hello.scala
hello/src/hello/Hello.scala
package hello
import scalatags.Text.all._
object Hello{
def main(args: Array[String]): Unit = {
println("Hello! " + args.toList)
println(div("one"))
}
}
The normal commands mill hello.compile
, mill hello.run
, all work. If you
want to build a standalone executable, you can use mill show hello.nativeLink
to create it.
ScalaNativeModule
builds scala sources to executable binaries using
Scala Native. You will need to have the
relevant parts of the
LLVM toolchain installed on your system. Optimized binaries can be built by
setting releaseMode
(see above) and more verbose logging can be enabled using
logLevel
. Currently two test frameworks are supported
utest and
scalatest. Support for
scalacheck should be possible when the relevant
artifacts have been published for scala native.
Here’s a slightly larger example, demonstrating how to use third party
dependencies (note the two sets of double-colons ::
necessary) and a test
suite:
build.sc
import mill._, scalalib._, scalanativelib._
object hello extends ScalaNativeModule {
def scalaNativeVersion = "0.3.8"
def scalaVersion = "2.11.12"
def ivyDeps = Agg(ivy"com.lihaoyi::scalatags::0.6.7")
object test extends Tests with TestModule.Utest {
def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.6.3")
}
}
.
├── build.sc
└── hello
├── src
│ └── hello
│ └── Hello.scala
└── test
└── src
└── HelloTests.scala
hello/test/src/HelloTests.scala
package hello
import utest._
import scalatags.Text.all._
object HelloTests extends TestSuite{
val tests = Tests{
'pass - {
assert(div("1").toString == "<div>1</div>")
}
'fail - {
assert(123 == 1243)
}
}
}
The same mill hello.compile
or mill hello.run
still work, as does `mill
hello.test
to run the test suite defined here.
SBT-Compatible Modules
build.sc
import mill._
import mill.scalalib._
object foo extends SbtModule {
def scalaVersion = "2.12.4"
}
These are basically the same as normal ScalaModule
s, but configured to follow
the SBT project layout:
build.sc
foo/
src/
main/
scala/
test/
scala/
Useful if you want to migrate an existing project built with SBT without having to re-organize all your files
SBT-Compatible Cross Scala-Version Modules
build.sc
import mill._
import mill.scalalib._
object foo extends Cross[FooModule]("2.10.6", "2.11.11", "2.12.4")
class FooModule(val crossScalaVersion: String) extends CrossSbtModule {
...
object test extends Tests {
...
}
}
A CrossSbtModule
is a version of CrossScalaModule
configured with the SBT
project layout:
build.sc
foo/
src/
main/
scala/
scala-2.10/
scala-2.11/
scala-2.12/
test/
scala/
scala-2.10/
scala-2.11/
scala-2.12/
Publishing
build.sc
import mill._
import mill.scalalib._
import mill.scalalib.publish._
object foo extends ScalaModule with PublishModule {
def scalaVersion = "2.12.4"
def publishVersion = "0.0.1"
def pomSettings = PomSettings(
description = "My first library",
organization = "com.lihaoyi",
url = "https://github.com/com-lihaoyi/mill",
licenses = Seq(License.MIT),
versionControl = VersionControl.github("lihaoyi", "mill"),
developers = Seq(
Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")
)
)
}
You can make a module publishable by extending PublishModule
.
PublishModule
then needs you to define a publishVersion
and pomSettings
.
The artifactName
defaults to the name of your module (in this case foo
) but can be overridden.
The organization
is defined in pomSettings
.
You may also check and update the values of sonatypeUri
and sonatypeSnapshotUri
, which may not be correct if you have a newer Sonatype account (when created after Feb. 2021).
Staging Releases
Once you’ve mixed in PublishModule
, you can publish your libraries to maven
central via:
mill mill.scalalib.PublishModule/publishAll \
foo.publishArtifacts \
lihaoyi:$SONATYPE_PASSWORD \
--gpgArgs --passphrase=$GPG_PASSWORD,--batch,--yes,-a,-b
This uploads them to oss.sonatype.org
where you can log-in and stage/release
them manually. You can also pass in the --release true
flag to perform the
staging/release automatically:
Sonatype credentials can be passed via environment variables ( |
mill mill.scalalib.PublishModule/publishAll \
foo.publishArtifacts \
lihaoyi:$SONATYPE_PASSWORD \
--gpgArgs --passphrase=$GPG_PASSWORD,--batch,--yes,-a,-b \
--release true
If you want to publish/release multiple modules, you can use the or
_
wildcard syntax:
mill mill.scalalib.PublishModule/publishAll \
__.publishArtifacts \
lihaoyi:$SONATYPE_PASSWORD \
--gpgArgs --passphrase=$GPG_PASSWORD,--batch,--yes,-a,-b \
--release true
To publish to repository other than oss.sonaytype.org
such as internal hosted
nexus at example.company.com
, you can pass in the --sonatypeUri
and
--sonatypeSnapshotUri
parameters to uploads to different site:
mill mill.scalalib.PublishModule/publishAll \
foo.publishArtifacts \
lihaoyi:$SONATYPE_PASSWORD \
--sonatypeUri http://example.company.com/release \
--sonatypeSnaphostUri http://example.company.com/snapshot
Since Feb. 2021 any new Sonatype accounts have been created on
|
Non-Staging Releases (classic Maven uploads)
If the site does not support staging releases as oss.sonatype.org
and s01.oss.sonatype.org
do (for
example, a self-hosted OSS nexus site), you can pass in the
--stagingRelease false
option to simply upload release artifacts to corresponding
maven path under sonatypeUri
instead of staging path.
mill mill.scalalib.PublishModule/publishAll \
foo.publishArtifacts \
lihaoyi:$SONATYPE_PASSWORD \
--sonatypeUri http://example.company.com/release \
--stagingRelease false
Example Builds
Mill comes bundled with example builds for existing open-source projects, as integration tests and examples:
Acyclic
A small single-module cross-build, with few sources, minimal dependencies, and wired up for publishing to Maven Central.
Jawn
A collection of relatively small modules, all cross-built across the same few versions of Scala.
Upickle
A single cross-platform Scala.js/Scala-JVM module cross-built against multiple versions of Scala, including the setup necessary for publishing to Maven Central.