Scala Library Dependencies
This page goes into more detail about configuring third party dependencies
for ScalaModule
.
Adding Ivy Dependencies
package build
import mill._, scalalib._
object `package` extends RootModule with ScalaModule {
def scalaVersion = "2.12.17"
def ivyDeps = Agg(
ivy"com.lihaoyi::upickle:3.1.0",
ivy"com.lihaoyi::pprint:0.8.1",
ivy"${scalaOrganization()}:scala-reflect:${scalaVersion()}"
)
}
You can define the ivyDeps
field to add ivy dependencies to your module,
named after the Ivy Dependency Resolver
and JVM package repository format. The Agg
factory function constructs an
Agg[Dep]
collection, which is a collection of `Dep`s without duplicates,
a type common in Mill builds.
-
Single
:
syntax (e.g."ivy"org.testng:testng:6.11"
) defines Java dependencies
-
Double
::
syntax (e.g.ivy"com.lihaoyi::upickle:0.5.1"
) defines Scala dependencies -
Triple
:::
syntax (e.g.ivy"org.scalamacros:::paradise:2.1.1"
) defines -
Triple
:::
syntax (e.g.ivy"org.scalamacros:::paradise:2.1.1"
) defines dependencies cross-published against the full Scala version e.g.2.12.4
instead of just2.12
. These are typically Scala compiler plugins or similar.
To select the test-jars from a dependency use the following syntax:
-
ivy"org.apache.spark::spark-sql:2.4.0;classifier=tests
.
Please consult the Library Dependencies in Mill section for more details.
> ./mill run i am cow
pretty-printed using PPrint: Array("i", "am", "cow")
serialized using uPickle: ["i","am","cow"]
Runtime and Compile-time Dependencies
If you want to use additional dependencies at runtime or override
dependencies and their versions at runtime, you can do so with
runIvyDeps
.
package build
import mill._, scalalib._
object foo extends ScalaModule {
def scalaVersion = "2.13.8"
def moduleDeps = Seq(bar)
def runIvyDeps = Agg(
ivy"javax.servlet:servlet-api:2.5",
ivy"org.eclipse.jetty:jetty-server:9.4.42.v20210604",
ivy"org.eclipse.jetty:jetty-servlet:9.4.42.v20210604"
)
def mainClass = Some("bar.Bar")
}
You can also declare compile-time-only dependencies with compileIvyDeps
.
These are present in the compile classpath, but will not propagate to the
transitive dependencies.
object bar extends ScalaModule {
def scalaVersion = "2.13.8"
def compileIvyDeps = Agg(
ivy"javax.servlet:servlet-api:2.5",
ivy"org.eclipse.jetty:jetty-server:9.4.42.v20210604",
ivy"org.eclipse.jetty:jetty-servlet:9.4.42.v20210604"
)
}
Typically, Mill assumes that a module with compile-time dependencies will
only be run after someone includes the equivalent run-time dependencies in
a later build step. e.g. in the case above, bar
defines the compile-time
dependencies, and foo
then depends on bar
and includes the runtime
dependencies. That is why we can run foo
as show below:
> ./mill foo.runBackground
> curl http://localhost:8079
<html><body>Hello World!</body></html>
Compile-time dependencies are translated to provided -scoped
dependencies when publish to Maven or Ivy-Repositories.
|
Both compileIvyDeps
and runIvyDeps
are non-transitive: a module does not
automatically aggregate them from its upstream dependencies. They must be
defined in every module that they are required in, either explicitly or via
a trait
that the module inherits from.
Keeping up-to-date with Scala Steward
It’s always a good idea to keep your dependencies up-to-date.
If your project is hosted on GitHub, GitLab, or Bitbucket, you can use Scala Steward to automatically open a pull request to update your dependencies whenever there is a newer version available.
Scala Steward can also keep your Mill version up-to-date. |
Unmanaged Jars
In most scenarios you should rely on ivyDeps
/moduleDeps
and let Mill manage
the compilation/downloading/caching of classpath jars for you, as Mill will
automatically pull in transitive dependencies which are generally needed for things
to work, and avoids including different versions of the same classfiles or jar which
can cause confusion. But in the rare case you receive a jar or folder-full-of-classfiles
from somewhere and need to include it in your project, unmanagedClasspath
is the
way to do it.
package build
import mill._, scalalib._
object `package` extends RootModule with ScalaModule {
def scalaVersion = "2.13.8"
def unmanagedClasspath = Task {
if (!os.exists(millSourcePath / "lib")) Agg()
else Agg.from(os.list(millSourcePath / "lib").map(PathRef(_)))
}
}
You can override unmanagedClasspath
to point it at any jars you place on the
filesystem, e.g. in the above snippet any jars that happen to live in the
lib/
folder.
> ./mill run '{"name":"John","age":30}' # mac/linux
Key: name, Value: John
Key: age, Value: 30
Downloading Unmanaged Jars
You can also override unmanagedClasspath
to point it at jars that you want to
download from arbitrary URLs.
requests.get
comes from the Requests-Scala
library, one of Mill’s Bundled Libraries.
package build
import mill._, scalalib._
object `package` extends RootModule with ScalaModule {
def scalaVersion = "2.13.8"
def unmanagedClasspath = Task {
os.write(
Task.dest / "fastjavaio.jar",
requests.get.stream(
"https://github.com/williamfiset/FastJavaIO/releases/download/1.1/fastjavaio.jar"
)
)
Agg(PathRef(Task.dest / "fastjavaio.jar"))
}
}
> ./mill run "textfile.txt" # mac/linux
I am cow
hear me moo
I weigh twice as much as you
Tasks like unmanagedClasspath
are
cached, so your jar is downloaded only once and re-used indefinitely after that.
This is usually not a problem, because usually URLs follow the rule that
Cool URIs don’t change, and so jars
downloaded from the same URL will always contain the same contents.
An unmanaged jar downloaded via requests.get is still unmanaged: even
though you downloaded it from somewhere, it requests.get does not know how to
pull in third party dependencies or de-duplicate different versions on the classpath
All the same caveats you need to worry about when dealing with unmanaged jars
apply here as well.
|
Repository Config
By default, dependencies are resolved from Maven Central,
the standard package repository for JVM languages like Java, Kotlin, or Scala. You
can also add your own resolvers by overriding the repositoriesTask
task in the module:
package build
import mill._, scalalib._
import mill.define.ModuleRef
import coursier.maven.MavenRepository
val sonatypeReleases = Seq(
MavenRepository("https://oss.sonatype.org/content/repositories/releases")
)
object foo extends ScalaModule {
def scalaVersion = "2.13.8"
def ivyDeps = Agg(
ivy"com.lihaoyi::scalatags:0.12.0",
ivy"com.lihaoyi::mainargs:0.6.2"
)
def repositoriesTask = Task.Anon {
super.repositoriesTask() ++ sonatypeReleases
}
}
Mill uses the Coursier dependency resolver, and reads Coursier config files automatically.
You can configure Coursier to use an alternate download location for Maven Central
artifacts via a mirror.properties
file:
central.from=https://repo1.maven.org/maven2
central.to=http://example.com:8080/nexus/content/groups/public
Note theses default config file locatations:
-
Linux:
~/.config/coursier/mirror.properties
-
MacOS:
~/Library/Preferences/Coursier/mirror.properties
-
Windows:
C:\Users\<user_name>\AppData\Roaming\Coursier\config\mirror.properties
You can also set the environment variable COURSIER_MIRRORS
or the jvm property coursier.mirrors
to specify config file location.
To add custom resolvers to the initial bootstrap of the build, you can create a
custom ZincWorkerModule
(named after the Zinc Incremental compiler
used to compile the build.mill
files) and override the zincWorker
method in your
ScalaModule
by pointing it to that custom object:
object CustomZincWorkerModule extends ZincWorkerModule with CoursierModule {
def repositoriesTask = Task.Anon { super.repositoriesTask() ++ sonatypeReleases }
}
object bar extends ScalaModule {
def scalaVersion = "2.13.8"
def zincWorker = ModuleRef(CustomZincWorkerModule)
// ... rest of your build definitions
def repositoriesTask = Task.Anon { super.repositoriesTask() ++ sonatypeReleases }
}
> ./mill foo.run --text hello
> ./mill bar.compile