diff --git a/Cargo.toml b/Cargo.toml index 0f8341a03ee..d5a15a223ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ path = "src/cargo/lib.rs" atty = "0.2" bytesize = "1.0" cargo-platform = { path = "crates/cargo-platform", version = "0.1.1" } -crates-io = { path = "crates/crates-io", version = "0.31" } +crates-io = { path = "crates/crates-io", version = "0.32" } crossbeam-utils = "0.7" crypto-hash = "0.3.1" curl = { version = "0.4.23", features = ["http2"] } diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index ba4173afdd0..445c1b797ad 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -144,6 +144,7 @@ pub struct Package { local: bool, alternative: bool, invalid_json: bool, + proc_macro: bool, } #[derive(Clone)] @@ -242,6 +243,7 @@ impl Package { local: false, alternative: false, invalid_json: false, + proc_macro: false, } } @@ -345,6 +347,12 @@ impl Package { self } + /// Specifies whether or not this is a proc macro. + pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package { + self.proc_macro = proc_macro; + self + } + /// Adds an entry in the `[features]` section. pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package { let deps = deps.iter().map(|s| s.to_string()).collect(); @@ -413,6 +421,7 @@ impl Package { "cksum": cksum, "features": self.features, "yanked": self.yanked, + "pm": self.proc_macro, }) .to_string(); @@ -498,6 +507,9 @@ impl Package { manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url())); } } + if self.proc_macro { + manifest.push_str("[lib]\nproc-macro = true\n"); + } let dst = self.archive_dst(); t!(fs::create_dir_all(dst.parent().unwrap())); diff --git a/crates/crates-io/Cargo.toml b/crates/crates-io/Cargo.toml index a4f93c8c0ff..4f56d92d8f9 100644 --- a/crates/crates-io/Cargo.toml +++ b/crates/crates-io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crates-io" -version = "0.31.0" +version = "0.32.0" edition = "2018" authors = ["Alex Crichton "] license = "MIT OR Apache-2.0" diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 68e5a3b9b0d..b31aeb2039b 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -54,8 +54,8 @@ pub struct NewCrate { pub license_file: Option, pub repository: Option, pub badges: BTreeMap>, - #[serde(default)] pub links: Option, + pub proc_macro: bool, } #[derive(Serialize)] diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index ef47f11ffc5..1f37b18da77 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -176,6 +176,7 @@ pub fn resolve_with_config_raw( &BTreeMap::>::new(), None::<&String>, false, + false, ) .unwrap(); let opts = ResolveOpts::everything(); @@ -577,6 +578,7 @@ pub fn pkg_dep(name: T, dep: Vec) -> Summary { &BTreeMap::>::new(), link, false, + false, ) .unwrap() } @@ -605,6 +607,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary { &BTreeMap::>::new(), link, false, + false, ) .unwrap() } @@ -619,6 +622,7 @@ pub fn remove_dep(sum: &Summary, ind: usize) -> Summary { &BTreeMap::>::new(), sum.links().map(|a| a.as_str()), sum.namespaced_features(), + sum.proc_macro(), ) .unwrap() } diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index 1dd7f233c48..a984adde276 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -178,9 +178,11 @@ fn deps_of_roots<'a, 'cfg>(roots: &[Unit<'a>], mut state: &mut State<'a, 'cfg>) } else if unit.target.is_custom_build() { // This normally doesn't happen, except `clean` aggressively // generates all units. - UnitFor::new_build(false) + UnitFor::new_host(false) + } else if unit.target.proc_macro() { + UnitFor::new_host(true) } else if unit.target.for_host() { - // Proc macro / plugin should never have panic set. + // Plugin should never have panic set. UnitFor::new_compiler() } else { UnitFor::new_normal() @@ -297,7 +299,7 @@ fn compute_deps<'a, 'cfg>( let dep_unit_for = unit_for .with_for_host(lib.for_host()) // If it is a custom build script, then it *only* has build dependencies. - .with_build_dep(unit.target.is_custom_build()); + .with_host_features(unit.target.is_custom_build() || lib.proc_macro()); if bcx.config.cli_unstable().dual_proc_macros && lib.proc_macro() && !unit.kind.is_host() { let unit_dep = new_unit_dep(state, unit, pkg, lib, dep_unit_for, unit.kind, mode)?; @@ -388,9 +390,10 @@ fn compute_deps_custom_build<'a, 'cfg>( return Ok(Vec::new()); } } - // All dependencies of this unit should use profiles for custom - // builds. - let script_unit_for = UnitFor::new_build(unit_for.is_for_build_dep()); + // All dependencies of this unit should use profiles for custom builds. + // If this is a build script of a proc macro, make sure it uses host + // features. + let script_unit_for = UnitFor::new_host(unit_for.is_for_host_features()); // When not overridden, then the dependencies to run a build script are: // // 1. Compiling the build script itself. @@ -445,7 +448,9 @@ fn compute_deps_doc<'a, 'cfg>( // Rustdoc only needs rmeta files for regular dependencies. // However, for plugins/proc macros, deps should be built like normal. let mode = check_or_build_mode(unit.mode, lib); - let dep_unit_for = UnitFor::new_normal().with_for_host(lib.for_host()); + let dep_unit_for = UnitFor::new_normal() + .with_for_host(lib.for_host()) + .with_host_features(lib.proc_macro()); let lib_unit_dep = new_unit_dep( state, unit, @@ -528,32 +533,32 @@ fn dep_build_script<'a>( .bcx .profiles .get_profile_run_custom_build(&unit.profile); - // UnitFor::new_build is used because we want the `host` flag set + // UnitFor::new_host is used because we want the `host` flag set // for all of our build dependencies (so they all get // build-override profiles), including compiling the build.rs // script itself. // - // If `is_for_build_dep` here is `false`, that means we are a + // If `is_for_host_features` here is `false`, that means we are a // build.rs script for a normal dependency and we want to set the // CARGO_FEATURE_* environment variables to the features as a // normal dep. // - // If `is_for_build_dep` here is `true`, that means that this - // package is being used as a build dependency, and so we only - // want to set CARGO_FEATURE_* variables for the build-dependency + // If `is_for_host_features` here is `true`, that means that this + // package is being used as a build dependency or proc-macro, and + // so we only want to set CARGO_FEATURE_* variables for the host // side of the graph. // // Keep in mind that the RunCustomBuild unit and the Compile // build.rs unit use the same features. This is because some // people use `cfg!` and `#[cfg]` expressions to check for enabled // features instead of just checking `CARGO_FEATURE_*` at runtime. - // In the case with `-Zfeatures=build_dep`, and a shared + // In the case with `-Zfeatures=host_dep`, and a shared // dependency has different features enabled for normal vs. build, // then the build.rs script will get compiled twice. I believe it // is not feasible to only build it once because it would break a // large number of scripts (they would think they have the wrong // set of features enabled). - let script_unit_for = UnitFor::new_build(unit_for.is_for_build_dep()); + let script_unit_for = UnitFor::new_host(unit_for.is_for_host_features()); new_unit_dep_with_profile( state, unit, diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 562bf1b3e3f..9ecd193a3d4 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -768,7 +768,7 @@ pub struct UnitFor { /// any of its dependencies. This enables `build-override` profiles for /// these targets. /// - /// An invariant is that if `build_dep` is true, `host` must be true. + /// An invariant is that if `host_features` is true, `host` must be true. /// /// Note that this is `true` for `RunCustomBuild` units, even though that /// unit should *not* use build-override profiles. This is a bit of a @@ -779,16 +779,16 @@ pub struct UnitFor { /// sticky (and forced to `true` for all further dependencies) — which is /// the whole point of `UnitFor`. host: bool, - /// A target for a build dependency (or any of its dependencies). This is - /// used for computing features of build dependencies independently of - /// other dependency kinds. + /// A target for a build dependency or proc-macro (or any of its + /// dependencies). This is used for computing features of build + /// dependencies and proc-macros independently of other dependency kinds. /// /// The subtle difference between this and `host` is that the build script /// for a non-host package sets this to `false` because it wants the /// features of the non-host package (whereas `host` is true because the - /// build script is being built for the host). `build_dep` becomes `true` - /// for build-dependencies, or any of their dependencies. For example, with - /// this dependency tree: + /// build script is being built for the host). `host_features` becomes + /// `true` for build-dependencies or proc-macros, or any of their + /// dependencies. For example, with this dependency tree: /// /// ```text /// foo @@ -799,17 +799,18 @@ pub struct UnitFor { /// └── shared_dep build.rs /// ``` /// - /// In this example, `foo build.rs` is HOST=true, BUILD_DEP=false. This is - /// so that `foo build.rs` gets the profile settings for build scripts - /// (HOST=true) and features of foo (BUILD_DEP=false) because build scripts - /// need to know which features their package is being built with. + /// In this example, `foo build.rs` is HOST=true, HOST_FEATURES=false. + /// This is so that `foo build.rs` gets the profile settings for build + /// scripts (HOST=true) and features of foo (HOST_FEATURES=false) because + /// build scripts need to know which features their package is being built + /// with. /// /// But in the case of `shared_dep`, when built as a build dependency, /// both flags are true (it only wants the build-dependency features). /// When `shared_dep` is built as a normal dependency, then `shared_dep - /// build.rs` is HOST=true, BUILD_DEP=false for the same reasons that + /// build.rs` is HOST=true, HOST_FEATURES=false for the same reasons that /// foo's build script is set that way. - build_dep: bool, + host_features: bool, /// How Cargo processes the `panic` setting or profiles. This is done to /// handle test/benches inheriting from dev/release, as well as forcing /// `for_host` units to always unwind. @@ -837,32 +838,35 @@ impl UnitFor { pub fn new_normal() -> UnitFor { UnitFor { host: false, - build_dep: false, + host_features: false, panic_setting: PanicSetting::ReadProfile, } } - /// A unit for a custom build script or its dependencies. + /// A unit for a custom build script or proc-macro or its dependencies. /// - /// The `build_dep` parameter is whether or not this is for a build - /// dependency. Build scripts for non-host units should use `false` - /// because they want to use the features of the package they are running - /// for. - pub fn new_build(build_dep: bool) -> UnitFor { + /// The `host_features` parameter is whether or not this is for a build + /// dependency or proc-macro (something that requires being built "on the + /// host"). Build scripts for non-host units should use `false` because + /// they want to use the features of the package they are running for. + pub fn new_host(host_features: bool) -> UnitFor { UnitFor { host: true, - build_dep, + host_features, // Force build scripts to always use `panic=unwind` for now to // maximally share dependencies with procedural macros. panic_setting: PanicSetting::AlwaysUnwind, } } - /// A unit for a proc macro or compiler plugin or their dependencies. + /// A unit for a compiler plugin or their dependencies. pub fn new_compiler() -> UnitFor { UnitFor { host: false, - build_dep: false, + // The feature resolver doesn't know which dependencies are + // plugins, so for now plugins don't split features. Since plugins + // are mostly deprecated, just leave this as false. + host_features: false, // Force plugins to use `panic=abort` so panics in the compiler do // not abort the process but instead end with a reasonable error // message that involves catching the panic in the compiler. @@ -879,7 +883,7 @@ impl UnitFor { pub fn new_test(config: &Config) -> UnitFor { UnitFor { host: false, - build_dep: false, + host_features: false, // We're testing out an unstable feature (`-Zpanic-abort-tests`) // which inherits the panic setting from the dev/release profile // (basically avoid recompiles) but historical defaults required @@ -902,7 +906,7 @@ impl UnitFor { pub fn with_for_host(self, for_host: bool) -> UnitFor { UnitFor { host: self.host || for_host, - build_dep: self.build_dep, + host_features: self.host_features, panic_setting: if for_host { PanicSetting::AlwaysUnwind } else { @@ -911,15 +915,16 @@ impl UnitFor { } } - /// Returns a new copy updating it for a build dependency. + /// Returns a new copy updating it whether or not it should use features + /// for build dependencies and proc-macros. /// /// This is part of the machinery responsible for handling feature /// decoupling for build dependencies in the new feature resolver. - pub fn with_build_dep(mut self, build_dep: bool) -> UnitFor { - if build_dep { + pub fn with_host_features(mut self, host_features: bool) -> UnitFor { + if host_features { assert!(self.host); } - self.build_dep = self.build_dep || build_dep; + self.host_features = self.host_features || host_features; self } @@ -929,8 +934,8 @@ impl UnitFor { self.host } - pub fn is_for_build_dep(&self) -> bool { - self.build_dep + pub fn is_for_host_features(&self) -> bool { + self.host_features } /// Returns how `panic` settings should be handled for this profile @@ -943,34 +948,34 @@ impl UnitFor { static ALL: &[UnitFor] = &[ UnitFor { host: false, - build_dep: false, + host_features: false, panic_setting: PanicSetting::ReadProfile, }, UnitFor { host: true, - build_dep: false, + host_features: false, panic_setting: PanicSetting::AlwaysUnwind, }, UnitFor { host: false, - build_dep: false, + host_features: false, panic_setting: PanicSetting::AlwaysUnwind, }, UnitFor { host: false, - build_dep: false, + host_features: false, panic_setting: PanicSetting::Inherit, }, - // build_dep=true must always have host=true + // host_features=true must always have host=true // `Inherit` is not used in build dependencies. UnitFor { host: true, - build_dep: true, + host_features: true, panic_setting: PanicSetting::ReadProfile, }, UnitFor { host: true, - build_dep: true, + host_features: true, panic_setting: PanicSetting::AlwaysUnwind, }, ]; @@ -978,8 +983,8 @@ impl UnitFor { } pub(crate) fn map_to_features_for(&self) -> FeaturesFor { - if self.is_for_build_dep() { - FeaturesFor::BuildDep + if self.is_for_host_features() { + FeaturesFor::HostDep } else { FeaturesFor::NormalOrDev } diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index 3af957fefe1..155db581ea0 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -50,7 +50,7 @@ use std::rc::Rc; /// Map of activated features. /// /// The key is `(PackageId, bool)` where the bool is `true` if these -/// are features for a build dependency. +/// are features for a build dependency or proc-macro. type ActivateMap = HashMap<(PackageId, bool), BTreeSet>; /// Set of all activated features for all packages in the resolve graph. @@ -68,8 +68,8 @@ struct FeatureOpts { package_features: bool, /// -Zfeatures is enabled, use new resolver. new_resolver: bool, - /// Build deps will not share share features with other dep kinds. - decouple_build_deps: bool, + /// Build deps and proc-macros will not share share features with other dep kinds. + decouple_host_deps: bool, /// Dev dep features will not be activated unless needed. decouple_dev_deps: bool, /// Targets that are not in use will not activate features. @@ -91,10 +91,11 @@ pub enum HasDevUnits { } /// Flag to indicate if features are requested for a build dependency or not. -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] pub enum FeaturesFor { NormalOrDev, - BuildDep, + /// Build dependency or proc-macro. + HostDep, } impl FeatureOpts { @@ -106,17 +107,16 @@ impl FeatureOpts { opts.new_resolver = true; for opt in feat_opts { match opt.as_ref() { - "build_dep" => opts.decouple_build_deps = true, + "build_dep" | "host_dep" => opts.decouple_host_deps = true, "dev_dep" => opts.decouple_dev_deps = true, "itarget" => opts.ignore_inactive_targets = true, "all" => { - opts.decouple_build_deps = true; + opts.decouple_host_deps = true; opts.decouple_dev_deps = true; opts.ignore_inactive_targets = true; } "compare" => opts.compare = true, "ws" => unimplemented!(), - "host" => unimplemented!(), s => anyhow::bail!("-Zfeatures flag `{}` is not supported", s), } } @@ -213,7 +213,7 @@ impl ResolvedFeatures { if let Some(legacy) = &self.legacy { legacy.get(&pkg_id).map_or_else(Vec::new, |v| v.clone()) } else { - let is_build = self.opts.decouple_build_deps && features_for == FeaturesFor::BuildDep; + let is_build = self.opts.decouple_host_deps && features_for == FeaturesFor::HostDep; if let Some(fs) = self.activated_features.get(&(pkg_id, is_build)) { fs.iter().cloned().collect() } else if verify { @@ -294,7 +294,19 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { let member_features = self.ws.members_with_features(specs, requested_features)?; for (member, requested_features) in &member_features { let fvs = self.fvs_from_requested(member.package_id(), requested_features); - self.activate_pkg(member.package_id(), &fvs, false)?; + let for_host = self.opts.decouple_host_deps + && self.resolve.summary(member.package_id()).proc_macro(); + self.activate_pkg(member.package_id(), &fvs, for_host)?; + if for_host { + // Also activate without for_host. 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 + // the proc-macro is selected on the command like (like with + // `--workspace`), this forces feature unification with normal + // dependencies. This is part of the bigger problem where + // features depend on which packages are built. + self.activate_pkg(member.package_id(), &fvs, false)?; + } } Ok(()) } @@ -303,18 +315,18 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { &mut self, pkg_id: PackageId, fvs: &[FeatureValue], - for_build: bool, + for_host: bool, ) -> CargoResult<()> { // Add an empty entry to ensure everything is covered. This is intended for // finding bugs where the resolver missed something it should have visited. // Remove this in the future if `activated_features` uses an empty default. self.activated_features - .entry((pkg_id, for_build)) + .entry((pkg_id, for_host)) .or_insert_with(BTreeSet::new); for fv in fvs { - self.activate_fv(pkg_id, fv, for_build)?; + self.activate_fv(pkg_id, fv, for_host)?; } - if !self.processed_deps.insert((pkg_id, for_build)) { + if !self.processed_deps.insert((pkg_id, for_host)) { // Already processed dependencies. There's no need to process them // again. This is primarily to avoid cycles, but also helps speed // things up. @@ -330,8 +342,8 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { // features that enable other features. return Ok(()); } - for (dep_pkg_id, deps) in self.deps(pkg_id, for_build) { - for (dep, dep_for_build) in deps { + for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) { + for (dep, dep_for_host) in deps { if dep.is_optional() { // Optional dependencies are enabled in `activate_fv` when // a feature enables it. @@ -339,7 +351,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { } // Recurse into the dependency. let fvs = self.fvs_from_dependency(dep_pkg_id, dep); - self.activate_pkg(dep_pkg_id, &fvs, dep_for_build)?; + self.activate_pkg(dep_pkg_id, &fvs, dep_for_host)?; } } Ok(()) @@ -350,42 +362,42 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { &mut self, pkg_id: PackageId, fv: &FeatureValue, - for_build: bool, + for_host: bool, ) -> CargoResult<()> { match fv { FeatureValue::Feature(f) => { - self.activate_rec(pkg_id, *f, for_build)?; + self.activate_rec(pkg_id, *f, for_host)?; } FeatureValue::Crate(dep_name) => { // Activate the feature name on self. - self.activate_rec(pkg_id, *dep_name, for_build)?; + self.activate_rec(pkg_id, *dep_name, for_host)?; // Activate the optional dep. - for (dep_pkg_id, deps) in self.deps(pkg_id, for_build) { - for (dep, dep_for_build) in deps { + for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) { + for (dep, dep_for_host) in deps { if dep.name_in_toml() != *dep_name { continue; } let fvs = self.fvs_from_dependency(dep_pkg_id, dep); - self.activate_pkg(dep_pkg_id, &fvs, dep_for_build)?; + self.activate_pkg(dep_pkg_id, &fvs, dep_for_host)?; } } } FeatureValue::CrateFeature(dep_name, dep_feature) => { // Activate a feature within a dependency. - for (dep_pkg_id, deps) in self.deps(pkg_id, for_build) { - for (dep, dep_for_build) in deps { + for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) { + for (dep, dep_for_host) in deps { if dep.name_in_toml() != *dep_name { continue; } if dep.is_optional() { // Activate the crate on self. let fv = FeatureValue::Crate(*dep_name); - self.activate_fv(pkg_id, &fv, for_build)?; + self.activate_fv(pkg_id, &fv, for_host)?; } // Activate the feature on the dependency. let summary = self.resolve.summary(dep_pkg_id); let fv = FeatureValue::new(*dep_feature, summary); - self.activate_fv(dep_pkg_id, &fv, dep_for_build)?; + self.activate_fv(dep_pkg_id, &fv, dep_for_host)?; } } } @@ -399,11 +411,11 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { &mut self, pkg_id: PackageId, feature_to_enable: InternedString, - for_build: bool, + for_host: bool, ) -> CargoResult<()> { let enabled = self .activated_features - .entry((pkg_id, for_build)) + .entry((pkg_id, for_host)) .or_insert_with(BTreeSet::new); if !enabled.insert(feature_to_enable) { // Already enabled. @@ -426,7 +438,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { } }; for fv in fvs { - self.activate_fv(pkg_id, fv, for_build)?; + self.activate_fv(pkg_id, fv, for_host)?; } Ok(()) } @@ -462,9 +474,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { .collect(); // Add optional deps. // Top-level requested features can never apply to - // build-dependencies, so for_build is `false` here. + // build-dependencies, so for_host is `false` here. for (_dep_pkg_id, deps) in self.deps(pkg_id, false) { - for (dep, _dep_for_build) in deps { + for (dep, _dep_for_host) in deps { if dep.is_optional() { // This may result in duplicates, but that should be ok. fvs.push(FeatureValue::Crate(dep.name_in_toml())); @@ -491,14 +503,14 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { fn deps( &self, pkg_id: PackageId, - for_build: bool, + for_host: bool, ) -> Vec<(PackageId, Vec<(&'a Dependency, bool)>)> { // Helper for determining if a platform is activated. let platform_activated = |dep: &Dependency| -> bool { // We always care about build-dependencies, and they are always // Host. If we are computing dependencies "for a build script", // even normal dependencies are host-only. - if for_build || dep.is_build() { + if for_host || dep.is_build() { return self .target_data .dep_platform_activated(dep, CompileKind::Host); @@ -510,6 +522,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { self.resolve .deps(pkg_id) .map(|(dep_id, deps)| { + let is_proc_macro = self.resolve.summary(dep_id).proc_macro(); let deps = deps .iter() .filter(|dep| { @@ -525,9 +538,9 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> { true }) .map(|dep| { - let dep_for_build = - for_build || (self.opts.decouple_build_deps && dep.is_build()); - (dep, dep_for_build) + let dep_for_host = for_host + || (self.opts.decouple_host_deps && (dep.is_build() || is_proc_macro)); + (dep, dep_for_host) }) .collect::>(); (dep_id, deps) diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 365c9e29fba..4c362a54978 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -30,6 +30,11 @@ struct Inner { checksum: Option, links: Option, namespaced_features: bool, + /// Whether or not this package is a proc-macro library. + /// + /// This was added in 2020. Packages published before this will always be + /// `false`. + proc_macro: bool, } impl Summary { @@ -39,6 +44,7 @@ impl Summary { features: &BTreeMap>>, links: Option>, namespaced_features: bool, + proc_macro: bool, ) -> CargoResult where K: Borrow + Ord + Display, @@ -68,6 +74,7 @@ impl Summary { checksum: None, links: links.map(|l| l.into()), namespaced_features, + proc_macro, }), }) } @@ -99,6 +106,9 @@ impl Summary { pub fn namespaced_features(&self) -> bool { self.inner.namespaced_features } + pub fn proc_macro(&self) -> bool { + self.inner.proc_macro + } pub fn override_id(mut self, id: PackageId) -> Summary { Rc::make_mut(&mut self.inner).package_id = id; diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 4749191598d..f0ca40fe9cb 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -749,10 +749,14 @@ fn generate_targets<'a>( bcx.profiles .get_profile(pkg.package_id(), ws.is_member(pkg), unit_for, target_mode); - let features = Vec::from(resolved_features.activated_features( - pkg.package_id(), - FeaturesFor::NormalOrDev, // Root units are never build dependencies. - )); + let features_for = if target.proc_macro() { + FeaturesFor::HostDep + } else { + // Root units are never build dependencies. + FeaturesFor::NormalOrDev + }; + let features = + Vec::from(resolved_features.activated_features(pkg.package_id(), features_for)); bcx.units.intern( pkg, target, @@ -950,9 +954,10 @@ fn resolve_all_features( // Include features enabled for use by dependencies so targets can also use them with the // required-features field when deciding whether to be built or skipped. for (dep_id, deps) in resolve_with_overrides.deps(package_id) { + let is_proc_macro = resolve_with_overrides.summary(dep_id).proc_macro(); for dep in deps { - let features_for = if dep.is_build() { - FeaturesFor::BuildDep + let features_for = if is_proc_macro || dep.is_build() { + FeaturesFor::HostDep } else { FeaturesFor::NormalOrDev }; diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 18c24a5df2b..cd1c3a0f829 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -255,6 +255,7 @@ fn transmit( ) }) .collect::>>(); + let proc_macro = pkg.targets().iter().any(|target| target.proc_macro()); let publish = registry.publish( &NewCrate { @@ -275,6 +276,7 @@ fn transmit( license_file: license_file.clone(), badges: badges.clone(), links: links.clone(), + proc_macro, }, tarball, ); diff --git a/src/cargo/sources/registry/index.rs b/src/cargo/sources/registry/index.rs index 9bfb15d7e42..d4b87787b60 100644 --- a/src/cargo/sources/registry/index.rs +++ b/src/cargo/sources/registry/index.rs @@ -715,6 +715,7 @@ impl IndexSummary { features, yanked, links, + pm, } = serde_json::from_slice(line)?; log::trace!("json parsed registry {}/{}", name, vers); let pkgid = PackageId::new(name, &vers, source_id)?; @@ -722,7 +723,15 @@ impl IndexSummary { .into_iter() .map(|dep| dep.into_dep(source_id)) .collect::>>()?; - let mut summary = Summary::new(pkgid, deps, &features, links, false)?; + let namespaced_features = false; + let mut summary = Summary::new( + pkgid, + deps, + &features, + links, + namespaced_features, + pm.unwrap_or(false), + )?; summary.set_checksum(cksum); Ok(IndexSummary { summary, diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 4e0c1d12d1e..b97b5b9e565 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -217,6 +217,7 @@ pub struct RegistryConfig { pub api: Option, } +/// A single line in the index representing a single version of a package. #[derive(Deserialize)] pub struct RegistryPackage<'a> { name: InternedString, @@ -225,8 +226,21 @@ pub struct RegistryPackage<'a> { deps: Vec>, features: BTreeMap>, cksum: String, + /// If `true`, Cargo will skip this version when resolving. + /// + /// This was added in 2014. Everything in the crates.io index has this set + /// now, so this probably doesn't need to be an option anymore. yanked: Option, + /// Native library name this package links to. + /// + /// Added early 2018 (see https://github.com/rust-lang/cargo/pull/4978), + /// can be `None` if published before then. links: Option, + /// Whether or not this package is a proc-macro library. + /// + /// If `None`, then the status is unknown (crate was published before this + /// field was added), and generally should be treated as `false.` + pm: Option, } #[test] diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index e8ea62c36c2..ee258699119 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1146,19 +1146,23 @@ impl TomlManifest { features.require(Feature::namespaced_features())?; } + let summary_features = me + .features + .as_ref() + .map(|x| { + x.iter() + .map(|(k, v)| (k.as_str(), v.iter().collect())) + .collect() + }) + .unwrap_or_else(BTreeMap::new); + let proc_macro = targets.iter().any(|target| target.proc_macro()); let summary = Summary::new( pkgid, deps, - &me.features - .as_ref() - .map(|x| { - x.iter() - .map(|(k, v)| (k.as_str(), v.iter().collect())) - .collect() - }) - .unwrap_or_else(BTreeMap::new), + &summary_features, project.links.as_deref(), project.namespaced_features.unwrap_or(false), + proc_macro, )?; let metadata = ManifestMetadata { description: project.description.clone(), diff --git a/src/doc/src/reference/registries.md b/src/doc/src/reference/registries.md index 9eaf523586d..c3f043eb5f9 100644 --- a/src/doc/src/reference/registries.md +++ b/src/doc/src/reference/registries.md @@ -239,7 +239,11 @@ explaining the format of the entry. "yanked": false, // The `links` string value from the package's manifest, or null if not // specified. This field is optional and defaults to null. - "links": null + "links": null, + // This is `true` if the package is a proc-macro. + // Note: This field was added in Rust 1.44. Packages published with + // earlier versions will not set this field. + "pm": false, } ``` @@ -404,6 +408,10 @@ considered as an exhaustive list of restrictions [crates.io] imposes. // The `links` string value from the package's manifest, or null if not // specified. This field is optional and defaults to null. "links": null, + // This is `true` if the package is a proc-macro. + // Note: This field was added in Rust 1.44. Packages published with + // earlier versions will not set this field. + "proc_macro": false, } ``` diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index a5a8293cacd..1ab1ce18e17 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -507,8 +507,8 @@ The available options are: When building this example for a non-Windows platform, the `f2` feature will *not* be enabled. -* `build_dep` — Prevents features enabled on build dependencies from being - enabled for normal dependencies. For example: +* `host_dep` — Prevents features enabled on build dependencies or proc-macros + from being enabled for normal dependencies. For example: ```toml [dependencies] @@ -522,6 +522,9 @@ The available options are: feature. When building the library of your package, it will not enable the feature. + Note that proc-macro decoupling requires changes to the registry, so it + won't be decoupled until the registry is updated to support the new field. + * `dev_dep` — Prevents features enabled on dev dependencies from being enabled for normal dependencies. For example: diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index cf45400b21a..972f81ce7bf 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -357,6 +357,7 @@ fn publish_with_registry_dependency() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -456,6 +457,7 @@ fn publish_to_alt_registry() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -519,6 +521,7 @@ fn publish_with_crates_io_dep() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, diff --git a/tests/testsuite/features2.rs b/tests/testsuite/features2.rs index a358ac52ce4..016826f24d1 100644 --- a/tests/testsuite/features2.rs +++ b/tests/testsuite/features2.rs @@ -1,5 +1,6 @@ //! Tests for the new feature resolver. +use cargo_test_support::paths::CargoPathExt; use cargo_test_support::registry::{Dependency, Package}; use cargo_test_support::{basic_manifest, project}; @@ -183,8 +184,8 @@ fn inactive_target_optional() { } #[cargo_test] -fn decouple_build_deps() { - // Basic test for `build_dep` decouple. +fn decouple_host_deps() { + // Basic test for `host_dep` decouple. Package::new("common", "1.0.0") .feature("f1", &[]) .file( @@ -229,14 +230,14 @@ fn decouple_build_deps() { .with_stderr_contains("[..]unresolved import `common::bar`[..]") .run(); - p.cargo("check -Zfeatures=build_dep") + p.cargo("check -Zfeatures=host_dep") .masquerade_as_nightly_cargo() .run(); } #[cargo_test] -fn decouple_build_deps_nested() { - // `build_dep` decouple of transitive dependencies. +fn decouple_host_deps_nested() { + // `host_dep` decouple of transitive dependencies. Package::new("common", "1.0.0") .feature("f1", &[]) .file( @@ -294,7 +295,7 @@ fn decouple_build_deps_nested() { .with_stderr_contains("[..]unresolved import `common::bar`[..]") .run(); - p.cargo("check -Zfeatures=build_dep") + p.cargo("check -Zfeatures=host_dep") .masquerade_as_nightly_cargo() .run(); } @@ -590,7 +591,7 @@ fn build_script_runtime_features() { .run(); // Normal only. - p.cargo("run -Zfeatures=dev_dep,build_dep") + p.cargo("run -Zfeatures=dev_dep,host_dep") .env("CARGO_FEATURE_EXPECT", "1") .masquerade_as_nightly_cargo() .run(); @@ -604,7 +605,7 @@ fn build_script_runtime_features() { .run(); // normal + dev unify - p.cargo("test -Zfeatures=build_dep") + p.cargo("test -Zfeatures=host_dep") .env("CARGO_FEATURE_EXPECT", "3") .masquerade_as_nightly_cargo() .run(); @@ -767,7 +768,7 @@ fn all_feature_opts() { } #[cargo_test] -fn required_features_build_dep() { +fn required_features_host_dep() { // Check that required-features handles build-dependencies correctly. let p = project() .file( @@ -817,13 +818,13 @@ Consider enabling them by passing, e.g., `--features=\"bdep/f1\"` ) .run(); - p.cargo("run --features bdep/f1 -Zfeatures=build_dep") + p.cargo("run --features bdep/f1 -Zfeatures=host_dep") .masquerade_as_nightly_cargo() .run(); } #[cargo_test] -fn disabled_shared_build_dep() { +fn disabled_shared_host_dep() { // Check for situation where an optional dep of a shared dep is enabled in // a normal dependency, but disabled in an optional one. The unit tree is: // foo @@ -888,7 +889,7 @@ fn disabled_shared_build_dep() { ) .build(); - p.cargo("run -Zfeatures=build_dep -v") + p.cargo("run -Zfeatures=host_dep -v") .masquerade_as_nightly_cargo() .with_stdout("hello from somedep") .run(); @@ -931,3 +932,179 @@ fn required_features_inactive_dep() { .with_stderr("[CHECKING] foo[..]\n[FINISHED] [..]") .run(); } + +#[cargo_test] +fn decouple_proc_macro() { + // proc macro features are not shared + Package::new("common", "1.0.0") + .feature("somefeat", &[]) + .file( + "src/lib.rs", + r#" + pub const fn foo() -> bool { cfg!(feature="somefeat") } + #[cfg(feature="somefeat")] + pub const FEAT_ONLY_CONST: bool = true; + "#, + ) + .publish(); + Package::new("pm", "1.0.0") + .proc_macro(true) + .feature_dep("common", "1.0", &["somefeat"]) + .file( + "src/lib.rs", + r#" + extern crate proc_macro; + extern crate common; + #[proc_macro] + pub fn foo(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + assert!(common::foo()); + "".parse().unwrap() + } + "#, + ) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2018" + + [dependencies] + pm = "1.0" + common = "1.0" + "#, + ) + .file( + "src/lib.rs", + r#" + //! Test with docs. + //! + //! ```rust + //! pm::foo!{} + //! fn main() { + //! let expected = std::env::var_os("TEST_EXPECTS_ENABLED").is_some(); + //! assert_eq!(expected, common::foo(), "common is wrong"); + //! } + //! ``` + "#, + ) + .file( + "src/main.rs", + r#" + pm::foo!{} + fn main() { + println!("it is {}", common::foo()); + } + "#, + ) + .build(); + + p.cargo("run") + .env("TEST_EXPECTS_ENABLED", "1") + .with_stdout("it is true") + .run(); + + p.cargo("run -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .with_stdout("it is false") + .run(); + + // Make sure the test is fallible. + p.cargo("test --doc") + .with_status(101) + .with_stdout_contains("[..]common is wrong[..]") + .run(); + + p.cargo("test --doc").env("TEST_EXPECTS_ENABLED", "1").run(); + + p.cargo("test --doc -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .run(); + + p.cargo("doc").run(); + assert!(p + .build_dir() + .join("doc/common/constant.FEAT_ONLY_CONST.html") + .exists()); + // cargo doc should clean in-between runs, but it doesn't, and leaves stale files. + // https://github.com/rust-lang/cargo/issues/6783 (same for removed items) + p.build_dir().join("doc").rm_rf(); + + p.cargo("doc -Zfeatures=host_dep") + .masquerade_as_nightly_cargo() + .run(); + assert!(!p + .build_dir() + .join("doc/common/constant.FEAT_ONLY_CONST.html") + .exists()); +} + +#[cargo_test] +fn proc_macro_ws() { + // Checks for bug with proc-macro in a workspace with dependency (shouldn't panic). + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo", "pm"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [features] + feat1 = [] + "#, + ) + .file("foo/src/lib.rs", "") + .file( + "pm/Cargo.toml", + r#" + [package] + name = "pm" + version = "0.1.0" + + [lib] + proc-macro = true + + [dependencies] + foo = { path = "../foo", features=["feat1"] } + "#, + ) + .file("pm/src/lib.rs", "") + .build(); + + p.cargo("check -p pm -Zfeatures=host_dep -v") + .masquerade_as_nightly_cargo() + .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..]--cfg[..]feat1[..]") + .run(); + // This may be surprising that `foo` doesn't get built separately. It is + // because pm might have other units (binaries, tests, etc.), and so the + // feature resolver must assume that normal deps get unified with it. This + // is related to the bigger issue where the features selected in a + // workspace depend on which packages are selected. + p.cargo("check --workspace -Zfeatures=host_dep -v") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[FRESH] foo v0.1.0 [..] +[FRESH] pm v0.1.0 [..] +[FINISHED] dev [..] +", + ) + .run(); + // Selecting just foo will build without unification. + p.cargo("check -p foo -Zfeatures=host_dep -v") + .masquerade_as_nightly_cargo() + // Make sure `foo` is built without feat1 + .with_stderr_line_without(&["[RUNNING] `rustc --crate-name foo"], &["--cfg[..]feat1"]) + .run(); +} diff --git a/tests/testsuite/profile_config.rs b/tests/testsuite/profile_config.rs index e9928c5ae5b..4cca6d0408d 100644 --- a/tests/testsuite/profile_config.rs +++ b/tests/testsuite/profile_config.rs @@ -413,7 +413,7 @@ fn named_config_profile() { assert_eq!(p.overflow_checks, true); // "dev" built-in (ignore package override) // build-override - let bo = profiles.get_profile(a_pkg, true, UnitFor::new_build(false), CompileMode::Build); + let bo = profiles.get_profile(a_pkg, true, UnitFor::new_host(false), CompileMode::Build); assert_eq!(bo.name, "foo"); assert_eq!(bo.codegen_units, Some(6)); // "foo" build override from config assert_eq!(bo.opt_level, "1"); // SAME as normal diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 345f269764d..05c82df27a6 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -23,6 +23,7 @@ const CLEAN_FOO_JSON: &str = r#" "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": "foo", @@ -47,6 +48,7 @@ fn validate_upload_foo() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -979,6 +981,7 @@ fn publish_with_patch() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -1148,6 +1151,7 @@ fn publish_git_with_version() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -1235,6 +1239,7 @@ fn publish_dev_dep_no_version() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": "foo", @@ -1295,3 +1300,66 @@ fn credentials_ambiguous_filename() { validate_upload_foo(); } + +#[cargo_test] +fn publish_proc_macro() { + registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + edition = "2018" + homepage = "https://example.com" + + [lib] + proc-macro = true + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --index") + .arg(registry_url().to_string()) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[UPLOADING] foo v0.0.1 ([CWD]) +", + ) + .run(); + + publish::validate_upload( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": null, + "features": {}, + "homepage": "https://example.com", + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "proc_macro": true, + "readme": null, + "readme_file": null, + "repository": null, + "vers": "0.0.1" + } + "#, + "foo-0.0.1.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + ); +}