Detecting different feature sets of transitive dependencies

I am working with a Cargo workspace containing many member crates. My team and I are a little confused by some behavior of Cargo.

When I execute cargo build and look at the standard output of the build process it seems like everything in the workspace is built. It also mentions "building ... [PACKAGE] (bin)" where [PACKAGE] is member crate with a main.rs file.

However, when I then run cargo build -p [PACKAGE], cargo seems to perform an additional build step. It takes quite a lot of extra time on top of the cargo build. After some research online, I found that cargo build does not seem to build all the binaries.

Why does the output from cargo build display that it is building [PACKAGE] (bin) even though it actually needs to perform additional steps to actually build that binary with another command? Is this intended behavior?

I find this confusing behavior.

  • It gives the impression to users that cargo is not "trustworthy" and doesn't always do what it says it does.
  • Besides, why does cargo build not build binaries, but cargo build -p does build binaries? This seems inconsistent to me.

This should be the official documentation on which packages exactly are built with simple cargo built depending on things like the workspace's configuration or the current working directory that the command is called from: cargo build - The Cargo Book

You mean that running cargo build -p [PACKAGE] is compiling some packages that cargo build already said it compiled? One of your dependencies likely has different features enabled between cargo build and cargo build -p [PACKAGE] triggering a rebuild of it and all dependent crates. If you have bin_a which depends on some_lib with feature a and bin_b which depends on some_lib without feature a, building bin_a and bin_b using a single cargo build will build some_lib with feature a and use it for both bin_a and bin_b. However if you only build bin_b, cargo will build some_lib without feature a triggering a rebuild.

4 Likes

Hi Bjorn3, I have investigated it further and indeed, when I run

cargo tree --prefix none -f "{p}: {f}" -p PACKAGE | sort | uniq

and compare it with the same command without -p, I see a reduced set of features for the same (transitive) dependencies. It seems like the workspace builds the cc crate for example with one more feature enabled compared to the package (if specified).

I have tried automating the process of detecting this kind of situation. I wrote a program that takes the output of cargo metadata and builds a build graph. It then checks which packages are roots and also tries to check whether there are identical transitive dependencies with different feature sets.

However, I run into the problem that I cannot convince cargo metadata to "forget" about the other sister crates of the package I am investigating. This means that it automatically takes the largest possible feature set to cover all uses of a transitive dependency, even in sister crates that are not directly related.

I have tried manually passing the manifest path of the package to cargo metadata and changing the working directory to the directory of the package. But these things do not seem to help. Cargo still takes all packages in the workspace into account.

I would like a kind of behaviour shows the real behaviour of cargo build -p PACKAGE, similar to cargo tree. Is there a similar library or tool that can do this?

Yeah, that is a known limitation of cargo metadata unfortunately. Basically the v1 resolver among other things did take the union of all packages and cargo metadata is hard coded to use the v1 resolver, even in a v2 workspace. My understanding is that there are fundamental differences in the v2 resolver that make it hard to keep producing the same output format as cargo metadata currently produces.