From 7225a8e577f9b9b9b4f80232585aee3589c7922e Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Mon, 28 Jul 2025 18:57:13 -0700 Subject: [PATCH 1/2] test(bindeps): reveal a bug of the propagation of artifact dependency Add 2 test cases to reproduce a bug of `-Zbindeps`. Basically, if - a package `foo` has an artifact dependency `artifact` with a specified target TARGET_ARTIFACT different from host TARGET_HOST, - `artifact` depends on proc macro `macro`, - `macro` conditionally depends on `arch` on TARGET_HOST, cargo build would panic on TARGET_HOST with the following error message: ``` did not find features for (PackageId { name: "arch", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo/arch" }, ArtifactDep(CompileTarget { name: "x86_64-apple-darwin" })) within activated_features: [ ( PackageId { name: "foo", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo", }, NormalOrDev, ), ( PackageId { name: "macro", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo/macro", }, ArtifactDep( CompileTarget { name: "x86_64-apple-darwin", }, ), ), ( PackageId { name: "artifact", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo/artifact", }, ArtifactDep( CompileTarget { name: "x86_64-apple-darwin", }, ), ), ] ``` Similar panic happens when - a package `foo` has an artifact dependency `artifact` with a specified target TARGET_ARTIFACT different from host TARGET_HOST, - `artifact` has a build dependency `builder`, - `builder` conditionally depends on `arch` on TARGET_HOST. Signed-off-by: Changyuan Lyu --- tests/testsuite/artifact_dep.rs | 178 ++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index 1850f6184ed..16b11beca14 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -3394,3 +3394,181 @@ staticlib present: true "#]], ); } + +#[should_panic] +#[cargo_test] +fn artifact_dep_target_does_not_propagate_to_deps_of_build_script() { + if cross_compile_disabled() { + return; + } + let bindeps_target = cross_compile::alternate(); + let native_target = cross_compile::native(); + + let p = project() + .file( + "Cargo.toml", + &r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + resolver = "2" + + [dependencies.artifact] + path = "artifact" + artifact = "bin" + target = "$TARGET" + "# + .replace("$TARGET", bindeps_target), + ) + .file( + "src/main.rs", + r#" + fn main() { + let _b = include_bytes!(env!("CARGO_BIN_FILE_ARTIFACT")); + } + "#, + ) + .file( + "artifact/Cargo.toml", + r#" + [package] + name = "artifact" + version = "0.0.1" + edition = "2015" + + [build-dependencies] + builder = { path = "../builder" } + "#, + ) + .file("artifact/src/main.rs", "fn main() { }") + .file( + "artifact/build.rs", + r#" + extern crate builder; + fn main() { + let _ = builder::add(1, 2); + } + "#, + ) + .file( + "builder/Cargo.toml", + &r#" + [package] + name = "builder" + version = "0.0.1" + edition = "2015" + + [target.'$TARGET'.dependencies] + arch = { path = "../arch" } + "# + .replace("$TARGET", native_target), + ) + .file( + "builder/src/lib.rs", + r#" + extern crate arch; + pub fn add(a: i32, b: i32) -> i32 { arch::add(a, b) } + "#, + ) + .file( + "arch/Cargo.toml", + r#" + [package] + name = "arch" + version = "0.0.1" + edition = "2015" + "#, + ) + .file( + "arch/src/lib.rs", + r#"pub fn add(a: i32, b: i32) -> i32 { a + b }"#, + ) + .build(); + p.cargo("test -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps"]) + .run(); +} + +#[should_panic] +#[cargo_test] +fn artifact_dep_target_does_not_propagate_to_proc_macro() { + if cross_compile_disabled() { + return; + } + let bindeps_target = cross_compile::alternate(); + let native_target = cross_compile::native(); + + let p = project() + .file( + "Cargo.toml", + &r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + resolver = "2" + + [dependencies.artifact] + path = "artifact" + artifact = "bin" + target = "$TARGET" + "# + .replace("$TARGET", bindeps_target), + ) + .file( + "src/main.rs", + r#" + fn main() { + let _b = include_bytes!(env!("CARGO_BIN_FILE_ARTIFACT")); + } + "#, + ) + .file( + "artifact/Cargo.toml", + r#" + [package] + name = "artifact" + version = "0.0.1" + edition = "2015" + + [dependencies] + macro = { path = "../macro" } + "#, + ) + .file("artifact/src/main.rs", "fn main() { }") + .file( + "macro/Cargo.toml", + &r#" + [package] + name = "macro" + version = "0.0.1" + edition = "2015" + + [lib] + proc-macro = true + + [target.'$TARGET'.dependencies] + arch = { path = "../arch" } + "# + .replace("$TARGET", native_target), + ) + .file("macro/src/lib.rs", "") + .file( + "arch/Cargo.toml", + r#" + [package] + name = "arch" + version = "0.0.1" + edition = "2015" + "#, + ) + .file( + "arch/src/lib.rs", + "pub fn add(a: i32, b: i32) -> i32 { a + b }", + ) + .build(); + p.cargo("test -Z bindeps") + .masquerade_as_nightly_cargo(&["bindeps"]) + .run(); +} From b54633809b04ee5c4ce8bbf7cc880639f72dccd7 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Sat, 26 Jul 2025 21:03:17 -0700 Subject: [PATCH 2/2] fix(bindeps): do not propagate artifact dependency to proc macro or build deps As reproduced in the 2 fixed test cases of this commit, cargo panicked when - a package `foo` has an artifact dependency `artifact` with a specified target TARGET_ARTIFACT different from host TARGET_HOST, - `artifact` depends on proc macro `macro`, - `macro` conditionally depends on `arch` on TARGET_HOST, with the follwing message ``` did not find features for (PackageId { name: "arch", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo/arch" }, ArtifactDep(CompileTarget { name: "x86_64-apple-darwin" })) within activated_features: [ ( PackageId { name: "foo", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo", }, NormalOrDev, ), ( PackageId { name: "macro", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo/macro", }, ArtifactDep( CompileTarget { name: "x86_64-apple-darwin", }, ), ), ( PackageId { name: "artifact", version: "0.0.1", source: "/Users/lencerf/Developer/cargo/target/tmp/cit/t0/foo/artifact", }, ArtifactDep( CompileTarget { name: "x86_64-apple-darwin", }, ), ), ] ``` From the above message, it is clear that proc macro `macro` was wrongly associated with `FeaturesFor::ArtifactDep` instead of `FeaturesFor::HostDep`. Package `arch` was later ignored because cargo thought `macro` should be built for TARGET_ARTIFACT (x86_64-apple-darwin), while `arch` was conditionally needed on TARGET_HOST (aarch64-apple-darwin). Similar analyses apply to the other test case. This commit fixes 2 paths: - when resolving features, if we encounter build dependencies or proc macros, always associate them with `FeaturesFor::HostDep`. - when deriving UnitFor for dependencies, stop propagating artifact_target_for_features if the the dependency is a build dependency or a proc macro. Signed-off-by: Changyuan Lyu --- src/cargo/core/profiles.rs | 8 +++++++- src/cargo/core/resolver/features.rs | 10 +++++----- tests/testsuite/artifact_dep.rs | 2 -- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index de660380431..bad9ef77dc4 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -1177,12 +1177,18 @@ impl UnitFor { } else { self.panic_setting }; + let artifact_target_for_features = + if dep_target.proc_macro() || parent.target.is_custom_build() { + None + } else { + self.artifact_target_for_features + }; UnitFor { host: self.host || dep_for_host, host_features, panic_setting, root_compile_kind, - artifact_target_for_features: self.artifact_target_for_features, + artifact_target_for_features, } } diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index e7d4da8d1f4..5355bd871d5 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -906,11 +906,11 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { // All this may result in a dependency being built multiple times // for various targets which are either specified in the manifest // or on the cargo command-line. - let lib_fk = if fk == FeaturesFor::default() { - (self.track_for_host - && (dep.is_build() || self.has_proc_macro_lib(dep_id))) - .then(|| FeaturesFor::HostDep) - .unwrap_or_default() + let lib_fk = if fk != FeaturesFor::HostDep + && self.track_for_host + && (dep.is_build() || self.has_proc_macro_lib(dep_id)) + { + FeaturesFor::HostDep } else { fk }; diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index 16b11beca14..fb520f5fa3d 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -3395,7 +3395,6 @@ staticlib present: true ); } -#[should_panic] #[cargo_test] fn artifact_dep_target_does_not_propagate_to_deps_of_build_script() { if cross_compile_disabled() { @@ -3490,7 +3489,6 @@ fn artifact_dep_target_does_not_propagate_to_deps_of_build_script() { .run(); } -#[should_panic] #[cargo_test] fn artifact_dep_target_does_not_propagate_to_proc_macro() { if cross_compile_disabled() {