├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Package Manager Dependency Resolution 2 | 3 | A reference for dependency resolution algorithms and strategies across different package managers. 4 | 5 | Background: [Package manager](https://en.wikipedia.org/wiki/Package_manager) and [Dependency hell](https://en.wikipedia.org/wiki/Dependency_hell) (Wikipedia). For academic treatment, see [Dependency Solving Is Still Hard, but We Are Getting Better at It](https://arxiv.org/abs/2011.07851) (Abate et al., 2020). 6 | 7 | ## Algorithm Categories 8 | 9 | Package managers generally fall into a few algorithmic families. 10 | 11 | Some registries only list one version per package in their index (APT, DNF, Pacman, Homebrew, Alpine), which simplifies resolution since there's no version selection. Others (npm, PyPI, RubyGems, crates.io, Maven Central) list all historical versions, requiring the resolver to choose among candidates. 12 | 13 | Language package registries rarely remove old versions or packages. System package repositories are more curated, partly to avoid conflicts and unresolvable dependency trees. 14 | 15 | | Algorithm Family | Description | Package Managers | 16 | |------------------|-------------|------------------| 17 | | **SAT Solving** | Translates dependencies to boolean satisfiability | Composer, DNF/Zypper/Conda (libsolv), Eclipse P2 (Sat4j), opam (via CUDF) | 18 | | **ASP** | Answer Set Programming for optimization | Spack | 19 | | **PubGrub** | Conflict-driven clause learning with good error messages | Dart pub, Poetry, uv, SwiftPM | 20 | | **Molinillo** | Backtracking with forward checking | Bundler, CocoaPods, RubyGems | 21 | | **Backtracking** | Try versions, backtrack on conflict | pip, Cargo, Cabal, ansible-galaxy (collections) | 22 | | **Minimal Version Selection** | Always use minimum satisfying version | Go modules | 23 | | **Deduplication with nesting** | Deduplicate where possible, nest on conflict | npm, Yarn, pnpm, Bun | 24 | | **Version Mediation** | Pick based on graph position or declaration order | Maven, Gradle, NuGet | 25 | | **Scoring/Priority** | Assigns scores to packages, resolves by priority | APT/aptitude | 26 | | **Ad-hoc** | Custom graph traversal without formal solver | cpanm | 27 | | **Bundled** | Dependencies included at build time, no runtime resolution | Snap | 28 | 29 | --- 30 | 31 | ## JavaScript 32 | 33 | ### npm 34 | 35 | npm (v7+) uses Arborist for dependency resolution, building a logical graph of dependencies overlaid on a physical tree of folders. 36 | 37 | **Algorithm**: Maximally naive deduplication with nested fallback 38 | 39 | **How it works**: 40 | - Builds a queue of dependencies starting from root 41 | - For each dependency, scans upward to find the shallowest placement that causes no conflicts 42 | - Preferentially deduplicates by finding versions that satisfy multiple dependents 43 | - When no single version satisfies all dependents, nests different versions under their respective requesters 44 | - Peer dependencies are validated as a set at each potential placement (they cannot be resolved by nesting) 45 | 46 | **Trade-offs**: 47 | - **Phantom dependencies**: Packages can accidentally access dependencies they did not declare because of hoisting 48 | - **Doppelgangers**: The same package may exist in multiple places when conflicts occur 49 | - **Peer dependency complexity**: Peer deps must be at same level, creating genuine conflicts that nesting cannot solve 50 | 51 | **References**: 52 | - [npm v7 Series - Arborist Deep Dive](https://blog.npmjs.org/post/618653678433435649/npm-v7-series-arborist-deep-dive.html) 53 | - [Arborist on GitHub](https://github.com/npm/arborist) 54 | 55 | --- 56 | 57 | ### Yarn Classic (v1) 58 | 59 | **Algorithm**: Similar to npm (deduplication with nested fallback) 60 | 61 | **How it works**: 62 | - Deduplicates where possible, nests when versions conflict 63 | - `yarn.lock` ensures deterministic installs across machines 64 | 65 | **Trade-offs**: 66 | - Same phantom dependency problem as npm 67 | - Deterministic lockfile was a key improvement over early npm 68 | 69 | **References**: 70 | - [Yarn Classic Documentation](https://classic.yarnpkg.com/en/docs/) 71 | 72 | --- 73 | 74 | ### Yarn Berry (v2+) 75 | 76 | Yarn Berry has its own resolver implementation (complete rewrite from Yarn Classic). 77 | 78 | **Algorithm**: Own implementation; deduplication with nested fallback 79 | 80 | **How it works**: 81 | - Own resolver in `@yarnpkg/core` ([source](https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/Resolver.ts)) 82 | - Deduplicates where possible, allows multiple versions when conflicts arise 83 | - Installation via Plug'n'Play: generates `.pnp.cjs` lookup file instead of `node_modules` 84 | 85 | **Trade-offs**: 86 | - **Faster installs**: No `node_modules` tree to write 87 | - **No phantom dependencies** (in strict mode) 88 | - **Compatibility issues**: Some tools expect `node_modules` to exist 89 | 90 | **References**: 91 | - [Plug'n'Play](https://yarnpkg.com/features/pnp) 92 | - [Resolver source](https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/Resolver.ts) 93 | 94 | --- 95 | 96 | ### pnpm 97 | 98 | pnpm has its own resolver implementation with similar semantics to npm. 99 | 100 | **Algorithm**: Own implementation; deduplication with isolated installation 101 | 102 | **How it works**: 103 | - Own resolver (`@pnpm/resolve-dependencies` and related packages) 104 | - Deduplicates where possible, allows multiple versions when conflicts arise 105 | - Each package gets isolated `node_modules/` with only declared dependencies 106 | 107 | **Trade-offs**: 108 | - **No phantom dependencies**: Packages only see their declared dependencies 109 | - **Stricter than npm**: Some packages may break if they rely on hoisting 110 | 111 | **References**: 112 | - [Motivation](https://pnpm.io/motivation) 113 | - [How peers are resolved](https://pnpm.io/how-peers-are-resolved) 114 | 115 | --- 116 | 117 | ### Bun 118 | 119 | Bun has its own resolver implementation written in Zig. 120 | 121 | **Algorithm**: Own implementation; npm-compatible semantics 122 | 123 | **How it works**: 124 | - Own resolver written in Zig 125 | - Uses hoisted installation by default (like npm) 126 | - Binary lockfile (`bun.lockb`) for fast parsing 127 | 128 | **Trade-offs**: 129 | - **Very fast**: Written in Zig with systems-level optimizations 130 | - **Compatible with npm**: Uses same `node_modules` layout 131 | - **No peer dependency conflict detection**: Does not warn about peer conflicts 132 | 133 | **References**: 134 | - [bun install](https://bun.sh/package-manager) 135 | 136 | --- 137 | 138 | ## Python 139 | 140 | ### pip 141 | 142 | pip (v20.3+) uses a backtracking resolver based on the resolvelib library. 143 | 144 | **Algorithm**: Backtracking with on-demand metadata fetching 145 | 146 | **How it works**: 147 | - Uses resolvelib, an abstract backtracking resolution algorithm 148 | - Fetches dependency metadata lazily (on-demand) because pre-computing the full tree is too expensive 149 | - When a conflict is found, backtracks to try alternative versions 150 | - NP-hard problem; can be slow on complex dependency graphs 151 | 152 | **Trade-offs**: 153 | - **Correct but slow**: Backtracking can take a long time on complex graphs 154 | - **Lazy metadata**: Cannot precompute optimal solution 155 | - **Better than legacy resolver**: Less likely to break environments 156 | - **No epochs in version comparisons**: PEP 440 epochs are supported but add complexity 157 | 158 | **References**: 159 | - [Dependency Resolution](https://pip.pypa.io/en/stable/topics/dependency-resolution/) 160 | - [More on Dependency Resolution](https://pip.pypa.io/en/stable/topics/more-dependency-resolution/) 161 | 162 | --- 163 | 164 | ### Poetry 165 | 166 | Poetry uses a PubGrub-based resolver (Mixology library). 167 | 168 | **Algorithm**: PubGrub (conflict-driven clause learning) 169 | 170 | **How it works**: 171 | - Uses Mixology, a Python implementation of PubGrub 172 | - Conflict-driven clause learning borrowed from SAT solvers 173 | - Learns from conflicts to prune search space 174 | - Provides clear error messages explaining why resolution failed 175 | 176 | **Trade-offs**: 177 | - **Better error messages**: Explains conflict chains 178 | - **Faster than naive backtracking** on many cases 179 | - **Slow metadata fetching**: PyPI does not always provide dependency metadata via API; Poetry must download packages to inspect 180 | - **Constraints help**: Narrower version ranges speed up resolution 181 | 182 | **Limitations**: 183 | - PubGrub does not support version epochs, which is why PyPA chose resolvelib for pip 184 | 185 | **References**: 186 | - [Poetry FAQ](https://python-poetry.org/docs/faq/) 187 | - [Mixology](https://github.com/sdispater/mixology) 188 | - [pipgrip](https://github.com/ddelange/pipgrip) 189 | 190 | --- 191 | 192 | ### uv 193 | 194 | uv (from Astral) uses pubgrub-rs, a Rust implementation of PubGrub. 195 | 196 | **Algorithm**: PubGrub with performance optimizations 197 | 198 | **How it works**: 199 | - Uses pubgrub-rs for version solving 200 | - Starts with virtual root package, selects highest priority undecided package 201 | - When conflict occurs, learns incompatibility to avoid repeating 202 | - Uses heuristics like backtracking and reordering packages that conflict frequently 203 | 204 | **Trade-offs**: 205 | - **Fast**: 8-10x faster than pip cold, 80-115x with cache 206 | - **Good error messages**: Inherited from PubGrub 207 | - **Drop-in replacement**: Compatible with pip/pip-tools/poetry workflows 208 | 209 | **References**: 210 | - [uv Resolver Internals](https://docs.astral.sh/uv/reference/internals/resolver/) 211 | - [uv GitHub](https://github.com/astral-sh/uv) 212 | 213 | --- 214 | 215 | ### Conda / Mamba 216 | 217 | Conda now uses libsolv (via libmamba) as its default solver. Mamba is a faster reimplementation of conda using the same solver. 218 | 219 | **Algorithm**: SAT solving via libsolv 220 | 221 | **How it works**: 222 | - Uses libsolv, the same SAT solver used by DNF/Zypper 223 | - Conflict-driven clause learning (CDCL) 224 | - Filters and sorts by channel priority, then by version 225 | 226 | **History**: Conda's original solver used pycosat (PicoSAT wrapper). libmamba became the default in conda 23.10 (2023) due to significant performance improvements. 227 | 228 | **Trade-offs**: 229 | - **Much faster than original**: 15s vs 91s in benchmarks 230 | - **Same solver as DNF**: Well-tested libsolv 231 | 232 | **References**: 233 | - [Package resolution](https://mamba.readthedocs.io/en/latest/advanced_usage/package_resolution.html) 234 | - [libmamba solver announcement](https://conda.org/blog/2023-11-06-conda-23-10-0-release/) 235 | 236 | --- 237 | 238 | ## Ruby 239 | 240 | ### Bundler / RubyGems 241 | 242 | Bundler and RubyGems use Molinillo, a backtracking resolver with forward checking. 243 | 244 | **Algorithm**: Molinillo (backtracking with forward checking and conflict-driven backjumping) 245 | 246 | **How it works**: 247 | - Maintains a stack of dependency and possibility states 248 | - For each dependency, sorts possibilities (versions) and tries them 249 | - Uses forward checking to detect conflicts early 250 | - On conflict, unwinds to the earliest state that contributed to the conflict 251 | - Shares resolver with CocoaPods 252 | 253 | **Trade-offs**: 254 | - **Efficient backjumping**: Skips irrelevant states on conflict 255 | - **Possibility grouping**: Versions with same sub-dependencies grouped to reduce search 256 | - **Shared**: Used by Bundler, RubyGems, and CocoaPods 257 | 258 | **References**: 259 | - [Molinillo](https://github.com/CocoaPods/Molinillo) 260 | - [Molinillo Architecture](https://github.com/CocoaPods/Molinillo/blob/master/ARCHITECTURE.md) 261 | - [Bundler 1.9 Announcement](https://bundler.io/blog/2015/03/21/hello-bundler-19.html) 262 | 263 | --- 264 | 265 | ## Rust 266 | 267 | ### Cargo 268 | 269 | Cargo uses a backtracking resolver that tries the highest compatible version first. 270 | 271 | **Algorithm**: Backtracking with semver-aware version selection 272 | 273 | **How it works**: 274 | - Tries highest version first (within semver bounds) 275 | - Allows multiple major versions of the same package to coexist 276 | - Uses `ActivationsKey` to track: only one semver-compatible version allowed 277 | - Features are unified across the dependency graph 278 | - `links` field ensures native libraries linked only once 279 | 280 | **Version interpretation**: 281 | - `1.2.3` means `>=1.2.3, <2.0.0` 282 | - `0.2.3` means `>=0.2.3, <0.3.0` 283 | - `0.0.3` means `>=0.0.3, <0.0.4` 284 | 285 | **Trade-offs**: 286 | - **Multiple major versions**: Different major versions can coexist 287 | - **Feature unification**: All feature flags combined (can cause unexpected compilation) 288 | - **Can produce duplicates**: Different minor versions might both be included if not deduplicable 289 | - **DFS-based**: Can be slow on large graphs 290 | 291 | **References**: 292 | - [Dependency Resolution](https://doc.rust-lang.org/cargo/reference/resolver.html) 293 | - [SemVer Compatibility](https://doc.rust-lang.org/cargo/reference/semver.html) 294 | - [Specifying Dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) 295 | 296 | --- 297 | 298 | ## Perl 299 | 300 | ### cpanm / CPAN.pm 301 | 302 | cpanm and CPAN.pm use ad-hoc dependency resolution that is neither correct nor complete according to academic analysis. 303 | 304 | **Algorithm**: Ad-hoc depth-first traversal 305 | 306 | **How it works**: 307 | - Fetches dependency metadata from META.json/META.yml in each distribution 308 | - Traverses dependencies depth-first, installing each before its parent 309 | - Version constraints support operators (`>=`, `<=`, `>`, `<`, `==`, `!=`) and can be combined with commas 310 | - Single version of each module system-wide (first found in `@INC` wins) 311 | - No backtracking: if a version is installed that later causes a conflict, resolution fails 312 | - `--scandeps` option outputs the dependency tree without installing 313 | 314 | **Versioning**: Free-form strings rather than semantic versioning. Version comparison uses Perl's version.pm rules. 315 | 316 | **Trade-offs**: 317 | - **Not complete**: May fail to find a solution even when one exists 318 | - **Not correct**: May propose solutions that violate constraints 319 | - **No conflict resolution**: The `conflicts` relationship exists in the spec but is rarely used and discouraged 320 | - **Simple and fast**: Lack of backtracking means resolution is quick when it works 321 | 322 | **Carton** adds lockfile support (`cpanfile.snapshot`) on top of cpanm for reproducible installs, but does not change the underlying resolution algorithm. 323 | 324 | **References**: 325 | - [cpanm](https://metacpan.org/pod/App::cpanminus) 326 | - [Carton](https://metacpan.org/pod/Carton) 327 | - [CPAN::Meta::Spec](https://perldoc.perl.org/CPAN::Meta::Spec) (dependency specification) 328 | - [Dependency Solving Is Still Hard (2020)](https://arxiv.org/abs/2011.07851) - academic survey classifying CPAN as ad-hoc, incomplete, and incorrect 329 | 330 | --- 331 | 332 | ## Go 333 | 334 | ### Go Modules 335 | 336 | Go modules use Minimal Version Selection (MVS). 337 | 338 | **Algorithm**: Minimal Version Selection 339 | 340 | **How it works**: 341 | - Does **not** select the newest version 342 | - Selects the **minimum** version that satisfies all requirements 343 | - Traverses the module graph, tracking maximum required version for each module 344 | - At the end, uses the highest version seen for each module (which is the minimum that works) 345 | - `go.sum` records cryptographic checksums for verification 346 | 347 | **Trade-offs**: 348 | - **Predictable**: Easy to understand and implement (~50 lines of code) 349 | - **High-fidelity builds**: Uses versions authors tested with 350 | - **May use old versions**: Does not automatically upgrade to latest 351 | - **Security updates require explicit action**: Must run `go get -u` to upgrade 352 | 353 | **References**: 354 | - [Minimal Version Selection](https://research.swtch.com/vgo-mvs) 355 | - [Go Modules Reference](https://go.dev/ref/mod) 356 | - [Modules Part 03: Minimal Version Selection](https://www.ardanlabs.com/blog/2019/12/modules-03-minimal-version-selection.html) 357 | 358 | --- 359 | 360 | ## Java/JVM 361 | 362 | ### Maven 363 | 364 | Maven uses "nearest definition first" with depth-based mediation. 365 | 366 | **Algorithm**: Nearest definition wins (breadth-first, first declaration breaks ties) 367 | 368 | **How it works**: 369 | - Traverses dependency graph breadth-first 370 | - For each dependency, uses the version from the nearest definition in the tree 371 | - If two dependencies are at same depth, first declaration in POM wins 372 | - `` section takes precedence over mediation 373 | 374 | **Trade-offs**: 375 | - **Not newest wins**: May use older version if declared closer to root 376 | - **Order-dependent**: Declaration order in POM affects resolution 377 | - **Can be surprising**: Transitive dependency version may override direct 378 | - **No configurable strategy**: Cannot switch to "highest wins" (though Maven 4 may change this) 379 | 380 | **Workarounds**: 381 | - Use `maven-enforcer-plugin` with `requireUpperBoundDeps` rule 382 | - Use `` to pin versions 383 | 384 | **References**: 385 | - [Introduction to the Dependency Mechanism](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html) 386 | - [Dependency Mediation and Conflict Resolution](https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution) 387 | - [How does version resolution work in Maven and Gradle?](http://jlbp.dev/how-does-version-resolution-work-in-maven-and-gradle) 388 | 389 | --- 390 | 391 | ### Gradle 392 | 393 | Gradle defaults to "newest version wins" with configurable conflict resolution. 394 | 395 | **Algorithm**: Highest version wins (configurable) 396 | 397 | **How it works**: 398 | - Collects all requested versions from dependency graph 399 | - By default, selects the highest version 400 | - Version conflicts are resolved automatically unless `failOnVersionConflict()` is enabled 401 | 402 | **Configuration options**: 403 | - `failOnVersionConflict()`: Fail build on any conflict 404 | - `force 'group:artifact:version'`: Force specific version 405 | - `preferProjectModules()`: Prefer local project over binary 406 | - `constraints {}`: Suggest versions without requiring 407 | 408 | **Trade-offs**: 409 | - **Automatic resolution**: Less manual intervention 410 | - **May upgrade unexpectedly**: Transitive dependency can bump version 411 | - **Flexible**: Many knobs to control behavior 412 | - **Different from Maven**: Can cause confusion when migrating 413 | 414 | **References**: 415 | - [Dependency Resolution](https://docs.gradle.org/current/userguide/dependency_resolution.html) 416 | - [Dependency Constraints and Conflict Resolution](https://docs.gradle.org/current/userguide/dependency_constraints_conflicts.html) 417 | - [ResolutionStrategy DSL](https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html) 418 | 419 | --- 420 | 421 | ## PHP 422 | 423 | ### Composer 424 | 425 | Composer uses a SAT solver ported from openSUSE's libzypp. 426 | 427 | **Algorithm**: SAT solving (DPLL/CDCL) 428 | 429 | **How it works**: 430 | - Translates dependencies into boolean satisfiability clauses 431 | - Uses conflict-driven clause learning (CDCL) 432 | - Finds assignment that satisfies all constraints or proves none exists 433 | 434 | **Clause generation**: 435 | - `A requires B`: `(-A|B1|B2|...)` 436 | - `A conflicts with B`: `(-A|-B)` 437 | - Policy rules for updates 438 | 439 | **Trade-offs**: 440 | - **Correct**: SAT solvers are well-studied 441 | - **Can be slow**: PHP implementation is not as fast as native SAT solvers 442 | - **NP-complete**: Worst case exponential, but usually fine in practice 443 | 444 | **Performance note**: Experiments showed native SAT solver (Plingeling) solving same formula 633x faster than Composer's PHP implementation. 445 | 446 | **References**: 447 | - [Composer's SAT Solver (2012 presentation)](https://www.naderman.de/slippy/tmp/2012-06-07-Composers-SAT-Solver.html) 448 | - [Dependency Resolution with SAT](https://www.slideshare.net/naderman/dependency-resolution-with-sat-symfony-live-2011-paris) 449 | - [Solver.php source](https://github.com/composer/composer/blob/main/src/Composer/DependencyResolver/Solver.php) 450 | 451 | --- 452 | 453 | ## .NET 454 | 455 | ### NuGet 456 | 457 | NuGet defaults to lowest matching version for dependencies. 458 | 459 | **Algorithm**: Lowest applicable version (configurable) 460 | 461 | **How it works**: 462 | - When constraint is `>= 2.1`, picks 2.1 if available, or next lowest 463 | - For transitive dependencies, uses lowest version that satisfies constraint 464 | - Floating versions (e.g., `6.0.*`) select highest matching version 465 | 466 | **Configuration**: 467 | - `-DependencyVersion` switch: `Lowest` (default), `HighestPatch`, `HighestMinor`, `Highest` 468 | - Only applies to `packages.config` projects, not `PackageReference` 469 | 470 | **Rationale**: Lowest version is most likely to be compatible since that's what the package author tested with. 471 | 472 | **Trade-offs**: 473 | - **Conservative**: Less likely to break 474 | - **May miss improvements**: Does not get latest patches automatically 475 | - **Different from most managers**: Most others pick highest 476 | 477 | **References**: 478 | - [NuGet Package Dependency Resolution](https://learn.microsoft.com/en-us/nuget/concepts/dependency-resolution) 479 | - [Package Versioning](https://learn.microsoft.com/en-us/nuget/concepts/package-versioning) 480 | 481 | --- 482 | 483 | ## Dart/Flutter 484 | 485 | ### pub 486 | 487 | pub uses PubGrub, an algorithm designed specifically for Dart. 488 | 489 | **Algorithm**: PubGrub (conflict-driven clause learning) 490 | 491 | **How it works**: 492 | - Starts with partial solution, iteratively selects packages 493 | - On conflict, derives incompatibility and backtracks 494 | - Uses unit propagation and logical resolution 495 | - Generates human-readable error messages from incompatibility chain 496 | 497 | PubGrub was designed to provide clear error messages explaining why resolution failed. 498 | 499 | **Trade-offs**: 500 | - **Good error messages**: Explains dependency conflicts in plain English 501 | - **Fast**: Uses modern SAT-solving techniques 502 | - **Widely adopted**: Ported to Rust (pubgrub-rs), used by uv and others 503 | 504 | **References**: 505 | - [Dart pub solver documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md) 506 | - [pubgrub-rs](https://github.com/pubgrub-rs/pubgrub) 507 | - [Package versioning](https://dart.dev/tools/pub/versioning) 508 | 509 | --- 510 | 511 | ## Elixir 512 | 513 | ### Mix/Hex 514 | 515 | **Algorithm**: Highest version, single version per package 516 | 517 | **How it works**: 518 | - Dependency resolution always tries to use latest version of all packages 519 | - Fails if incompatible version requirements exist 520 | - Single version of each package in the VM (cannot have multiple versions) 521 | 522 | **Options**: 523 | - `:override` option forces a dependency version to be used everywhere 524 | 525 | **Trade-offs**: 526 | - **Simple model**: Only one version of each package 527 | - **Clear conflicts**: Either resolves or fails with error 528 | - **No multiple versions**: Cannot work around conflicts by having two versions 529 | 530 | **References**: 531 | - [Mix usage](https://hex.pm/docs/usage) 532 | - [mix deps](https://hexdocs.pm/mix/1.12/Mix.Tasks.Deps.html) 533 | 534 | --- 535 | 536 | ## Haskell 537 | 538 | ### Cabal 539 | 540 | Cabal uses a modular solver with configurable backtracking. 541 | 542 | **Algorithm**: Modular solver with backjumping 543 | 544 | **How it works**: 545 | - Inspired by Nordin and Tolmach's modular lazy search 546 | - Uses backjumping to skip irrelevant states 547 | - `--reorder-goals` heuristic can speed up some resolutions 548 | - `--count-conflicts` prefers goals involved in many conflicts (default) 549 | 550 | **Configuration**: 551 | - `max-backjumps`: Maximum backtrack steps (-1 for unlimited, default 2000) 552 | - `--reorder-goals`: Try to order goals more efficiently 553 | - `--count-conflicts`: Prioritize conflict-heavy packages 554 | 555 | **Trade-offs**: 556 | - **Configurable**: Many knobs to tune behavior 557 | - **Can be slow**: Complex dependency graphs cause extensive backtracking 558 | - **Single instance**: Avoids multiple versions of same package in build plan 559 | 560 | **References**: 561 | - [cabal.project Reference](https://cabal.readthedocs.io/en/3.4/cabal-project.html) 562 | - [Modular Solver](https://wiki.haskell.org/HaskellImplementorsWorkshop/2011/Loeh) 563 | 564 | --- 565 | 566 | ## OCaml 567 | 568 | ### opam 569 | 570 | opam is notable for being one of the few package managers to fully embrace external CUDF solvers, as advocated by the Mancoosi research project. 571 | 572 | **Algorithm**: External CUDF solvers (mccs built-in, aspcud, packup, or custom) 573 | 574 | **How it works**: 575 | - Translates the dependency problem to CUDF (Common Upgradeability Description Format) 576 | - Invokes an external solver (mccs by default since opam 2.0) 577 | - User can specify optimization criteria (e.g., minimize changes, minimize removals) 578 | - mccs uses Mixed Integer Linear Programming; aspcud uses Answer Set Programming 579 | 580 | **User preferences**: opam allows custom solver criteria. For example: 581 | ``` 582 | opam install merlin --criteria="-changed,-removed" 583 | ``` 584 | This minimizes changes to other installed packages. 585 | 586 | **Trade-offs**: 587 | - **Correct and complete**: Uses formal solvers 588 | - **User preferences**: Can express optimization criteria 589 | - **Pluggable**: Can swap solvers without changing opam 590 | - **Separation of concerns**: Solver is a separate component 591 | 592 | **References**: 593 | - [External solvers](https://opam.ocaml.org/doc/External_solvers.html) 594 | - [Specifying Solver Preferences](https://opam.ocaml.org/doc/1.1/Specifying_Solver_Preferences.html) 595 | - [mccs](https://github.com/ocaml-opam/ocaml-mccs) 596 | - [Dependency Solving Is Still Hard (2020)](https://arxiv.org/abs/2011.07851) - cites opam as the primary example of the "separation of concerns" approach 597 | 598 | --- 599 | 600 | ## Swift/iOS 601 | 602 | ### CocoaPods 603 | 604 | CocoaPods uses Molinillo, the same resolver as Bundler. 605 | 606 | **Algorithm**: Molinillo (backtracking with forward checking) 607 | 608 | See Bundler section for algorithm details. 609 | 610 | **References**: 611 | - [Molinillo](https://github.com/CocoaPods/Molinillo) 612 | - [Molinillo Architecture](https://github.com/CocoaPods/Molinillo/blob/master/ARCHITECTURE.md) 613 | 614 | --- 615 | 616 | ### Swift Package Manager 617 | 618 | Swift Package Manager uses PubGrub for dependency resolution. 619 | 620 | **Algorithm**: PubGrub 621 | 622 | **How it works**: 623 | - Same PubGrub algorithm used by Dart pub, Poetry, uv 624 | - `Package.resolved` records resolved versions 625 | - Target-based resolution (Swift 5.2+): only resolves dependencies actually needed by included targets 626 | 627 | **Trade-offs**: 628 | - **Good error messages**: PubGrub explains why resolution failed 629 | - **Integrated with Xcode**: First-class support in Apple tooling 630 | 631 | **References**: 632 | - [PubGrub implementation PR](https://github.com/apple/swift-package-manager/pull/1918) 633 | - [Swift Package Manager source](https://github.com/apple/swift-package-manager/tree/main/Sources/PackageGraph) 634 | 635 | --- 636 | 637 | ## System Package Managers 638 | 639 | ### APT (Debian/Ubuntu) 640 | 641 | APT uses a scoring-based resolver with immediate dependency resolution. 642 | 643 | **Algorithm**: Scoring with immediate resolution 644 | 645 | **How it works**: 646 | - Packages are assigned scores based on importance (Essential: 100, Required: 3, Important: 2, etc.) 647 | - Uses two-stage resolution: first marks packages for action, then resolves 648 | - For OR dependencies, examines alternatives in declared order 649 | - Pre-depends must be installed and configured before dependent package 650 | - dpkg itself does not resolve dependencies; APT handles this layer 651 | 652 | **Trade-offs**: 653 | - **Scoring guides choices**: More important packages preferred 654 | - **Order matters for OR**: First satisfying alternative chosen 655 | - **Separate tools**: dpkg for low-level, apt for resolution 656 | - **Mature**: Decades of use on Debian-based systems 657 | 658 | **References**: 659 | - [Dependency resolution in aptitude](https://www.debian.org/doc/manuals/aptitude/ch02s03s01.en.html) 660 | - [Immediate dependency resolution](https://www.debian.org/doc/manuals/aptitude/ch02s03s02.en.html) 661 | 662 | --- 663 | 664 | ### DNF/YUM (Fedora/RHEL) 665 | 666 | DNF uses libsolv, a SAT-based dependency resolver from openSUSE. 667 | 668 | **Algorithm**: SAT solving via libsolv 669 | 670 | **How it works**: 671 | - Formulates dependencies as a Boolean satisfiability (SAT) problem 672 | - Uses a reimplementation of the Minisat solver 673 | - Hawkey library interfaces between DNF and libsolv 674 | - Parses RPM metadata to construct dependency graph 675 | - Finds minimal set of packages satisfying all constraints 676 | 677 | **History**: DNF replaced YUM in Fedora 22+ and RHEL 8+. YUM's ad-hoc dependency checking was slow and unpredictable; libsolv provides modern SAT-based resolution. 678 | 679 | **Trade-offs**: 680 | - **Fast**: Native C implementation with optimized SAT solver 681 | - **Correct**: SAT solvers are mathematically well-founded 682 | - **Shared**: Same libsolv used by Zypper (openSUSE) 683 | - **Complex metadata**: RPM repositories have rich dependency information 684 | 685 | **References**: 686 | - [libsolv](https://github.com/openSUSE/libsolv) 687 | - [DNF GitHub](https://github.com/rpm-software-management/dnf) 688 | - [Features/DNF](https://fedoraproject.org/wiki/Features/DNF) 689 | 690 | --- 691 | 692 | ### Pacman (Arch Linux) 693 | 694 | Pacman uses [libalpm](https://gitlab.archlinux.org/pacman/pacman/-/tree/master/lib/libalpm) for package management. Limited public documentation on the resolution algorithm internals. 695 | 696 | **How it works**: 697 | - Resolves dependencies during install/update 698 | - Single version of each package system-wide 699 | - Supports optional dependencies (not installed by default) 700 | 701 | **Trade-offs**: 702 | - **Simple model**: One version per package 703 | - **Rolling release**: Always latest versions 704 | 705 | **References**: 706 | - [pacman ArchWiki](https://wiki.archlinux.org/title/Pacman) 707 | 708 | --- 709 | 710 | ### Homebrew (macOS/Linux) 711 | 712 | Homebrew has a simpler model than most package managers: one version of each formula at a time. 713 | 714 | **Algorithm**: Single version per formula, topological sort for install order 715 | 716 | **How it works**: 717 | - Each formula specifies its dependencies (no version ranges) 718 | - Only one version of each formula is available at a time in a given tap 719 | - Dependencies installed in topological order 720 | - Build-time dependencies can be skipped when installing from bottles 721 | 722 | **Trade-offs**: 723 | - **No version conflicts**: Only one version exists 724 | - **Upgrade cascades**: Updating a dependency may require rebuilding dependents 725 | - **Simple model**: No constraint solving needed 726 | 727 | **References**: 728 | - [Formula Cookbook](https://docs.brew.sh/Formula-Cookbook) 729 | 730 | --- 731 | 732 | ### Nix 733 | 734 | Nix avoids traditional resolution by having each package explicitly specify exact versions of its dependencies. 735 | 736 | **Algorithm**: No resolution needed - dependencies are explicit 737 | 738 | **How it works**: 739 | - Each package (derivation) specifies exact dependencies, not version ranges 740 | - No constraint solving or version selection at install time 741 | - Multiple versions of the same package can coexist 742 | - Nixpkgs (the package set) determines which versions are available together 743 | 744 | **Trade-offs**: 745 | - **No dependency conflicts**: Each package gets exactly what it declares 746 | - **Multiple versions**: Different packages can use different versions of the same dependency 747 | - **Nixpkgs is the constraint**: Available versions determined by which nixpkgs revision you use 748 | 749 | **References**: 750 | - [How Nix Works](https://nixos.org/guides/how-nix-works/) 751 | - [Nix Manual](https://nixos.org/manual/nix/stable/introduction) 752 | 753 | --- 754 | 755 | ### Snap 756 | 757 | Snaps bundle their dependencies rather than resolving them at install time. 758 | 759 | **Algorithm**: No runtime resolution - dependencies bundled at build time 760 | 761 | **How it works**: 762 | - Dependencies are bundled into the snap at build time 763 | - Snapcraft uses APT to resolve build dependencies during snap creation 764 | - At install time, no resolution needed since everything is bundled 765 | - Base snaps provide common runtime dependencies 766 | 767 | **Trade-offs**: 768 | - **No dependency conflicts at runtime**: Everything bundled 769 | - **Larger package sizes**: Each snap carries its own dependencies 770 | - **Build-time resolution only**: Uses APT for build dependencies 771 | 772 | **References**: 773 | - [Manage dependencies](https://snapcraft.io/docs/build-and-staging-dependencies) 774 | 775 | --- 776 | 777 | ### Alpine APK 778 | 779 | **Algorithm**: Unknown (limited documentation on internals) 780 | 781 | **How it works**: 782 | - Maintains "World" file listing explicitly installed packages 783 | - Resolves dependencies to satisfy World requirements 784 | - Supports virtual packages (multiple packages can provide same capability) 785 | - Single version of each package 786 | 787 | **References**: 788 | - [Alpine Package Keeper](https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper) 789 | 790 | --- 791 | 792 | ## HPC 793 | 794 | ### Spack 795 | 796 | Spack is a package manager for HPC that uses Answer Set Programming (ASP) for dependency resolution. 797 | 798 | **Algorithm**: Answer Set Programming (ASP) via Clingo 799 | 800 | **How it works**: 801 | - Uses Clingo, an ASP solver, for "concretization" (resolving abstract specs to concrete versions) 802 | - Models dependencies as logic programming rules and constraints 803 | - Optimizes for user preferences (most tested, most optimized, etc.) 804 | - Can handle complex constraints like compiler versions, build variants, and target architectures 805 | 806 | **Why ASP over SAT**: SAT finds any satisfying solution; ASP finds an optimal solution according to user-defined criteria. 807 | 808 | **Trade-offs**: 809 | - **Handles HPC complexity**: Compiler versions, MPI implementations, GPU targets 810 | - **Optimization**: Finds best solution, not just any solution 811 | - **Slower than SAT**: More expressive but more expensive 812 | 813 | **References**: 814 | - [Concretizer settings](https://spack.readthedocs.io/en/latest/build_settings.html) 815 | - [Spack's new Concretizer (FOSDEM 2020)](https://archive.fosdem.org/2020/schedule/event/dependency_solving_not_just_sat/) 816 | 817 | --- 818 | 819 | ## Algorithm Comparison 820 | 821 | | Package Manager | Algorithm | Default Version | Lockfile | Multiple Versions | 822 | |-----------------|-----------|-----------------|----------|-------------------| 823 | | npm | Dedup + nesting | Highest | Yes | Yes (nested) | 824 | | Yarn Classic | Dedup + nesting | Highest | Yes | Yes (nested) | 825 | | Yarn Berry | Dedup + nesting | Highest | Yes | Yes (isolated) | 826 | | pnpm | Dedup + nesting | Highest | Yes | Yes (isolated) | 827 | | Bun | Dedup + nesting | Highest | Yes | Yes (nested) | 828 | | pip | Backtracking | Highest | No\* | No | 829 | | Poetry | PubGrub | Highest | Yes | No | 830 | | uv | PubGrub | Highest | Yes | No | 831 | | Conda/Mamba | SAT (libsolv) | Highest | Yes | No | 832 | | Bundler | Molinillo | Highest | Yes | No | 833 | | Cargo | Backtracking | Highest | Yes | Yes (major) | 834 | | cpanm/Carton | Ad-hoc (depth-first) | Highest | Yes (Carton) | No | 835 | | Go | MVS | Lowest | No | No | 836 | | Maven | Nearest | Nearest | No | No | 837 | | Gradle | Newest | Highest | Yes | No | 838 | | Composer | SAT | Highest | Yes | No | 839 | | NuGet | Lowest | Lowest | Yes | No | 840 | | pub | PubGrub | Highest | Yes | No | 841 | | Mix/Hex | Latest | Highest | Yes | No | 842 | | Cabal | Modular | Highest | Yes | No | 843 | | opam | CUDF (external) | Highest | Yes | No | 844 | | CocoaPods | Molinillo | Highest | Yes | No | 845 | | SwiftPM | PubGrub | Highest | Yes | No | 846 | | APT | Scoring | Highest | No | No | 847 | | DNF | SAT (libsolv) | Highest | No | No | 848 | | Pacman | Unknown | Latest | No | No | 849 | | Homebrew | Formula-based | Latest | No | No | 850 | | Nix | Explicit (no resolution) | Specified | Yes (flakes) | Yes (by design) | 851 | | Snap | Bundled (no resolution) | N/A | No | N/A | 852 | | Alpine APK | Unknown | Latest | No | No | 853 | | Spack | ASP (Clingo) | Optimized | Yes | Yes | 854 | 855 | > [!tip] 856 | > \* pip cannot *install* from standard lock files; `pip freeze > requirements.txt` with pinned versions serves 857 | > a similar purpose; additionally, `constraint.txt` files can be used to restrict dependency resolution; 858 | > pip v25.3 is able to produce the tool-agnostic ecosystem standard [`pylock.toml`] lock file accepted through 859 | > [PEP 751] at the beginning of 2025 — but cannot use it during installation (as of Dec 2025). Other installers 860 | > in the Python ecosystem are able to install from [`pylock.toml`] files. 861 | > 862 | > [`pylock.toml`]: https://packaging.python.org/en/latest/specifications/pylock-toml/ 863 | > [PEP 751]: https://peps.python.org/pep-0751/ 864 | 865 | --- 866 | 867 | ## Common Resolution Strategies 868 | 869 | ### Highest Version (Most Common) 870 | Select the highest version that satisfies all constraints. Used by most modern package managers. 871 | 872 | **Pros**: Gets latest features and security fixes 873 | **Cons**: More likely to introduce breaking changes 874 | 875 | ### Lowest Version (NuGet default, Go MVS) 876 | Select the lowest version that satisfies all constraints. 877 | 878 | **Pros**: More stable, uses what was tested 879 | **Cons**: May miss security patches 880 | 881 | ### Nearest Definition (Maven) 882 | Select version based on proximity in dependency graph. 883 | 884 | **Pros**: Gives control to direct dependencies 885 | **Cons**: Order-dependent, can be surprising 886 | 887 | ### Deduplication with Nesting (npm, Yarn, pnpm, Bun) 888 | Try to find versions that satisfy multiple dependents; nest different versions when conflicts arise. 889 | 890 | **Pros**: Handles conflicts without failing, disk efficient when versions align 891 | **Cons**: Phantom dependencies possible (except pnpm/Yarn PnP), multiple copies when conflicts exist 892 | 893 | ### Strict Isolation (pnpm, Yarn PnP) 894 | Each package only sees its declared dependencies. 895 | 896 | **Pros**: No phantom dependencies 897 | **Cons**: Some packages may break if they rely on hoisted dependencies 898 | 899 | --- 900 | 901 | ## Phantom Dependencies 902 | 903 | A phantom dependency is when package A can `require('B')` even though A does not list B in its dependencies, simply because B was installed for another package and hoisted to a common ancestor. 904 | 905 | **Affected by**: npm, Yarn Classic 906 | **Prevented by**: pnpm, Yarn PnP (strict mode) 907 | 908 | This causes problems when: 909 | - The package depending on B is removed 910 | - Different version of B is needed 911 | - Different environment has different hoisting result 912 | 913 | --- 914 | 915 | ## Solver Correctness and Completeness 916 | 917 | A 2020 academic survey ([Dependency Solving Is Still Hard](https://arxiv.org/abs/2011.07851)) evaluated package managers on two properties: 918 | 919 | - **Correct**: Will the solver always propose solutions that respect dependency constraints? 920 | - **Complete**: Will the solver always find a solution if one exists? 921 | 922 | Their findings: 923 | 924 | | Package Manager | Solver Type | Correct | Complete | User Preferences | 925 | |-----------------|-------------|---------|----------|------------------| 926 | | npm | ad-hoc | ? | ? | No | 927 | | opam | CUDF (external) | Yes | Yes | Yes | 928 | | pip | ad-hoc | Yes | Yes | No | 929 | | NuGet | ad-hoc | Yes | Yes | No | 930 | | Maven | ad-hoc | Yes | Yes | With plugins | 931 | | RubyGems | ad-hoc | ? | ? | ? | 932 | | Cargo | ad-hoc | Yes | Yes | No | 933 | | CPAN | ad-hoc | No | No | No | 934 | | Cabal | ? | No | No | No | 935 | | Debian (apt) | CUDF (external) | Yes | Yes | Yes | 936 | | RedHat (dnf) | libzypp SAT | Yes | Yes | ? | 937 | | Eclipse P2 | Sat4j | Yes | Yes | Yes | 938 | 939 | The paper notes that SAT-based solvers (libsolv, Sat4j) and CUDF-based external solvers are generally both correct and complete, while ad-hoc implementations vary. 940 | 941 | --- 942 | 943 | ## Complexity Note 944 | 945 | General dependency resolution with arbitrary version constraints is NP-hard (reducible to SAT) when you must select exactly one version of each package. However, not all package managers face this complexity: 946 | 947 | - **npm/Yarn/pnpm** attempt to deduplicate by finding versions that satisfy multiple dependents, but can nest different versions when conflicts arise. Peer dependencies complicate this since they must be at the same level and cannot be resolved by nesting. 948 | - **Go's MVS** is polynomial-time because it always picks the minimum version, avoiding the combinatorial explosion of choosing among candidates. 949 | - **SAT-based resolvers** (Composer, libsolv) embrace the complexity and use optimized solvers. 950 | - **Backtracking resolvers** (pip, Cargo) can hit exponential worst cases but use heuristics to perform well in practice. 951 | - **PubGrub** uses conflict-driven clause learning to prune the search space efficiently. 952 | - **Ad-hoc resolvers** (CPAN, older npm) may be incomplete, failing to find solutions that exist. 953 | 954 | --- 955 | 956 | ## References 957 | 958 | - Abate et al., [Dependency Solving Is Still Hard, but We Are Getting Better at It](https://arxiv.org/abs/2011.07851) (2020) - Census of solver correctness/completeness across package managers 959 | - Abate et al., [Dependency solving: A separate concern in component evolution management](https://www.sciencedirect.com/science/article/abs/pii/S0164121212000477) (2012) - Modular architecture using external SAT/PBO/MILP solvers 960 | - Tucker et al., [OPIUM: Optimal Package Install/Uninstall Manager](https://cseweb.ucsd.edu/~lerner/papers/opium.pdf) (2007) - SAT-based solver; found 23.3% of Debian users hit apt-get incompleteness 961 | - Mancinelli et al., [Managing the Complexity of Large Free and Open Source Package-Based Software Distributions](https://www.researchgate.net/publication/29599445_Managing_the_Complexity_of_Large_Free_and_Open_Source_Package-Based_Software_Distributions) (2006) - Original NP-completeness proof for Debian dependencies 962 | - Di Cosmo & Vouillon, [On software component co-installability](https://dl.acm.org/doi/10.1145/2025113.2025149) (2011) - Formal framework for compatible component combinations 963 | - Burrows, [Modelling and Resolving Software Dependencies](https://www.debian.org/doc/manuals/aptitude/ch02s03s01.en.html) (2005) - Aptitude's best-first-search resolver 964 | - Weizenbaum, [PubGrub: Next-Generation Version Solving](https://nex3.medium.com/pubgrub-2fb6470504f) (2018) - Algorithm used by Dart pub, Poetry, uv, SwiftPM 965 | - Cox, [Minimal Version Selection](https://research.swtch.com/vgo-mvs) (2018) - Go modules' polynomial-time approach 966 | - Gamblin et al., [The Spack Package Manager](https://tgamblin.github.io/pubs/spack-sc15.pdf) (2015) - ASP-based resolution for HPC 967 | - Dolstra et al., [Nix: A Safe and Policy-Free System for Software Deployment](https://nixos.org/~eelco/pubs/nspfssd-lisa2004-final.pdf) (2004) - Purely functional package management 968 | - Cappos, [Stork: Secure Package Management for VM Environments](https://www.cs.arizona.edu/sites/default/files/TR08-04.pdf) (2008) - PhD thesis introducing backtracking dependency resolution 969 | 970 | For a comprehensive bibliography, see [Package Management Papers](https://nesbitt.io/2025/11/13/package-management-papers.html). 971 | 972 | --- 973 | 974 | ## License 975 | 976 | [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) 977 | --------------------------------------------------------------------------------