cargo tree -d --format "{p} {f}"
for my project shows this:
memchr v2.6.4 alloc,default,std
├── async-compression v0.3.15 flate2,gzip,tokio
│ └── reqwest v0.11.13...
...
memchr v2.6.4 alloc,std
└── nom v7.1.1 alloc,std
├── cexpr v0.6.0
│ └── bindgen v0.64.0 runtime
│ [build-dependencies]
...
└── nmea v0.2.0 default,std
And I can not understand what is going on.
At first, I thought that this is ok. cargo
treats build time dependencies and runtime dependencies as different things. So it can build memchr
twice, and not normalise dependencies as memchr v2.6.4 alloc,default,std
.
But nmea
is runtime dependency. So cargo
would build and link the same version of memchr
with different features twice to my application?
Or this is bug in cargo tree -d
?
alice
June 7, 2024, 11:58am
2
Cargo will compile the same library two times if it is used both as a runtime and build-time dependency. Cargo will never link together two different versions of the same library into the final binary (unless their version numbers are not semver compatible).
4 Likes
So it is bug in cargo tree -d
, which show that
my main crate depend on reqwest -> async-compression -> memchr v2.6.4 alloc,default,std
and nmea -> nom -> memchr v2.6.4 alloc,std
, where nmea
and reqwest
both are runtime dependecies?
kornel
June 7, 2024, 12:16pm
4
I think cargo tree output is technically correct, it's just presented in an unclear way.
the copy of memchr is for bindgen which runs at build time, so gets a separate debug build of memchr and all other dependencies. Cargo isn't trying to reuse optimized runtime dependencies for unoptimized build scripts.
1 Like
bindgen
yes it is build time dependency, but nmea
which also depend on memchr
without default
feature on, is runtime dependency, not build time.
But cargo tree
join bindgen
and nmea
into one subtree.
Simple Cargo.toml to reproduce problem:
[package]
name = "foo"
version = "0.1.0"
edition = "2021"
[dependencies]
async-compression = "=0.3.15"
nmea = "=0.2.0"
[build-dependencies]
bindgen = { version = "0.64.0", default-features = false, features = ["runtime"] }
cargo tree -d
output:
❯ cargo tree -d
memchr v2.7.2
└── async-compression v0.3.15
└── foo v0.1.0 (/private/tmp/foo)
memchr v2.7.2
└── nom v7.1.3
├── cexpr v0.6.0
│ └── bindgen v0.64.0
│ [build-dependencies]
│ └── foo v0.1.0 (/private/tmp/foo)
└── nmea v0.2.0
└── foo v0.1.0 (/private/tmp/foo)
Looks like cargo tree
and cargo check/build/test
uses different algorithm to calculate dependency graph So this makes cargo tree
partly useless:
opened 06:32PM - 18 Jun 21 UTC
C-bug
Command-tree
**Problem**
There are some situations where `cargo tree` will mark a package as… a duplicate `(*)` when it probably shouldn't (and shows the wrong features with `--no-dedupe`). This arises when a package is built twice with the same features, but with different dependencies.
A real-world example is when looking at the following:
```toml
[package]
name = "foo"
version = "0.1.0"
resolver = "2"
[dependencies]
diesel = { version = "=1.4.7", features = ["postgres"] }
diesel_migrations = "=1.4.0"
```
Running `cargo tree -f '{p} {f}'` results in:
```
~/Proj/rust/cargo/scratch/diesel-issue> cargo tree -f '{p} {f}'
diesel-issue v0.1.0 (/Users/eric/Proj/rust/cargo/scratch/diesel-issue)
├── diesel v1.4.7 32-column-tables,bitflags,default,postgres,pq-sys,with-deprecated
│ ├── bitflags v1.2.1 default
│ ├── byteorder v1.4.3 default,std
│ ├── diesel_derives v1.4.1 (proc-macro) default,postgres
│ │ ├── proc-macro2 v1.0.27 default,proc-macro
│ │ │ └── unicode-xid v0.2.2 default
│ │ ├── quote v1.0.9 default,proc-macro
│ │ │ └── proc-macro2 v1.0.27 default,proc-macro (*)
│ │ └── syn v1.0.73 clone-impls,default,derive,extra-traits,fold,full,parsing,printing,proc-macro,quote
│ │ ├── proc-macro2 v1.0.27 default,proc-macro (*)
│ │ ├── quote v1.0.9 default,proc-macro (*)
│ │ └── unicode-xid v0.2.2 default
│ └── pq-sys v0.4.6
└── diesel_migrations v1.4.0 default
├── migrations_internals v1.4.1 default
│ └── diesel v1.4.7 32-column-tables,bitflags,default,postgres,pq-sys,with-deprecated (*)
└── migrations_macros v1.4.2 (proc-macro) default
├── migrations_internals v1.4.1 default (*) <-- PROBLEM IS HERE
├── proc-macro2 v1.0.27 default,proc-macro (*)
├── quote v1.0.9 default,proc-macro (*)
└── syn v1.0.73 clone-impls,default,derive,extra-traits,fold,full,parsing,printing,proc-macro,quote (*)
```
The problem is the diesel-issue → diesel_migrations → migrations_macros → migrations_internals. It has a `(*)` to indicate that it is duplicated. However, migrations_internals is built twice, and this is deceiving because the second migrations_internals has a dependency on diesel with no features.
Running with `--no-dedupe` is even worse, because it shows the wrong features for `diesel` under migrations_internals.
**Steps**
The following is an example in Cargo's testsuite format:
```rust
#[cargo_test]
fn dedupe_transitive_features2() {
Package::new("differs", "1.0.0")
.feature("some-feat", &[])
.publish();
Package::new("shared", "1.0.0")
.dep("differs", "1.0")
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
resolver = "2"
[dependencies]
shared = "1.0"
differs = {version="1.0", features=["some-feat"]}
bar = {path="bar"}
[build-dependencies]
"#,
)
.file("src/lib.rs", "")
.file(
"bar/Cargo.toml",
r#"
[package]
name = "bar"
version = "0.1.0"
[build-dependencies]
shared = "1.0"
"#,
)
.file("bar/src/lib.rs", "")
.build();
// ISSUE HERE: The last `shared` shouldn't be `(*)`
p.cargo("tree -f")
.arg("{p} [{f}]")
.with_stdout(
"\
foo v0.1.0 [..]
├── bar v0.1.0 [..]
│ [build-dependencies]
│ └── shared v1.0.0 []
│ └── differs v1.0.0 []
├── differs v1.0.0 [some-feat]
└── shared v1.0.0 [] (*)
",
)
.run();
}
// ISSUE HERE: The last `differs` shows with no features!
p.cargo("tree --no-dedupe -f")
.arg("{p} [{f}]")
.with_stdout(
"\
foo v0.1.0 [..]
├── bar v0.1.0 [..]
│ [build-dependencies]
│ └── shared v1.0.0 []
│ └── differs v1.0.0 []
├── differs v1.0.0 [some-feat]
└── shared v1.0.0 []
└── differs v1.0.0 [some-feat]
",
)
.run();
```
**Possible Solution(s)**
The issue is that `cargo tree` doesn't have the same logic that was added in #8701 to accommodate dependencies that are the same, but link to different dependencies.
I have toyed with the idea of changing `cargo tree` to use the `UnitGraph` computed for a normal build instead of trying to recreate how some of these things are computed. There are some complexities and downsides to that approach, but I continue to feel that trying to replicate this logic in multiple places is not a good idea.
**Notes**
Output of `cargo version`: cargo 1.54.0-nightly (44456677b 2021-06-12)
3 Likes
system
Closed
September 5, 2024, 3:15pm
8
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.