= Library Dependencies in Mill
:link-coursier: https://github.com/coursier/coursier
:link-coursier-doc: https://get-coursier.io/docs/overview
:page-aliases: Library_Dependencies.adoc



Beside the dependencies between Mill modules, most non-trivial source projects have dependencies to other libraries.

Mill uses {link-coursier}[coursier] to resolve and download dependencies.
Once downloaded, they are located in the coursier specific cache locations.
For more details about coursier, refer to the {link-coursier-doc}[coursier documentation].

== Dependencies in General

Mill dependencies have the simple form:

----
mvn"{organization}:{name}:{version}"
----

Additional attributes are also supported:

----
mvn"{organization}:{name}:{version}[;{attribute}={value}]*"
----

When working in other Java and Scala projects, you will find some synonyms, which typically all mean the same.
For example in the Maven ecosystem, the `organization` is called the `group` and the `name` is called the `artifact`.
The whole triplet is often called `GAV`, short for "Group, Artifact, Version".

In Mill we use the additional term `artifactId` which is identical to the `name` when used in the normal form shown above.
When a different form is used, e.g. some double-colons are used between the parts, the `artifactId` typically contains suffixes, but the name doesn't.

.Example for a simple Java Dependency
[source,scala]
----
def mvnDeps = Seq(
  mvn"org.slf4j:slf4j-api:1.7.25"
)
----


=== Test dependencies (there is no `test` scope)

One difference between Mill and other build tools like sbt or Maven is the fact, that tests are ordinary submodules on their own.
For convenience, most modules already come with a pre-configured trait for a test submodule,
which already inherits all dependencies of their parent module.
If you need additional test dependencies, you simply add them by overriding `def mvnDeps`, as you would do with normal library dependencies.

When migrating a sbt project and seeing a dependency like this: `"ch.qos.logback" % "logback-classic" % "1.2.3" % "test"`,
simply add it to the test module's `mvnDeps` as ordinary dependency.
There is no special test scope in Mill.

.Example
[source,scala]
----
object main extends JavaModule {
  object test extends JavaTests {
    def mvnDeps = Seq(
      mvn"org.qos.logback:logback-classic:1.2.3"
    )
  }
}
----

=== Compile-only dependencies (`provided` scope)

If you want to use a dependency only at compile time, you can declare it with the `compileMvnDeps` task.

.Example
[source,scala]
----
def compileMvnDeps = Seq(
  mvn"org.slf4j:slf4j-api:1.7.25"
)
----

When Mill generated file to interact with package manager like `pom.xml` for Maven repositories, such compile-only dependencies are mapped to the `provided` scope.

Please note, that dependencies with `provided` scope will never be resolved transitively. Hence, the name "provided", as the task runtime needs to "provide" them, if they are needed.


=== Runtime dependencies

If you want to declare dependencies to be used at runtime (but not at compile time), you can use the `runMvnDeps` tasks.

.Example
[source,scala]
----
def runMvnDeps = Seq(
  mvn"ch.qos.logback:logback-classic:1.2.0"
)
----

It is also possible to use a higher version of the same library dependencies already defined in `mvnDeps`, to ensure you compile against a minimal API version, but actually run with the latest available version.

== Transitive dependencies

To render a tree of dependencies (transitive included) you can run `mill myModule.showMvnDepsTree`. Here is how the start of `./mill __.showMvnDepsTree` looks like in the `mill` project itself:

[source,text]
----
├─ ch.epfl.scala:bsp4j:2.1.0-M3
│  ├─ org.eclipse.lsp4j:org.eclipse.lsp4j.generator:0.12.0
│  │  ├─ org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0
│  │  │  └─ com.google.code.gson:gson:2.9.1
│  │  └─ org.eclipse.xtend:org.eclipse.xtend.lib:2.24.0
│  │     ├─ org.eclipse.xtend:org.eclipse.xtend.lib.macro:2.24.0
│  │     │  └─ org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.24.0
...
│  │  ├─ com.lihaoyi:fastparse_2.13:2.3.0
│  │  │  ├─ com.lihaoyi:geny_2.13:0.6.0 -> 0.7.1 (possible incompatibility)
│  │  │  │  └─ org.scala-lang:scala-library:2.13.10
│  │  │  └─ com.lihaoyi:sourcecode_2.13:0.2.1 -> 0.3.0 (possible incompatibility)
----

After compiling your module(s) you can find and examine files such as `mvnDeps.json` and `transitiveMvnDeps.json` in your `out` build's folder for a given module.
After running the `showMvnDepsTree` command you'll also find the `showMvnDepsTree.json` and `showMvnDepsTree.log` file that contain the output of the above `showMvnDepsTree` command.

You can observe the actual version being used by running `mill show myModule.resolvedMvnDeps`. If you run `mill myModule.resolvedMvnDeps`, the same information is available in `out/myModule/resolvedMvnDeps.json`.

=== Figuring out where a dependency comes from

There will be times when you want to figure out where a dependency is coming
from. The output of `showMvnDepsTree` can be quite large in larger projects so the
command provides a nice utility to be able to target the part of the tree that
brings in a specific dependency.

For example, let's again  use the Mill codebase as an example. We'll search the
tree in the  `main` module and try to find where the `jsoniter-scala-core_2.13`
artifact is coming from using the `--whatDependsOn` argument:

[source,txt]
----
❯ ./mill dev.run ~/Documents/scala-workspace/com-lihaoyi/mill  main.showMvnDepsTree --whatDependsOn com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-core_2.13
[33/33] main.showMvnDepsTree
└─ com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-core_2.13:2.13.5
   ├─ io.get-coursier:coursier_2.13:2.1.0-RC1
   └─ org.virtuslab.scala-cli:config_2.13:0.1.16
      └─ io.get-coursier:coursier-cache_2.13:2.1.0-RC1
         └─ io.get-coursier:coursier_2.13:2.1.0-RC1
----

By looking at the output we can see that it's our dependency on `coursier_2.13`
that is bringing in the `jsoniter-scala-core_2.13` artifact.

The `--whatDependsOn` argument can also be repeated to target multiple
artifacts at once. Just repeat the `--whatDependsOn <artifact>` pattern. Note
that the artifact pattern follows the `org:artifact` convention. You can't
include a version as the utility will show you all usages of the `artifact`.
Also note that when using `--whatDependsOn` on usage of `--inverse` is forced
in order to make the tree appear in an inverted manner to more easily show you
where the dependency is coming from.

=== Excluding transitive dependencies

You can use the `.exclude` method on a dependency. It accepts  `organization` and `name` tuples, to be excluded.
Use the special name `*` to match all ``organization``s or ``name``s.

.Example: Exclude `fansi_2.12` library from transitive dependency set of `pprint`.
[source,scala]
----
def deps = Seq(
  mvn"com.lihaoyi::pprint:0.5.3".exclude("com.lihaoyi" -> "fansi_2.12")
)
----

You can also use `.excludeOrg` or `excludeName`:

There is also a short notation available:

.Example: Short notation to exclude `fansi_2.12` library from transitive dependency set of `pprint`.
[source,scala]
----
def deps = Seq(
  mvn"com.lihaoyi::pprint:0.5.3;exclude=com.lihaoyi:fansi_2.12"
)
----

.Example: Exclude all `com.lihaoyi` libraries from transitive dependency set of `pprint`.
[source,scala]
----
val deps = Seq(mvn"com.lihaoyi::pprint:0.5.3".excludeOrg("com.lihaoyi"))
----

Note: You can chain multiple exclusions with `exclude`, `excludeOrg`, and `excludeName`.

.Example: Excluding a library (fansi) by name from transitive dependency set of `pprint`.
[source,scala]
----
val deps = Seq(
  mvn"com.lihaoyi::pprint:0.5.3"
    .excludeName("fansi_2.12")
    .excludeName("sourcecode")
)
----

=== Forcing versions

You can use the `forceVersion` method to ensure the used version of a dependency is what you have declared.

* You declare a dependency `val deps = Seq(mvn"com.lihaoyi::fansi:0.2.14")`
* There is another dependency, `val deps = Seq(mvn"com.lihaoyi::PPrint:0.8.1")`
* PPrint 0.8.1 uses fansi 0.4.0, so it is a transitive dependency
* `mill show myModule.resolvedMvnDeps | grep "fansi"` should show fansi 0.4.0
* If you want to force to the older version (to prevent it being evicted, and replaced by 0.4.0) then you can use `val deps = Seq(mvn"com.lihaoyi::fansi:0.2.14".forceVersion())`
* `mill show myModule.resolvedMvnDeps | grep "fansi"` should show fansi 0.2.14

== Dependency management

Dependency management consists in listing dependencies whose versions we want to force. Having
a dependency in dependency management doesn't mean that this dependency will be fetched, only
that

* if it ends up being fetched transitively, its version will be forced to the one in dependency management

* if its version is empty in an `mvnDeps` section in Mill, the version from dependency management will be used

Dependency management also allows to add exclusions to dependencies, both explicit dependencies and
transitive ones.

Dependency management can be passed to Mill in two ways:

* via external Maven Bill of Material (BOM), like https://repo1.maven.org/maven2/com/google/cloud/libraries-bom/26.50.0/libraries-bom-26.50.0.pom[this one],
whose Maven coordinates are `com.google.cloud:libraries-bom:26.50.0`

* via the `depManagement` task, that allows to directly list dependencies whose versions we want to enforce

=== External Bill of Material (BOM)

include::partial$example/fundamentals/dependencies/1-external-bom.adoc[]

=== Managed Bill of Material (BOM)

include::partial$example/fundamentals/dependencies/2-managed.adoc[]


== Searching For Dependency Updates

include::partial$example/fundamentals/dependencies/3-search-updates.adoc[]
