-Zfeatures=host_dep: Support decoupling proc-macro features.

This commit is contained in:
Eric Huss 2020-03-15 15:59:42 -07:00
parent 5a1862cd36
commit 0b115f57aa
10 changed files with 339 additions and 112 deletions

View File

@ -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()));

View File

@ -176,6 +176,7 @@ pub fn resolve_with_config_raw(
&BTreeMap::<String, Vec<String>>::new(),
None::<&String>,
false,
false,
)
.unwrap();
let opts = ResolveOpts::everything();
@ -577,6 +578,7 @@ pub fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> Summary {
&BTreeMap::<String, Vec<String>>::new(),
link,
false,
false,
)
.unwrap()
}
@ -605,6 +607,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary {
&BTreeMap::<String, Vec<String>>::new(),
link,
false,
false,
)
.unwrap()
}
@ -619,6 +622,7 @@ pub fn remove_dep(sum: &Summary, ind: usize) -> Summary {
&BTreeMap::<String, Vec<String>>::new(),
sum.links().map(|a| a.as_str()),
sum.namespaced_features(),
sum.proc_macro(),
)
.unwrap()
}

View File

@ -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,

View File

@ -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
}

View File

@ -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<InternedString>>;
/// 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::<Vec<_>>();
(dep_id, deps)

View File

@ -106,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;

View File

@ -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
};

View File

@ -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:

View File

@ -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();
}

View File

@ -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