diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index fdb49c0bf9f..807bdc4533a 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -426,10 +426,12 @@ pub struct FeatureResolver<'a, 'gctx> { /// If this is `true`, then a non-default `feature_key` needs to be tracked while /// traversing the graph. /// - /// This is only here to avoid calling `is_proc_macro` when all feature - /// options are disabled (because `is_proc_macro` can trigger downloads). - /// This has to be separate from `FeatureOpts.decouple_host_deps` because + /// This is only here to avoid calling [`has_any_proc_macro`] when all feature + /// options are disabled (because [`has_any_proc_macro`] can trigger downloads). + /// This has to be separate from [`FeatureOpts::decouple_host_deps`] because /// `for_host` tracking is also needed for `itarget` to work properly. + /// + /// [`has_any_proc_macro`]: FeatureResolver::has_any_proc_macro track_for_host: bool, /// `dep_name?/feat_name` features that will be activated if `dep_name` is /// ever activated. @@ -490,7 +492,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { let member_features = self.ws.members_with_features(specs, cli_features)?; for (member, cli_features) in &member_features { let fvs = self.fvs_from_requested(member.package_id(), cli_features); - let fk = if self.track_for_host && self.is_proc_macro(member.package_id()) { + let fk = if self.track_for_host && self.has_any_proc_macro(member.package_id()) { // Also activate for normal dependencies. This is needed if the // proc-macro includes other targets (like binaries or tests), // or running in `cargo test`. Note that in a workspace, if @@ -852,7 +854,7 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { // 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.is_proc_macro(dep_id))) + (self.track_for_host && (dep.is_build() || self.has_proc_macro_lib(dep_id))) .then(|| FeaturesFor::HostDep) .unwrap_or_default() } else { @@ -957,10 +959,24 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { } } - fn is_proc_macro(&self, package_id: PackageId) -> bool { + /// Whether the given package has any proc macro target, including proc-macro examples. + fn has_any_proc_macro(&self, package_id: PackageId) -> bool { self.package_set .get_one(package_id) .expect("packages downloaded") .proc_macro() } + + /// Whether the given package is a proc macro lib target. + /// + /// This is useful for checking if a dependency is a proc macro, + /// as it is not possible to depend on a non-lib target as a proc-macro. + fn has_proc_macro_lib(&self, package_id: PackageId) -> bool { + self.package_set + .get_one(package_id) + .expect("packages downloaded") + .library() + .map(|lib| lib.proc_macro()) + .unwrap_or_default() + } } diff --git a/tests/testsuite/features2.rs b/tests/testsuite/features2.rs index 9d696978f91..18951457287 100644 --- a/tests/testsuite/features2.rs +++ b/tests/testsuite/features2.rs @@ -2652,3 +2652,47 @@ fn dep_with_optional_host_deps_activated() { ) .run(); } + +#[cargo_test] +fn dont_unify_proc_macro_example_from_dependency() { + // See https://github.com/rust-lang/cargo/issues/13726 + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + edition = "2021" + + [dependencies] + pm_helper = { path = "pm_helper" } + "#, + ) + .file("src/lib.rs", "") + .file( + "pm_helper/Cargo.toml", + r#" + [package] + name = "pm_helper" + + [[example]] + name = "pm" + proc-macro = true + crate-type = ["proc-macro"] + "#, + ) + .file("pm_helper/src/lib.rs", "") + .file("pm_helper/examples/pm.rs", "") + .build(); + + p.cargo("check") + .with_stderr( + "\ +[LOCKING] 2 packages to latest compatible versions +[CHECKING] pm_helper v0.0.0 ([CWD]/pm_helper) +[CHECKING] foo v0.0.0 ([CWD]) +[FINISHED] `dev` [..] +", + ) + .run(); +}