This is the companion to Categorizing Package Registries, focusing on the client side: how package managers resolve dependencies, track versions, run build code, and declare dependencies. The data is also available as CSV. There are gaps; contributions welcome.
Each package manager combines these choices differently. Cargo uses backtracking resolution, generates lockfiles, allows build hooks via build.rs, and uses TOML manifests. Go uses minimal version selection, achieves reproducibility without lockfiles, forbids hooks entirely, and embeds dependencies in go.mod. npm uses deduplication with nesting, generates lockfiles, allows postinstall hooks, and uses JSON. The particular combination shapes the developer experience more than any single choice.
Resolution algorithms
How does the package manager decide which versions to install? The ecosyste.ms resolver documentation covers the major algorithm families.
SAT solving treats resolution as a satisfiability problem. Can find solutions when they exist and prove when they don’t, but computationally expensive. PubGrub is a variant that produces better error messages by tracking why versions were excluded.
Backtracking tries versions in order and backs up when conflicts arise.
- pip
- Cargo
- Cabal4
ASP solving uses answer set programming for complex constraint solving.
- Spack5
Minimal version selection picks the oldest version that satisfies constraints.
- Go modules
- vcpkg6
Deduplication with nesting installs multiple versions when packages need incompatible versions.
- npm
- Yarn
- pnpm
Version mediation lets build systems pick winners using different strategies.
Molinillo is a backtracking solver with heuristics tuned for Ruby’s ecosystem.
- Bundler
- RubyGems
- CocoaPods
System package resolution handles system-level constraints like file conflicts and provides/requires relationships.
- apt11
- pacman
- apk
- Portage
- FreeBSD ports
- pkgsrc
Single version per formula with topological sort for dependency ordering.
- Homebrew
Explicit dependencies with no version resolution needed.
- Nix
- Guix
Lockfiles and reproducibility
Can you reproduce the same install later?
Generates lockfiles to record exact versions resolved. Committed to version control.
- Bundler
- npm
- Yarn
- pnpm
- Cargo
- Poetry
- uv
- pdm
- Composer
- Mix
- pub
- Swift Package Manager
- Elm
- Cabal12
- Stack
- Spack
- Homebrew
- NuGet
- Julia
- Conan
- dub
- CocoaPods
- opam
Deterministic resolution through algorithms that produce stable results without needing a lockfile to pin versions.
- Go modules13
No native lockfile means resolution happens fresh each time, or build systems handle pinning.
Content-addressed packages are identified by input hash. Reproducibility without traditional lockfiles.
- Nix
- Guix
Build hooks
Can packages run code during installation?
Hooks allowed let packages execute scripts during install, build, or publish.
- npm17
- Yarn
- pip18
- Composer19
- RubyGems20
- Maven21
- Gradle
- Cargo22
- CocoaPods
- Homebrew
- Nix23
- apt/dpkg24
- RPM/DNF
Hooks restricted allow some build-time execution with limitations.
No hooks by design.
- Go
- Elm
- Swift Package Manager
- pip28
Manifest format
How are dependencies declared?
TOML
- Cargo
- Poetry
- uv
- Julia
- pdm
JSON
- npm
- Composer
- Deno
- Elm
- dub
- vcpkg
YAML
- Conda
- pub
- Homebrew
- GitHub Actions
- Helm
- pnpm
Host language
- Bundler (Ruby)
- CocoaPods (Ruby)
- Gradle (Groovy/Kotlin)
- sbt (Scala)
- Mix (Elixir)
- Swift Package Manager (Swift)
- Leiningen (Clojure)
- Nix
XML
- Maven
- NuGet
- Ivy
Custom format
- Go
- Cabal
- CPAN
- CRAN
- pip
-
Uses external CUDF solvers. ↩
-
Modular solver with backjumping. ↩
-
Microsoft explicitly documents vcpkg’s minimal version selection algorithm. ↩
-
Nearest definition wins. ↩
-
Highest version wins. ↩
-
Lowest applicable version. ↩
-
Clojars uses Maven’s resolution algorithm since it’s a Maven-compatible repository. ↩
-
Scoring with immediate resolution. ↩
-
The
cabal freezecommand generates a freeze file pinning versions. ↩ -
go.sum exists but contains checksums for verification, not version pins. MVS means the same go.mod always resolves to the same versions. ↩
-
pip-tools, Poetry, and uv provide lockfile functionality for pip. ↩
-
Cabal can generate freeze files, but Hackage itself doesn’t require them. ↩
-
conda-lock is a separate tool that adds lockfile support. ↩
-
postinstall scripts run after package installation. ↩
-
Historically via setup.py; modern PEP 517 builds use wheel build backends, which still execute arbitrary code at build time. ↩
-
Composer scripts can run at various lifecycle points. ↩
-
Native extensions compile C code during installation. ↩
-
Maven plugins execute during build phases. ↩
-
build.rs scripts run at compile time, typically for native code compilation. ↩
-
Build hooks run in a sandboxed environment. ↩
-
System packages do have maintainer scripts that run as root, but those are part of the distribution’s build pipeline, not arbitrary user-space package hooks. ↩
-
Scripts are disabled by default; must be explicitly enabled. ↩
-
Lifecycle scripts are disabled by default. ↩
-
Requires explicit permission flags for network, file system, and subprocess access. ↩
-
Wheel format explicitly forbids install-time code execution; sdist still allows setup.py hooks. ↩