Fix cargo resolver pruning of optional/feature-gated dependencies#191
Fix cargo resolver pruning of optional/feature-gated dependencies#191evandowning wants to merge 1 commit into
Conversation
The cargo resolver marked every declared dependency of a package as already-resolved, including optional/feature-gated deps that `cargo metadata` does not activate under the default feature set. Those deps resolved to nothing and were pruned, collapsing it-depends' "resolve all possible versions" behavior to the single locked tree for cargo projects. Only mark a dependency resolved when `cargo metadata` actually added a matching package to the cache, so inactive optional deps fall through to normal resolution instead of being pruned. Also wrap the `cargo metadata` subprocess call in `get_dependencies` with CalledProcessError handling, mirroring pip.py (#125), so one unresolvable crate no longer aborts the entire resolution. Fixes #189 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ran the siderophile reproduction + re-enabled smoke testThe PR note said Reproduction — confirms the fix works
Smoke test — fails on one invariant, but it's baseline drift, not a regressionThe only failure is: Root cause, verified from the resolved graph:
So 🤖 Generated with Claude Code |
Fixes #189.
Problem
The cargo resolver marked every declared dependency of a package as already-resolved. But
cargo metadatadoes not add optional/feature-gated dependencies to its resolved package set when the active feature set doesn't enable them. Those deps therefore matched no package in the cache, andresolution.pypruned them and their entire subtrees.Net effect: for cargo projects, it-depends' core "resolve all possible dependency versions" behavior collapsed to "the single locked dependency tree" (e.g.
rayon,backtrace,afland ~30 other crates reachable only via optional edges were dropped for thesiderophilefixture).Changes
resolve_from_source— collect all packages first, then onlyset_resolved(dep)when a matching package was actually added to the cache. Inactive optional deps fall through to normal resolution instead of being marked resolved-to-nothing and pruned. This also unifies the previously-separate handling of theSourcePackage's direct deps and transitive deps.get_dependencies— wrap thecargo metadatasubprocess call inexcept subprocess.CalledProcessError, mirroringpip.py's johnnydep handling (Add exception handling #125). Previously a single crate whose temp-projectcargo metadatafailed (exit 101) raisedCalledProcessErrorand aborted the entire resolution; the old pruning shortcut had inadvertently masked this by never resolving those crates.Tests
Added
test_resolve_from_source_only_marks_resolved_deps, which mocksget_dependencies(nocargobinary required) and verifies that a dep with a matching resolved package is marked resolved while optional deps with no matching package are left unresolved.Open question (draft)
Fix (1) restores full "all possible versions" expansion, which is large — the issue reports ~963 crates for
siderophilevs. 163 in the 2021 baseline and 129 on currentmaster. Per the issue, whether the intended cargo semantics are the full possible set, the locked tree, or a middle ground (e.g. union of all features at locked versions) should be decided deliberately. Opening as a draft so that semantics decision can be settled before merge.Note:
cargois not installed in this environment, so the end-to-endsiderophilereproduction and the re-enabled smoke test (#181) were not run here — CI should exercise those.🤖 Generated with Claude Code