33

I've recently been introduced to the concept of a dependency version lock file when reading about package managers like NPM, Yarn, Paket, Cargo, etc. My understanding is that it is a file that lists all direct and transitive dependencies along with their exact version number so subsequent builds are guaranteed to use an equivalent set of dependencies. This seems to be a desirable feature since many package managers have or are adopting the concept.

My questions are then:

  1. Why doesn't Maven or Gradle use a lock file? Or if they do, why haven't I seen it?

  2. What are the pros and cons of allowing version ranges in a package manager's dependency resolution strategy vs only allowing exact versions?

jmrah
  • 5,715
  • 3
  • 30
  • 37
  • 6
    If you define the versions in your pom file the dependency tree is always the same which means you don't need to define all transitive dependencies and you saved a lot of work. Except if you use versions ranges in Maven (that will result in non reproducible builds this is also true for all other build systems). – khmarbaise Jun 13 '17 at 12:34
  • 2
    Perhaps if you could elaborate or reword your comment. People seem to seem to think it's a helpful comment, but I'm having a hard time seeing how it answers my questions. – jmrah Jun 13 '17 at 14:37
  • 1
    Simple answer is: Maven, Gradle etc. have this already implemented not as an explicit file but based on the idea of a dependency tree which is iterated always the same way which means the tree has always the same versions which means in the end you don't need a file to define all transitive and non transitive dependencies with their versions (BTW: Maven had that concept of defining everything in a file 10 yeas ago in Maven 1.X)... – khmarbaise Jun 13 '17 at 18:44
  • Note that Node.js with ranges specified in package.json *does* result in reproducable builds as long as the lock file (package-lock.json) is retained and subsequent installs are performed with "nom ci" instead of "npm install". – Matt Welke Oct 07 '22 at 13:43
  • @khmarbaise This solution based on the assumption that none in the dependency chain will use a version range. Little problematic in real world – Hritik Apr 17 '23 at 03:52
  • I think Matt meant "npm ci". – Michael Rivera May 24 '23 at 02:53

2 Answers2

23
  1. Maven does not have a way of to achieve what you are asking for. Even if you set specific versions for your direct dependencies, which you should, your transitive dependencies can easily be unintentionally changed due to a seemingly unrelated change. For example, adding a dependency on a new library can give you an older version of an existing transitive dependency.

    What you need is to have a dependencyManagement section that lists all your direct and transitive dependencies. You will still not be able to detect if a transitive dependency is removed or added which is a feature that, for example, NPM provides. The problem with missing that is that all your dependencies are no longer in the dependencyManagement section. To detect those changes you could use something like dependency-lock-maven-plugin which I have written. Using it will also make it less important to have everything in a dependencyManagement section since changes in transitive dependencies will be detected.

    I would also recommend having https://maven.apache.org/enforcer/enforcer-rules/requireUpperBoundDeps.html in your build since Maven chooses the versions of the transitive dependencies that are closes in the tree and not, as you would expect, the highest version.

    I have seen many runtime problems caused by developers accidentally changing transitive dependencies.

    TL;DR: You do need something like a lock file in Maven, but it is not there due to historical ideological reasons.

  2. I would not recommend using version ranges since they make your build not reproducible. Neither does it behave as you would believe when it comes to transitive dependencies.

BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
Mikael Vandmo
  • 887
  • 8
  • 14
  • Is there a way we can exclude certain dependencies. For eg the Pom.xml has few dependencies listed which are not from the maven central but from the local artifactory that we have and those are produced by other builds. Is there something as exclude – abhishek phukan May 14 '19 at 02:47
  • Exlude from what exactly? dependeny-lock plugin or requireUpperBoundDeps? Or something else? :) If it is for dependency-lock plugin you can create an issue at GitHub with your use case and I can help you there. I am planning an ignore flag or similar, but maybe you can use version: use-mine? – Mikael Vandmo May 15 '19 at 12:33
  • You can exclude indirect dependencies with ``: https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html Of course you can "exclude" a direct dependency by deleting it from your ``. – Jason Young Nov 19 '19 at 18:47
  • @MikaelVandmo do you happen to have a source for the "due to historical ideological reasons" part? – hesxenon Oct 12 '22 at 12:42
  • @hesxenon I don't, sorry. IIRC it was some mailing list archives, or possibly Maven Jira Issue, where the conclusion was that current functionality was enough. – Mikael Vandmo Oct 14 '22 at 09:53
5

Dependency locking was a feature that achieved some maturity by Gradle 5.0: https://docs.gradle.org/current/userguide/dependency_locking.html

Gradle's implementation was inspired by the Nebula plugin: https://github.com/nebula-plugins/gradle-dependency-lock-plugin

Version ranges do work well, when used as input to whatever updates your locking mechanism. So, for Gradle, you can actually just target specific dependencies that will look to resolve version ranges you've specified for:

gradle classes --update-locks org.apache.commons:commons-lang3,org.slf4j:slf4j-api

Or, you can just say "go update all my deps":

gradle dependencies --write-locks

Specifying resolution strategies is also worth reviewing, if you're looking into automation: https://docs.gradle.org/current/userguide/dependency_resolution.html

Tristan Juricek
  • 1,804
  • 18
  • 20