Do not use version ranges in project.clj
Build tools like lein and maven make it very easy to use libraries in clojure. Just go to clojars or central and find the one you want. However, there are several situations where dependency resolution does not go as planned. A lot of these are a result of using version ranges for a dependency.
I’m going to pick on Christophe Grand and
use his regex library for some
examples of what can happen. It contains a version range for
clojure of [1.2.0,)
. This means it wants version 1.2.0 or later.
Version ranges check a pom for every version
Given this project.clj:
(defproject tester "0.0.1"
:dependencies [[org.clojure/clojure "1.4.0"]
[net.cgrand/regex "1.1.0"]])
Running lein deps
produces:
Retrieving org/clojure/clojure/1.3.0/clojure-1.3.0.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/sonatype/oss/oss-parent/5/oss-parent-5.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.2.1/clojure-1.2.1.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0/clojure-1.3.0.jar (3022k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0/clojure-1.4.0.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/maven-metadata.xml (1k)
from http://repo1.maven.org/maven2/
Could not find metadata org.clojure:clojure/maven-metadata.xml in clojars (https://clojars.org/repo/)
Retrieving org/clojure/clojure/1.2.0/clojure-1.2.0.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-alpha5/clojure-1.3.0-alpha5.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-alpha6/clojure-1.3.0-alpha6.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-alpha7/clojure-1.3.0-alpha7.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-alpha8/clojure-1.3.0-alpha8.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-beta1/clojure-1.3.0-beta1.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-beta2/clojure-1.3.0-beta2.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-beta3/clojure-1.3.0-beta3.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.3.0-RC0/clojure-1.3.0-RC0.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-alpha1/clojure-1.4.0-alpha1.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-alpha2/clojure-1.4.0-alpha2.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-alpha3/clojure-1.4.0-alpha3.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-alpha4/clojure-1.4.0-alpha4.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-alpha5/clojure-1.4.0-alpha5.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta1/clojure-1.4.0-beta1.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta2/clojure-1.4.0-beta2.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta3/clojure-1.4.0-beta3.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta4/clojure-1.4.0-beta4.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta5/clojure-1.4.0-beta5.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta6/clojure-1.4.0-beta6.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0-beta7/clojure-1.4.0-beta7.pom (1k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.5.0-alpha1/clojure-1.5.0-alpha1.pom (2k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.5.0-alpha2/clojure-1.5.0-alpha2.pom (2k)
from http://repo1.maven.org/maven2/
Retrieving org/clojure/clojure/1.4.0/clojure-1.4.0.jar (3045k)
from http://repo1.maven.org/maven2/
This produces a large number of requests for poms. Unfortunately Aether, the library underneath lein2/maven3, cannot merge the two dependencies on clojure without downloading everything. This is a rather slight annoyance, but it makes clean builds, such as on travis-ci, very noisy.
Version ranges cause surprises
(defproject tester "0.0.1"
:dependencies [[org.clojure/clojure "1.1.0"]
[net.cgrand/regex "1.1.0"]])
lein deps :tree
shows:
[net.cgrand/regex "1.1.0"]
[org.clojure/clojure "1.5.0-alpha2"]
Here is a project with a direct dependency on clojure 1.1.0. This is
outside of the range desired by net.cgrand/regex. Aether lets version
ranges take priority over normal “soft” dependencies, and the project
uses 1.5.0-alpha2 instead. Normally a direct dependency will take
priority over any transitive dependencies. This situation causes a
surprise for the user, who would not be expecting a transitive version
range. The user has to go explicitly add an :exclusion
to get the
desired version of clojure.
Version ranges are hard to track down
(defproject tester "0.0.1"
:dependencies [[org.clojure/clojure "1.1.0"]
[serializable-fn "1.1.2"]
[net.cgrand/regex "1.1.0"]])
lein deps :tree
shows:
[net.cgrand/regex "1.1.0"]
[serializable-fn "1.1.2"]
[org.clojure/clojure "1.3.0"]
Here the project has another dependency – serializable-fn. It wants
clojure 1.3.0. The dependency tree shows that clojure 1.3.0 got
chosen because of serializable-fn. However, this only happens because
net.cgrand/regex has a version range. This association of why a
dependency was downloaded does not help the user determine where an
:exclusion
should go. Using the data they would place an :exclusion
on serializable-fn, but that would just lead to getting 1.5.0-alpha2
as above.
Version ranges cause unrepeatable builds
Open ended version ranges also create unrepeatable builds. Since net.cgrand/regex uses the latest above 1.2.0, building a version now, versus building a version a week from now may result in different clojure versions running the test suite.
Do not use version ranges
Do not use version ranges. There have been several instances of people asking for help on #clojure or #leiningen and a version range being the underlying issue. They cause problems for other developers and users of your libraries.