Quartermaster logo
Rust · PubGrub dependency resolver

Resolve versions.
Understand conflicts.

A from-scratch package manager whose interesting half is the resolver: a readable PubGrub version solver that pins every dependency, or proves in plain English why it can't. Try it live below.

The resolver, live

Two boxes. On the left, what your project asks for. On the right, every package version that exists and what each one needs. Hit Resolve and the real PubGrub algorithm runs in your browser. It either pins one version of everything, or explains in plain English why no combination works. New here? Tap a preset below to load a ready-made example.

Load: solvable Load: web app stack Load: deep chain Load: backtracking Load: diamond that fits Load: diamond clash Load: pin vs range Load: a conflict Resolve ▶

What your project needs · one per line: name then version range

Packages that exist · name and version, then its own needs indented below

^1.0 compatible updates ~1.2 patch updates only >=1.0, <2.0 a range 1.0.0 that exact version * any version
How to use this playground

The left box is your project's direct requirements, one per line: a package name and a version range (web ^1.0). The right box is the registry, every package version that exists, with each version's own dependencies indented beneath it.

Tap a preset chip to load a ready-made example, then press Resolve. If a valid combination exists you get one pinned version per package plus a dependency tree; if not, you get the numbered proof of exactly which two requirements collide. Edit either box and resolve again to experiment.

The part everyone else skips

Every build-your-own package manager hand-waves resolution and every real one explains failures badly. Quartermaster is built around exactly those two things.

01

A real resolver

Picking one version of every package so all constraints hold is NP-hard. Quartermaster uses PubGrub, the same conflict-driven algorithm as Dart's pub and uv: unit propagation, learned incompatibilities, and backjumping. Not a greedy toy.

02

Failures are proofs

When npm or pip can't resolve, they dump version numbers. Quartermaster hands back the derivation path in English, ending at the exact two requirements that collide, so a person or an AI agent knows what to change.

How it works

Two moves alternate over a growing set of incompatibilities, combinations of versions that cannot coexist.

Propagate

Forced facts

When an incompatibility has all but one term already forced by earlier choices, the last term's negation becomes a new derived fact. When every term is forced, that's a conflict.

Decide & backjump

Try, learn, retreat

Pick a required package, take its highest allowed version, add its dependencies. On a conflict, resolve it against its cause into a new incompatibility and jump straight back to where that becomes decisive, never re-treading dead branches.

# the version algebra is boundary-exact: ranges are interval sets
^1.2.3  →  >=1.2.3, <2.0.0
~1.2.0  →  >=1.2.0, <1.3.0
1 || 3  →  >=1.0.0,<2.0.0  or  >=3.0.0,<4.0.0

# intersection, union, and complement are total and exact,
# so the solver reasons correctly instead of string-matching versions.

The same solver, on the command line

The browser demo above is a faithful port of the Rust engine. Here is the real binary running against the example registry that ships in the repo.

$ qm resolve examples/app.qm --registry examples/registry.qm
resolved myapp 0.1.0 (4 packages):
  bytes 1.1.5
  http 1.3.0
  json 1.3.0
  web 1.1.0

$ qm tree examples/app.qm --registry examples/registry.qm
myapp 0.1.0
├─ json 1.3.0
│  └─ bytes 1.1.5
└─ web 1.1.0
   ├─ http 1.3.0
   │  └─ bytes 1.1.5 (*)
   └─ json 1.3.0 (*)

(*) marks a shared dependency already printed above, so it is shown once.

Four subcommands, one small binary

Zero dependencies, pure Rust standard library. Point qm at a manifest and a registry and pick what you want back.

resolve

Pin every version

Runs the PubGrub solver and prints one exact version for the project and every transitive dependency, or the reason none exists.

tree

See the shape

The resolved set as a dependency tree, with shared packages marked (*) so each one is printed a single time.

lock

Freeze it

Emit a lockfile of the resolved versions, so the same set is reproduced on the next install.

explain

Read the failure

When there is no solution, render the derivation as a numbered proof that ends at the exact two requirements that collide.

# build, then run any subcommand
cargo build
./target/debug/qm resolve examples/app.qm --registry examples/registry.qm
./target/debug/qm tree    examples/app.qm --registry examples/registry.qm
./target/debug/qm lock    examples/app.qm --registry examples/registry.qm
./target/debug/qm explain examples/conflict.qm --registry examples/registry.qm