├── 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 |
--------------------------------------------------------------------------------