Summary
graphify --cargo silently drops every internal workspace dependency edge whose entry uses Cargo's package = "..." rename. The affected crate node still exists, but the crate_depends_on edge to it never appears in the graph.
Where
graphify/cargo_introspect.py:76-96:
for source_name, (source_id, manifest, data) in sorted(crates.items()):
dependencies = data.get("dependencies", {})
if not isinstance(dependencies, dict):
continue
source_file = manifest.relative_to(root_path).as_posix()
for dependency_name in sorted(dependencies):
target = crates.get(dependency_name)
if target is None:
continue
edges.append({...})
The loop iterates the [dependencies] table KEYS. crates is keyed by each member's [package].name (line 63). Cargo's rename mechanism lets those diverge:
[dependencies]
db = { path = "../storage", package = "internal-storage" }
Here the key is db (the name used in use db::…;) but the target crate at ../storage/Cargo.toml has [package] name = "internal-storage". crates.get("db") returns None, the edge is skipped, and there is nothing in the log to say a dep was dropped.
This pattern is common in monorepos that want short local names (db, mq, auth) without colliding with crates.io names, and in workspaces that vendor two versions of the same crate under different local aliases. Documented in the Cargo book under Specifying dependencies → Renaming dependencies in Cargo.toml.
Reproduction
# workspace root Cargo.toml
[workspace]
members = ["storage", "app"]
# app/Cargo.toml
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
db = { path = "../storage", package = "internal-storage" }
# storage/Cargo.toml
[package]
name = "internal-storage"
version = "0.1.0"
edition = "2021"
Run graphify with --cargo on the workspace root. Observe:
app node ✅
internal-storage node ✅
app -> internal-storage edge ❌ (missing)
Replace db = { path = "…", package = "internal-storage" } with internal-storage = { path = "…" } and the edge appears.
Suggested fix
When the dep entry is an inline table, honor its package override before looking up crates:
for dep_key, spec in sorted(dependencies.items()):
real_name = dep_key
if isinstance(spec, dict):
pkg = spec.get("package")
if isinstance(pkg, str) and pkg:
real_name = pkg
target = crates.get(real_name)
if target is None:
continue
edges.append({...})
Same shape as the existing loop, but keys the lookup off the real package name.
Happy to open a PR with a fixture that adds a rename-based dep and asserts the edge is emitted.
Summary
graphify --cargosilently drops every internal workspace dependency edge whose entry uses Cargo'spackage = "..."rename. The affected crate node still exists, but thecrate_depends_onedge to it never appears in the graph.Where
graphify/cargo_introspect.py:76-96:The loop iterates the
[dependencies]table KEYS.cratesis keyed by each member's[package].name(line 63). Cargo's rename mechanism lets those diverge:Here the key is
db(the name used inuse db::…;) but the target crate at../storage/Cargo.tomlhas[package] name = "internal-storage".crates.get("db")returnsNone, the edge is skipped, and there is nothing in the log to say a dep was dropped.This pattern is common in monorepos that want short local names (
db,mq,auth) without colliding with crates.io names, and in workspaces that vendor two versions of the same crate under different local aliases. Documented in the Cargo book under Specifying dependencies → Renaming dependencies in Cargo.toml.Reproduction
Run
graphifywith--cargoon the workspace root. Observe:appnode ✅internal-storagenode ✅app -> internal-storageedge ❌ (missing)Replace
db = { path = "…", package = "internal-storage" }withinternal-storage = { path = "…" }and the edge appears.Suggested fix
When the dep entry is an inline table, honor its
packageoverride before looking upcrates:Same shape as the existing loop, but keys the lookup off the real package name.
Happy to open a PR with a fixture that adds a rename-based dep and asserts the edge is emitted.