diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index 8ebe21b9002..fbabdccda36 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -161,7 +161,13 @@ fn deps_of_roots(roots: &[Unit], mut state: &mut State<'_, '_>) -> CargoResult<( // without, once for `--test`). In particular, the lib included for // Doc tests and examples are `Build` mode here. let unit_for = if unit.mode.is_any_test() || state.global_mode.is_rustc_test() { - UnitFor::new_test(state.config) + if unit.target.proc_macro() { + // Special-case for proc-macros, which are forced to for-host + // since they need to link with the proc_macro crate. + UnitFor::new_host_test(state.config) + } else { + UnitFor::new_test(state.config) + } } else if unit.target.is_custom_build() { // This normally doesn't happen, except `clean` aggressively // generates all units. diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index d4859cb2078..0dc7266feaa 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -967,6 +967,16 @@ impl UnitFor { } } + /// This is a special case for unit tests of a proc-macro. + /// + /// Proc-macro unit tests are forced to be run on the host. + pub fn new_host_test(config: &Config) -> UnitFor { + let mut unit_for = UnitFor::new_test(config); + unit_for.host = true; + unit_for.host_features = true; + unit_for + } + /// Returns a new copy based on `for_host` setting. /// /// When `for_host` is true, this clears `panic_abort_ok` in a sticky diff --git a/tests/testsuite/features2.rs b/tests/testsuite/features2.rs index 4807f1a7d26..e156888164c 100644 --- a/tests/testsuite/features2.rs +++ b/tests/testsuite/features2.rs @@ -1864,3 +1864,85 @@ warning: feat: enabled ) .run(); } + +#[cargo_test] +fn test_proc_macro() { + // Running `cargo test` on a proc-macro, with a shared dependency that has + // different features. + // + // There was a bug where `shared` was built twice (once with feature "B" + // and once without), and both copies linked into the unit test. This + // would cause a type failure when used in an intermediate dependency + // (the-macro-support). + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "runtime" + version = "0.1.0" + [dependencies] + the-macro = { path = "the-macro", features = ['a'] } + [build-dependencies] + shared = { path = "shared", features = ['b'] } + "#, + ) + .file("src/lib.rs", "") + .file( + "the-macro/Cargo.toml", + r#" + [package] + name = "the-macro" + version = "0.1.0" + [lib] + proc-macro = true + test = false + [dependencies] + the-macro-support = { path = "../the-macro-support" } + shared = { path = "../shared" } + [dev-dependencies] + runtime = { path = ".." } + [features] + a = [] + "#, + ) + .file( + "the-macro/src/lib.rs", + " + fn _test() { + the_macro_support::foo(shared::Foo); + } + ", + ) + .file( + "the-macro-support/Cargo.toml", + r#" + [package] + name = "the-macro-support" + version = "0.1.0" + [dependencies] + shared = { path = "../shared" } + "#, + ) + .file( + "the-macro-support/src/lib.rs", + " + pub fn foo(_: shared::Foo) {} + ", + ) + .file( + "shared/Cargo.toml", + r#" + [package] + name = "shared" + version = "0.1.0" + [features] + b = [] + "#, + ) + .file("shared/src/lib.rs", "pub struct Foo;") + .build(); + p.cargo("test -Zfeatures=all --manifest-path the-macro/Cargo.toml") + .masquerade_as_nightly_cargo() + .run(); +}