Skip to content

Non-deterministic builds with new resolver #8549

Closed
@kylefleming

Description

@kylefleming

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 enables subdep's feat feature)
  • When building both bins in the same cargo build invocation, crate B's build-dependency on dep is built against a build of subdep 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

Metadata

Metadata

Assignees

Labels

A-features2Area: issues specifically related to the v2 feature resolverC-bugCategory: bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions