Skip to content

jamesgober/dev-deps

Repository files navigation

Rust logo
dev-deps
DEPENDENCY HYGIENE FOR RUST CRATES

crates.io downloads CI MSRV docs.rs

Detect unused, outdated, and many-major-versions-behind dependencies. Wraps cargo-udeps + cargo-outdated, emits a structured verdict.


Part of the dev-* verification collection.
Also available as the deps feature of the dev-tools umbrella crate — one dependency, every verification layer.


What it does

dev-deps answers two questions about your dependency tree:

  • Are any declared dependencies actually unused?
  • Are any outdated, and by how many major versions?

It wraps cargo-udeps and cargo-outdated and emits findings as a dev-report::Report so AI agents and CI gates can act on them programmatically.

Quick start

[dependencies]
dev-deps = "0.9"

One-time tool install:

cargo install cargo-udeps cargo-outdated
rustup toolchain install nightly      # cargo-udeps requires nightly

Drive it from code:

use dev_deps::{DepCheck, DepScope};

let check = DepCheck::new("my-crate", "0.1.0").scope(DepScope::All);
let result = check.execute()?;
let report = result.into_report();
println!("{}", report.to_json()?);
# Ok::<(), Box<dyn std::error::Error>>(())

Scopes

Scope What it runs
DepScope::Unused cargo +nightly udeps --output json only.
DepScope::Outdated cargo outdated --format json only.
DepScope::All Both.

Severity policy

Finding dev-report::Severity
Unused dependency Warning
Outdated, 0–1 major behind Info
Outdated, 2+ majors behind Warning
Outdated, ≥ escalate_at_majors behind Error (failing)

By default, every finding is a Warn-verdict check — dependency health is advisory, not blocking. Call .escalate_at_majors(n) on the builder to make findings at least n majors behind produce a failing CheckResult instead.

Allow-list, exclude, and severity threshold

use dev_deps::{DepCheck, DepScope};
use dev_report::Severity;

let check = DepCheck::new("my-crate", "0.1.0")
    .scope(DepScope::All)
    .workspace()                          // pass --workspace to both tools
    .exclude("vendored-crate")            // skip a whole crate
    .allow("legacy-shim")                 // skip a single advisory ID / crate name
    .allow_all(["a", "b"])
    .severity_threshold(Severity::Warning) // drop Info findings
    .escalate_at_majors(3);                // fail when 3+ majors behind

let _result = check.execute()?;
# Ok::<(), Box<dyn std::error::Error>>(())

Producer integration

DepProducer plugs the check into a multi-producer pipeline driven by dev-tools:

use dev_deps::{DepCheck, DepProducer, DepScope};
use dev_report::Producer;

let producer = DepProducer::new(
    DepCheck::new("my-crate", "0.1.0").scope(DepScope::All),
);

let report = producer.produce();
println!("{}", report.to_json().unwrap());

Subprocess failures map to a single failing CheckResult named deps::health with Severity::Critical — the pipeline keeps running.

Wire format

DepResult, UnusedDep, OutdatedDep, DepScope, and DepKind are all serde-derived. JSON output uses snake_case field names and omits optional fields when they are None:

{
  "name": "my-crate",
  "version": "0.1.0",
  "scope": "all",
  "unused": [
    { "crate_name": "legacy", "kind": "development" }
  ],
  "outdated": [
    {
      "crate_name": "serde",
      "current": "1.0.0",
      "latest": "2.0.0",
      "major_behind": 1,
      "kind": "normal"
    }
  ]
}

Examples

File What it shows
examples/basic.rs Full check (All scope); graceful tool-missing handling.
examples/unused_only.rs Unused scope only.
examples/outdated_only.rs Outdated scope only.
examples/producer.rs DepProducer (gated by DEV_DEPS_EXAMPLE_RUN).

Requirements

Both tools must be installed:

cargo install cargo-udeps cargo-outdated
rustup toolchain install nightly      # cargo-udeps requires nightly

The crate detects absence of either tool and surfaces a typed DepError variant rather than panicking.

Runtime dependency footprint: dev-report, serde, serde_json.

Migration from 0.1.0

UnusedDep::kind was a String in 0.1.0; it is now a typed DepKind enum. OutdatedDep also gained an optional kind field. If you constructed these struct literals in 0.1.0, update:

# use dev_deps::{DepKind, OutdatedDep, UnusedDep};
let _unused = UnusedDep {
    crate_name: "foo".into(),
    kind: DepKind::Normal,           // was: String
};

let _outdated = OutdatedDep {
    crate_name: "bar".into(),
    current: "1.0.0".into(),
    latest: "2.0.0".into(),
    major_behind: 1,
    kind: Some(DepKind::Normal),     // new in 0.9.0
};

The constructor surface (DepCheck::new, DepScope variants, DepResult::into_report) is unchanged.

The dev-* collection

dev-deps ships independently and is also re-exported by the dev-tools umbrella crate as the deps feature. Sister crates cover the other verification dimensions:

Status

v0.9.x is the pre-1.0 stabilization line. The API is feature-complete for unused-dependency detection, outdated-version detection, major-lag escalation, allow-listing, and severity gating. Production use is fine; 1.0 will pin the public API and the wire format.

Minimum supported Rust version

1.85 — pinned in Cargo.toml via rust-version and verified by the MSRV job in CI.

License

Apache-2.0. See LICENSE.


Copyright © 2026 James Gober.

About

Dependency health checking for Rust. Unused, outdated, policy-violating deps. Wraps cargo-udeps and cargo-outdated. Part of the dev-* verification suite.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages