Maven (and now Leiningen) provides a capability called "Dependency Management".
The idea is to provide a way to specify a version number for common library
dependencies in a single location, and re-use those version numbers from other
discrete maven/lein projects. This makes it easy to, e.g., update your clj-time
dependency across a large number of projects without having to be mindful
of every common dependency version across all of your libraries.
When using :pedantic? :abort
in your projects, to ensure that you are producing
a consistent and predictable build, it can be very cumbersome to play the
"dependency version whack-a-mole" game that arises whenever an upstream library
bumps a version of one of its dependencies. :managed-dependencies
can help
alleviate this issue by allowing you to keep the dependency version numbers
centralized.
The :managed-dependencies
section of your project.clj
file is just like the
regular :dependencies
section, with two exceptions:
- It does not actually introduce any dependencies to your project. It only says, "hey leiningen, if you encounter one of these dependencies later, here are the versions that you should fall back to if the version numbers aren't explicitly specified."
- It allows the version number to be omitted from the
:dependencies
section, for any artifact that you've listed in your:managed-dependencies
section.
Here's an example:
(defproject superfun/happyslide "1.0.0-SNAPSHOT"
:description "A Clojure project with managed dependencies"
:min-lein-version "2.7.0"
:managed-dependencies [[clj-time "0.12.0"]
[me.raynes/fs "1.4.6"]
[ring/ring-codec "1.0.1"]]
:dependencies [[clj-time]
[me.raynes/fs]])
In the example above, the final, resolved project will end up using the specified
versions of clj-time
and me.raynes/fs
. It will not have an actual dependency
on ring/ring-codec
at all, since that is not mentioned in the "real" :dependencies
section.
This feature is not all that useful on its own, because in the example above,
we're specifying the :managed-dependencies
and :dependencies
sections right
alongside one another, and you could just as easily include the version numbers
directly in the :dependencies
section. The feature becomes more powerful
when your build workflow includes some other way of sharing the :managed-dependencies
section across multiple projects.
The managed dependencies support in leiningen does work with modifiers such as
:exclusions
and :classifier
. There are two legal syntaxes; you can explicitly
specify a nil
for the version string, or you can simply omit the version string:
(defproject superfun/happyslide "1.0.0-SNAPSHOT"
:description "A Clojure project with managed dependencies"
:min-lein-version "2.7.0"
:managed-dependencies [[clj-time "0.12.0"]]
:dependencies [[clj-time :exclusions [foo]]])
or
(defproject superfun/happyslide "1.0.0-SNAPSHOT"
:description "A Clojure project with managed dependencies"
:min-lein-version "2.7.0"
:managed-dependencies [[clj-time "0.12.0"]]
:dependencies [[clj-time nil :exclusions [foo]]])
Note that :classifier
is actually a part of the maven coordinates for an
artifact, so for :classifier
artifacts you will need to specify the :classifier
value in both the :managed-dependencies
and the normal :dependencies
section:
(defproject superfun/happyslide "1.0.0-SNAPSHOT"
:description "A Clojure project with managed dependencies"
:min-lein-version "2.7.0"
:managed-dependencies [[commons-math "1.2" :classifier "sources"]]
:dependencies [[commons-math :classifier "sources"]])
One way of leveraging :managed-dependencies
across multiple projects is to use
the lein-parent
plugin. This plugin
will allow you to define a single "parent" project that is inherited by multiple
"child" projects; e.g.:
(defproject superfun/myparent "1.0.0"
:managed-dependencies [[clj-time "0.12.0"]
[me.raynes/fs "1.4.6"]
[ring/ring-codec "1.0.1"]])
(defproject superfun/kid-a "1.0.0-SNAPSHOT"
:parent-project [:coords [superfun/myparent "1.0.0"]
:inherit [:managed-dependencies]]
:dependencies [[clj-time]
[me.raynes/fs]])
(defproject superfun/kid-b "1.0.0-SNAPSHOT"
:parent-project [:coords [superfun/myparent "1.0.0"]
:inherit [:managed-dependencies]]
:dependencies [[clj-time]
[ring/ring-codec]])
In this example, we've consolidated the task of managing common version dependencies in the parent project, and defined two child projects that will inherit those dependency versions from the parent without needing to specify them explicitly.
This makes it easier to ensure that all of your projects are using the same versions of your common dependencies, which can help make sure that your uberjar builds are more predictable and repeatable.
Since the defproject
form is a macro, it would be possible to write other plugins
that generated the value for a :managed-dependencies
section dynamically. That
could provide other useful ways to take advantage of the :managed-dependencies
functionality without needing to explicitly populate that section in all of your
project.clj
files.
It is likely that the functionality provided by the lein-parent
plugin may integrated
into the leiningen core in a future release; for now we have added only the :managed-dependencies
functionality because it is necessary in order for the plugin to leverage it. We
will be experimenting with different ideas for implementation / API in plugins and
making sure that we find an API that works well before submitting for inclusion
into core leiningen.