No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-24 08:20:03 +02:00
examples feat: init 2026-09-19 08:18:28 +02:00
src fix: persist explicit override lockfile updates and enforce locked resolution 2026-10-24 08:20:03 +02:00
tests fix: persist explicit override lockfile updates and enforce locked resolution 2026-10-24 08:20:03 +02:00
.gitignore feat: init 2026-09-19 08:18:28 +02:00
Cargo.lock fix: persist explicit override lockfile updates and enforce locked resolution 2026-10-24 08:20:03 +02:00
Cargo.toml feat: init 2026-09-19 08:18:28 +02:00
LICENSE-MIT feat: init 2026-09-19 08:18:28 +02:00
README.md fix: persist explicit override lockfile updates and enforce locked resolution 2026-10-24 08:20:03 +02:00

cargo-overrides

Experimental npm-style transitive dependency overrides for Cargo.

Cargo intentionally will not unify SemVer-incompatible requirements. This is especially noticeable for pre-1.0 crates, where 0.7 and 0.8 are incompatible ranges. cargo-overrides gives the workspace owner an escape hatch when they have verified that the newer version is actually source-compatible.

What it does

Given a graph like:

app
├── parent-a -> foo ^0.7
├── parent-b -> foo ^0.8
└── parent-c -> foo ^0.8

and:

# Cargo.overrides.toml
[overrides]
foo = "0.8.5"

cargo-overrides:

  1. runs cargo metadata;
  2. finds crates.io parent crates that declare a crates.io dependency on foo;
  3. copies those parents into target/cargo-overrides/shadow/;
  4. rewrites those dependency requirements to exactly =0.8.5 (including already-compatible ranges, so the requested version is pinned);
  5. generates a Cargo config with [patch.crates-io] entries pointing at the shadow crates;
  6. runs cargo metadata with that config to resolve/update the lockfile and verifies the requested crates.io version converged;
  7. runs the requested Cargo command with cargo --config <generated-config> ....

Cargo performs the final dependency resolution and writes Cargo.lock. Successful cargo overrides lock, cargo overrides exec update, and cargo overrides exec generate-lockfile keep their lockfile changes. Other commands restore the original lockfile afterward, including when they fail; failed updates also restore it. If no lockfile existed, restoration removes the temporary file. target/cargo-overrides/ contains disposable shadow crates and patch configuration.

--locked rejects a missing lockfile or any change required by the override graph, before running the wrapped command. --frozen additionally uses offline resolution. Generate and commit the override-aware lockfile first:

cargo overrides lock
cargo overrides exec build --locked

On a fresh checkout, the helper discovers registry parents and regenerates the shadow crates before validating the override graph against the original lockfile. Discovery temporarily restores registry source identities for locked shadow packages so Cargo retrieves their pinned versions rather than newer releases; this temporary discovery lockfile is discarded before the locked validation. The final lockfile is generated by Cargo, including its source/checksum checks. Plain Cargo does not apply these generated patches; use the helper consistently for builds that consume the override-aware lockfile.

Install

cargo install --path .

Then, from a Cargo workspace:

cargo overrides list
cargo overrides add foo@0.8.5
cargo overrides prepare
cargo overrides check
cargo overrides lock
cargo overrides exec build --release
cargo overrides exec test --all-features

You can also edit the file directly:

[overrides]
foo = "0.8.5"
bar = "0.12.3"

Important behavior

The override is intentionally stronger than Cargo's normal resolver: a dependency declaration such as:

foo = "0.7"

inside a crates.io parent can become:

foo = "=0.8.5"

in the generated shadow copy.

That may fail to compile or, worse, compile with unintended behavior. The root project is taking responsibility for violating the parent's declared compatibility range. Run tests.

Current MVP limitations

  • Only exact target versions are accepted (foo@0.8.5), not ranges.
  • Only crates.io parent packages are shadow-patched.
  • If a workspace member itself declares the incompatible requirement, edit that workspace manifest directly.
  • Git/alternate-registry parents are skipped. They are not patchable through [patch.crates-io], but they never cause an override command to fail.
  • The generated shadow directory is disposable and lives under target/cargo-overrides.
  • Builds that rely on these overrides must be invoked through cargo overrides ... (or use the generated Cargo config themselves). A lockfile alone cannot encode "ignore the parent's declared version requirement".

Why not edit Cargo.lock directly?

Cargo.lock describes a resolved graph. The incompatible requirement lives in the parent package's Cargo.toml. Forging a lockfile cannot make that requirement disappear; Cargo will validate/re-resolve the graph. Shadow-patching the parent manifest changes the input to the resolver, after which Cargo can generate a legitimate lockfile.

Files

Cargo.overrides.toml            # commit this
target/cargo-overrides/         # generated, do not commit
Cargo.lock                      # generated by Cargo as usual

Safety / CI suggestion

A useful CI sequence is:

cargo overrides check -- --all-targets --all-features
cargo overrides exec test --all-features

Treat an override as temporary technical debt. Prefer upgrading or upstreaming a relaxed dependency requirement when possible.