Description
Problem
When using the new resolver, I'm encountering a bug where cargo seems to be unifying features inconsistently when a feature is enable on a build-dependency that is also the dependency of another dependency, when building with multiple roots (e.g. cargo build --bin bin1 --bin bin2
).
It's a bit hard to narrow down the exact circumstances under which this seems to be occurring since the configuration is somewhat complex, however, I've noticed it in several other configurations, not just the one below.
The essentials seem to be:
- Crate A defines a bin,
bin1
- Crate B defines a bin,
bin2
- Both crates are in the same workspace
- Crate A declares a dependency on a lib,
dep
- Crate B declares a build-dependency on the same lib, but doesn't also declare it as a normal dependency
- The lib,
dep
, is in the same configuration when declared as crate A's dependency as it is when declared as crate B's build-dependency - The lib,
dep
, declares another dependenncy,subdep
- This other dependency,
subdep
, is configured differently between crate A's dependency tree and crate B's build-dependency tree (in the example below, crate B's build-dependency declaration enablessubdep
'sfeat
feature) - When building both bins in the same
cargo build
invocation, crate B's build-dependency ondep
is built against a build ofsubdep
that is sometimes compiled in the configuration of crate A's dependency tree and sometimes compiled in the configuration of crate B's build-dependency tree.
Note: In this explanation of what's going on, I'm making the assumption that -Zfeatures=host_dep
is intended to completely separate the feature unification of build-dependencies
from normal dependencies. However, even if this assumption is incorrect, there is still a bug related to build determinism, since no matter the intended behavior of feature unification, I'm assuming cargo is not meant to unify features arbitrarily between successive invocations.
Steps
The following script illustrates the issue:
echo '[workspace]' > Cargo.toml
echo 'members = ["bin1", "bin2"]' >> Cargo.toml
cargo new --bin bin1
echo 'dep = { path = "../dep" }' >> bin1/Cargo.toml
cargo new --bin bin2
echo '[build-dependencies]' >> bin2/Cargo.toml
echo 'dep = { path = "../dep" }' >> bin2/Cargo.toml
echo 'subdep = { path = "../subdep", features = ["feat"] }' >> bin2/Cargo.toml
echo 'fn main() { dep::feat_func(); }' > bin2/build.rs
cargo new --lib dep
echo 'subdep = { path = "../subdep" }' >> dep/Cargo.toml
echo 'pub fn feat_func() { subdep::feat_func(); }' > dep/src/lib.rs
cargo new --lib subdep
echo '[features]' >> subdep/Cargo.toml
echo 'feat = []' >> subdep/Cargo.toml
echo 'pub fn feat_func() {' > subdep/src/lib.rs
echo ' #[cfg(feature = "feat")] println!("feat: enabled");' >> subdep/src/lib.rs
echo ' #[cfg(not(feature = "feat"))] println!("feat: not enabled");' >> subdep/src/lib.rs
echo '}' >> subdep/src/lib.rs
for i in {1..6}; do
cargo build -vv --bin bin1 --bin bin2 -Zunstable-options -Zfeatures=host_dep 2>&1 | grep -v ' Running'
done
Sample output (the relevant parts only):
Created binary (application) `bin1` package
Created binary (application) `bin2` package
Created library `dep` package
Created library `subdep` package
Compiling subdep v0.1.0 (/Users/kyle/code/experiments/resolver-test/subdep)
Compiling dep v0.1.0 (/Users/kyle/code/experiments/resolver-test/dep)
Compiling bin2 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin2)
Compiling bin1 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin1)
[bin2 0.1.0] feat: enabled
Finished dev [unoptimized + debuginfo] target(s) in 2.43s
Fresh subdep v0.1.0 (/Users/kyle/code/experiments/resolver-test/subdep)
Fresh dep v0.1.0 (/Users/kyle/code/experiments/resolver-test/dep)
Fresh bin1 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin1)
Fresh bin2 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin2)
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Compiling subdep v0.1.0 (/Users/kyle/code/experiments/resolver-test/subdep)
Compiling dep v0.1.0 (/Users/kyle/code/experiments/resolver-test/dep)
Compiling bin2 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin2)
Compiling bin1 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin1)
[bin2 0.1.0] feat: not enabled
Finished dev [unoptimized + debuginfo] target(s) in 0.80s
Fresh subdep v0.1.0 (/Users/kyle/code/experiments/resolver-test/subdep)
Fresh dep v0.1.0 (/Users/kyle/code/experiments/resolver-test/dep)
Fresh bin1 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin1)
Fresh bin2 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin2)
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Fresh subdep v0.1.0 (/Users/kyle/code/experiments/resolver-test/subdep)
Fresh dep v0.1.0 (/Users/kyle/code/experiments/resolver-test/dep)
Compiling bin1 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin1)
Compiling bin2 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin2)
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
Fresh subdep v0.1.0 (/Users/kyle/code/experiments/resolver-test/subdep)
Fresh dep v0.1.0 (/Users/kyle/code/experiments/resolver-test/dep)
Fresh bin1 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin1)
Fresh bin2 v0.1.0 (/Users/kyle/code/experiments/resolver-test/bin2)
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Notes
% cargo version
cargo 1.47.0-nightly (aa6872140 2020-07-23)
% rustc --version
rustc 1.47.0-nightly (6c8927b0c 2020-07-26)
% rustup show active-toolchain
nightly-x86_64-apple-darwin (default)
% uname -rs
Darwin 19.6.0