Dependency Manager

Modular software is composed of modules that could depend on other modules. Source: Elena 2017.
Modular software is composed of modules that could depend on other modules. Source: Elena 2017.

A good practice in software design is to build software from smaller, single-purpose modules that expose well-defined interfaces. This is also in the spirit of software reuse. With the widespread adoption of open source software, often our own applications or modules depend on those written by others. Thus, to build our software we need to bring in all parts on which it depends, including language libraries and remote third-party modules.

But it's not trivial to ensure that we have all necessary dependencies, particularly when dependencies themselves depend on others. This is why we need a Dependency Manager, often invoked during the software build process. A useful definition states that,

Dependency management is a technique for declaring, resolving and using dependencies required by the project in an automated fashion.

Discussion

  • Isn't dependency manager same as a package manager?

    No. A package manager operates at the system level and most often deals with binaries. It's role is to install libraries, tools, and other applications. It affects the entire system and not just one project. Because of this, it requires root access. Examples of package managers are apt-get (Linux), Brew (MacOS), and Chocolatey (Windows).

    A dependency manager operates at source code level. It's role is to setup the right dependencies for a particular application. It therefore implies that a dependency manager is something used by developers, not users or system admin.

    Thus, a dependency manager is project specific. It helps in easy management of project dependencies. It has to ensure that same source code is used across environments, for example, when moving from Windows developer machine to Linux production system. There should be end-to-end visibility of dependencies. A dependency manager is essential for Continuous Integration. A good dependency manager should also make it easy to publish new packages.

  • What are the main components of a dependency management system?

    A dependency management system typically has the following components:

    • Module: A software artefact, that we wish to use in our project. Depending on the language, modules are also called packages or libraries. Modules come with metadata to indicate dependencies.
    • Manifest: Usually a file that specifies immediate/direct dependencies for our project. It specifies intent, such as, saying that we desire "version 2.3 or above of module A".
    • Lock: Usually a file that captures a particular realization of the developer's intent. To extend the above example, version 2.3.2 of module A might be used.
    • Repository: A place where modules are stored. Often repos are remote and accessed over the internet. Dependency manager usually knows the location of the repo but location can also be specified in the manifest file.
    • Dependency Constraint: Usually in the manifest file, this is a constraint about versions of a module that are allowed for this project.
    • Resolution Rule: Rules are applied by the dependency manager to arrive at a suitable module version. Selecting the right version is called Dependency Resolution.
  • What's the usual Project Dependency Management (PDM) pipeline?
    A PDM pipeline involves four states and their forward transitions. Source: Boyer 2016.
    A PDM pipeline involves four states and their forward transitions. Source: Boyer 2016.

    Dependency managers bring together all necessary project dependencies before passing them to the compiler or interpreter. In this sense, their work is somewhat a preprocessing phase before the actual build happens.

    Dependency managers start by reading the manifest file, in which direct dependencies are noted. They then read the metadata of these dependencies from their repositories to figure out the next level of dependencies. In other cases, they may download the dependencies right away and then process their dependencies. Either way, all dependencies must be downloaded and installed.

    Exact installed versions of dependencies are recorded in a lock file. While the manifest file is important for a developer, the lock file has greater importance for operations folks. The lock file makes the project's dependencies reproducible in any environment.

  • What is transitive dependency?

    Transitivity is a concept in logic and mathematics. In the context of software dependencies, it can be explained as follows. If module A depends on module B, which in turn depends on module C, we can say that module A also depends on module C. Many levels of dependencies produce what's called a Dependency Graph.

    When a module appears multiple times in the graph, with different version numbers, the dependency manager must take a decision about which version to use. A similar decision must also be made when dynamic version numbers are specified. This selection process is called Dependency Resolution.

    A good dependency manager will not only resolve direct dependencies as mentioned in your project's top-level manifest file but also resolve all transitive dependencies. It should be able to handle any level of nesting. Typically, cyclic dependencies will cause problems.

  • Could you mention some rules used in Dependency Resolution?

    We mention a few sample rules below for specific dependency managers. However, similar rules are likely to exist across many dependency managers.

    In Gradle, after taking into account all transitive dependencies and constraints, the highest suitable version is selected. If there's a conflict, an error is thrown. You can also use dependency configurations and make manual adjustments. Maven uses the nearest version but if conflicts are at the same depth, the first one wins based on the order of declaration.

    NuGet uses four main rules: lowest applicable version, floating versions, nearest-wins, and cousin dependencies. This rule is applied with a warning since the package at a deeper level might get downgraded to an older version.

    NPM actually builds a dependency tree, where different versions of dependent packages can be installed in different branches of the tree. However, using what's called "peer dependencies", NPM allows us to enforce same versions where desired.

  • What's the purpose of lock files used by dependency managers?
    The auto-generated Gopkg.lock is used in Go. Source: Yuen 2018.
    The auto-generated Gopkg.lock is used in Go. Source: Yuen 2018.

    If dependencies are specified in terms of exact versions, anyone else trying to build your project in any other environment will use the same versions.

    When dynamic version numbers are specified in manifest files, such as a range of versions, the version used may depend on the environment or the latest version available in the repository at build time. Dynamic version numbers are good because they allow us to bring in latest features/patches of dependent modules into our project. However, this causes uncertainty for continuous integration and production release. To solve this, versions used are recorded in a lock file. This lock file must be committed to version control so that anyone else using the project is guaranteed to use the same versions.

    What if you use only exact versions? Is lock file required in such a case? Yes. Even if direct dependencies are exact, transitive dependencies might be specified in a non-exact manner. Therefore, a lock file is essential in all cases.

    Lock file is sometimes called "a manifestation of the manifest".

  • What should I look for when choosing a dependency manager?

    The purpose of a dependency manager is to allow developers create a consistent and reproducible set of dependencies for their projects. It should be easy to use, have a well-defined behaviour and hooks for customizations. Some essential features to compare are the following:

    • Organization: For example, Yarn uses flat mode while NPM builds a dependency tree.
    • Semantic Versioning: Has provision to specify a range of versions.
    • Dependency Locking: Uses lock files to reproduce exact versions.
    • Dependency Resolution: Handles transitive dependencies. Resolves conflict with a well-defined set of rules.
    • Multiple Configurations: Allows different versions of the same package. This is enabled by defining different configurations or groups, such as build vs testing.
    • Performance: Caches downloaded packages for faster updates. Parallel downloads.
    • Security: Does integrity checks via package checksums.
    • Customizations: Dependencies can be made optional or versions can be manually specified. Can be configured to fetch from any repository. Supports offline installation.
    • Support: Has good community support and adoption, with frequent updates.
  • What are some best practices that developers can adopt to better manage project dependencies?

    Developers need to version control not only the source code but also the manifest file and the lock file. In particular, always version control your lock file if you're building an application. If you're building a library, it's a good idea to do this as well. However, if you wish to build your library with the latest versions of its dependencies, it's recommended to use a second continuous integration build.

    If you're making a Java library, let it not depend on frameworks such as Spring. Changes to frameworks can cause "dependency hell" for users of your library. Don't ship fat JAR files, files that include all dependencies. Only depend on what you need.

    Finally, it's best to avoid dependencies if you can implement them yourself. The argument is that this will keep your application lean and secure. Check if there's a native library to do the equivalent. Look for those with less transitive dependencies. Check memory usage and load time. Use fewer high-level interfaces. Prefer low-level function calls. Use abstractions. Test regularly on target environments.

  • Could you give examples of dependency managers in various languages?

    A few examples of dependency managers are listed below in the format name: (language, default repo, default manifest, default lock):

    • Composer: (PHP, Packagist, composer.json, composer.lock)
    • Gradle: (Java, Maven Central, build.gradle, gradle/dependency-locks/*.lockfile)
    • Node Package Manager (NPM): (NodeJS, NPM, package.json, package-lock.json)
    • Yarn: (NodeJS, NPM, package.json, yarn.lock)
    • Bundler: (Ruby, RubyGems, Gemfile, Gemfile.lock)
    • dep: (Go, -, Gopkg.toml, Gopkg.lock)
    • Cargo: (Rust, Crates.io, Cargo.toml, Cargo.lock)
    • Pipenv: (Python, PyPI, Pipfile, Pipfile.lock)

    In many languages, newer, better dependency managers are being preferred over older ones: Gradle over Maven and Any-Ivy (Java); Yarn over NPM and Bower (JavaScript); Bundler over RubyGems (Ruby); Composer over PEAR (PHP); dep over godep, glide and govendor (Go). In some cases, such as dep in Go, there's no default repository. The manifest file includes information on package sources.

    Gradle did not initially support locking but locking could be implemented using Netflix's Nebula Plugins project. This plugin inspired Gradle to support locking. While build.gradle specifies dependencies at the top level, module metadata files (.module, .pom or ivy.xml) must also be read.

  • In which languages are dependencies more often used?
    A look of dependency counts in some popular languages. Source: Szulik 2018.
    A look of dependency counts in some popular languages. Source: Szulik 2018.

    One study of 6.9 million versions of open source packages across 14 repository showed that NPM and CPAN have higher dependency counts; all others have 5 or less dependencies, on average. NPM and CPAN also have a larger spread. In general, higher the count, more complex is the dependency graph. While using third-party packages saves development time, there's also risks in terms of security, licensing and performance. Application health depends on all dependencies that we bring in.

    Each repository contains packages of a particular language: NPM (JavaScript), CPAN (Perl), Rubygems (Ruby), Maven (Java), Packagist (PHP), Cargo (Rust), Atom (JavaScript), Pub (Dart), CRAN (R), PyPI (Python), NuGet (.NET/C#/F#/Visual Basic), Elm (Elm), Hex (Erlang), Dub (D). Libraries.io tracks open source packages across different repositories. Since Go language doesn't have a default repository, Go Search provides a handy search functionality.

Milestones

Mar
2002

Based on Project Object Model (POM), Maven 1.0-beta-2 is released for managing Java projects. Maven 1.x reaches end of life in 2014. As a rewrite of Maven 1.x, with extra functionality, Maven 2.0 is released in October 2005. Popular Maven plugins are also converted to the new version. Maven 2.0 can manage transitive dependencies.

Jul
2009

For managing dependencies for Java, including Android projects, Gradle v0.7 is released. Version v1.0 follows in June 2012. Version v4.8 is released in June 2018. Version v4.8 includes support for dependency locking.

Sep
2010

As a dependency manager for Ruby, Bundler V1.0.0 is released. Earliest release can be traced to 2009. Ruby community is credited with introducing the concept of dependency locking.

Oct
2010
Maven Dependency Diagrams from JetBrains' IntelliJ IDEA can flag version conflicts. Source: Bulenkov 2010.
Maven Dependency Diagrams from JetBrains' IntelliJ IDEA can flag version conflicts. Source: Bulenkov 2010.

While being compatible with Maven 2.x projects and plugins, Maven 3.0 is released. Among other things, it isolates project and plugin dependencies, and does better dependency resolution.

Mar
2011

Version v1.0.0rc9 of Node Package Manager (NPM) for NodeJS/JavaScript is released. Release v0.0.1 can be traced to January 2010.

Sep
2011

As a dependency manager for Xcode projects (Objective-C or Swift), CocoaPods 0.0.1 is released. Version 1.0.0 is released in May 2016. An alternative to CocoaPods is Carthage that trades configuration for convention.

Jul
2013

Version 1.0.0-alpha1 of Composer is released as a dependency manager for PHP. Version 1.0.0 is released in April 2016. Composer has been inspired by Node's NPM and Ruby's Bundler. Composer uses Packagist as its default repository but for internal/Intranet use, Satis is an alternative.

Sep
2014

To overcome the limitations of NuGet, Paket is released as a dependency manager for .NET. NuGet can be seen as a package manager. It doesn't handle transitive dependencies and selects the latest version when there are conflicts. Paket solves these problems of NuGet.

Aug
2015

With initial code commits from March 2014, Cargo version 0.4.0 is released as a dependency manager for Rust. As of June 2018, version 0.28.0 is the latest available.

Jun
2016
Steps taken by Yarn for managing dependencies. Source: Hanif 2017.
Steps taken by Yarn for managing dependencies. Source: Hanif 2017.

Version 0.2.0 of Yarn, is released for dependency management for NodeJS/Javascript. Version 1.0.0 is released in September 2017. For performance, Yarn caches downloaded packages and does downloads concurrently. It uses checksums to verify integrity of downloaded packages before installing them. It's inspired by Bundler, Cargo and NPM.

May
2017

Version 5.0.0 of NPM is released for NodeJS/JavaScript. This version introduces dependency locking. In July, version 5.1.0 is released to fix a problem that ignored changes to packages.json when lock file is present.

Jul
2017

For managing dependencies for Go, dep v0.2.0 is released.

Sample Code

  • // Source: https://packagist.org/
    // File: composer.json (for PHP Composer)
    // Example showing package information along with dependencies
    // :dependencies are specified within "require"
    // :version numbers use semantic versioning
    {
        "name": "your-vendor-name/package-name",
        "description": "A short description of what your package does",
        "require": {
            "php": "^5.3.3 || ^7.0",
            "vendor/package": "1.3.2",
            "vendor/package2": "1.*",
            "vendor/package3": "^2.0.3"
        }
    }
     

References

  1. Apache Jakarta Project. 2011. "News & Status - 2002." December 21. Accessed 2018-06-27.
  2. Apache Maven Docs. 2018. "Maven Releases History." Apache Maven Project, June 23. Accessed 2018-06-27.
  3. Apache Maven Project. 2018. "Introduction to the Dependency Mechanism." Apache Maven Project, June 23. Accessed 2018-06-26.
  4. Arko, André. 2015. "How Bundler Works: A History of Ruby Dependency Management." Cloud City Development Blog, July 10. Accessed 2018-06-27.
  5. Atkinson, Sam. 2016. "Kill Your Dependencies: Java/Maven Edition." DZone, February 23. Accessed 2018-06-27.
  6. Boyer, Sam. 2016. "So you want to write a package manager." Medium, February 12. Accessed 2018-06-26.
  7. Brigance, Yuri. 2017. "Choosing the Right iOS Dependency Manager." AIM Consulting Group, October 23. Accessed 2018-06-27.
  8. Buckaroo. 2017. "Approaches to C++ Dependency Management, or Why We Built Buckaroo." Hackernoon, July 17. Accessed 2018-06-26.
  9. Bulenkov, Konstantin. 2010. "Maven Dependencies Diagram." IntelliJ IDEA Blog, May 31. Accessed 2018-06-26.
  10. Bundler. 2018. "Bundler Homepage." Accessed 2018-06-26.
  11. Bundler GitHub. 2018. "bundler/bundler." Accessed 2018-06-27.
  12. CocoaPods GitHub. 2018. "CocoaPods/CocoaPods." Accessed 2018-06-27.
  13. Composer Docs. 2018. "Getting Started (Introduction)." v1.6.5. Accessed 2018-06-27.
  14. Composer GitHub. 2018. "composer/composer." Accessed 2018-06-27.
  15. Corentin. 2018. "Accio Dependency Manager." Medium, March 03. Accessed 2018-06-26.
  16. Crouch, Steve. 2018. "Defending your code against dependency problems." Software Sustainability Institute. Accessed 2018-06-26.
  17. Elena. 2017. "Modularization and dependency management: three steps to better code." Smart Puffin Blog, May 23. Accessed 2018-06-26.
  18. FSharp Projects. 2018a. "Release Notes." Paket, June 21. Accessed 2018-06-27.
  19. FSharp Projects. 2018b. "FAQ — Frequently Asked Questions." Paket, June 21. Accessed 2018-06-27.
  20. Gradle. 2018. "Releases." Accessed 2018-06-27.
  21. Gradle Docs. 2018a. "Introduction to Dependency Management." Gradle Docs, V4.8.1, May 23. Accessed 2018-06-26.
  22. Gradle Docs. 2018b. "Dependency Management Terminology." Gradle Docs, V4.8.1, May 23. Accessed 2018-06-27.
  23. Gradle Docs. 2018c. "Dependency Locking." Gradle Docs, V4.8.1, May 23. Accessed 2018-06-27.
  24. Gradle Docs. 2018d. "Gradle Release Notes, V4.8." Accessed 2018-06-27.
  25. Gradle Docs. 2018e. "Managing Transitive Dependencies." Gradle Docs, V4.8.1, May 23. Accessed 2018-06-27.
  26. Hammar, Anders. 2011. "Top Ten Reasons to Move to Maven 3." Sonatype Blog, February 24. Accessed 2018-06-27.
  27. Hanif, Owais. 2017. "Yarn a better package manager." OH786, February 09. Accessed 2018-06-26.
  28. Katz, Yehuda. 2016. "Cargo: predictable dependency management." The Rust Programming Language Blog, May 05. Accessed 2018-06-26.
  29. King, Alexis. 2016. "Understanding the npm dependency model." Blog on GitHub.io, August 24. Accessed 2018-06-26.
  30. Kunal. 2015. "Three Core Problems of Dependency Management." Mingle, ThoughtWorks, August 06. Accessed 2018-06-26.
  31. Kutner, Joe. 2017. "Locking Dependency Versions in Gradle." Blog on GitHub, March 29. Accessed 2018-06-27.
  32. Manohar, Tejas. 2017. "The State of Go Dependency Management." Hackernoon, Medium, February 27. Accessed 2018-06-27.
  33. Matt, Seun. 2017. "What are Dependency Managers?" Prodsters Blog, Medium, April 26. Accessed 2018-06-26.
  34. Microsoft Docs. 2017. "How NuGet resolves package dependencies." NuGet, August 18. Accessed 2018-06-27.
  35. Murzea, Radu and Cătălin Criste. 2016. "Composer and Packagist – PHP’s Superstars." Today Software Magazine, vol. 44, February. Accessed 2018-06-26.
  36. Muschko, Benjamin. 2014. "Why Build Your Java Projects with Gradle Rather than Ant or Maven?" Dr. Dobb's, July 08. Accessed 2018-06-26.
  37. NPM Docs. 2018. "package-lock.json." V6.1.0, May 24. Accessed 2018-06-26.
  38. Oloruntoba, Samuel. 2016. "Yarn Package Manager: An Improvement over npm." Scotch.io, October 14. Accessed 2018-06-27.
  39. Oracle Docs. 2015. "Oracle® Fusion Middleware Developing Applications Using Continuous Integration." Middleware, Oracle Docs. Accessed 2018-06-26.
  40. Ostruszka, Michał. 2018. "It depends. The art of dependency management in Javascript." SoftwareMill Tech Blog, February 06. Accessed 2018-06-26.
  41. Perham, Mike. 2016. "Kill Your Dependencies." February 09. Accessed 2018-06-27.
  42. PyPA GitHub. 2018. "pypa/pipenv." Accessed 2018-06-26.
  43. Quigley, James. 2017. "Everything You Wanted To Know About package-lock.json But Were Too Afraid To Ask." Medium, August 12. Accessed 2018-06-27.
  44. Rust Docs. 2018. "The Cargo Book." Rust 1.27.0, 3eda71b00. Accessed 2018-06-26.
  45. Szulik, Keenan. 2018. "Dependency management and your software health." Tidelift Blog, February 08. Accessed 2018-06-26.
  46. Vocabulary. 2018. "transitivity." Vocabulary.com Dictionary. Accessed 2018-06-26.
  47. Yarn Docs. 2018. "Working with version control." June 05. Accessed 2018-06-26.
  48. Yuen, Ying Kit. 2018. "An intro to dep: How to manage your Golang project dependencies." freeCodeCamp, February 03. Accessed 2018-06-26.
  49. golang GitHub. 2018. "golang/dep." Accessed 2018-06-27.
  50. npm GitHub. 2018. "npm/npm." Accessed 2018-06-27.
  51. rust-lang GitHub. 2018. "rust-lang/cargo." Accessed 2018-06-27.
  52. yarnpkg GitHub. 2018. "yarnpkg/yarn." Accessed 2018-06-27.

Further Reading

  1. Boyer, Sam. 2016. "So you want to write a package manager." Medium, February 12. Accessed 2018-06-26.
  2. Kunal. 2015. "Why You Have Dependencies, and How to Resolve Them." Mingle, ThoughtWorks, November 20. Accessed 2018-06-26.
  3. Krutisch, Jan. 2017. "A brief history of dependency management." Depfu Blog, March 22. Accessed 2018-06-26.
  4. Gradle Docs. 2018a. "Introduction to Dependency Management." Gradle Docs, V4.8.1, May 23. Accessed 2018-06-26.
  5. Arko, André. 2015. "How Bundler Works: A History of Ruby Dependency Management." Cloud City Development Blog, July 10. Accessed 2018-06-27.
  6. Kernfeld, Paul. 2015. "Best Practices for Python Dependency Management." Knewton, September 28. Accessed 2018-06-27.

Article Stats

Author-wise Stats for Article Edits

Author
No. of Edits
No. of Chats
DevCoins
2
0
3616
1
0
27
1
0
5
2274
Words
2
Likes
30K
Hits

Cite As

Devopedia. 2022. "Dependency Manager." Version 4, February 15. Accessed 2024-06-25. https://devopedia.org/dependency-manager
Contributed by
3 authors


Last updated on
2022-02-15 11:48:21