mirror of https://github.com/rust-lang/cargo
-Zfeatures=host_dep: Support decoupling proc-macro features.
This commit is contained in:
parent
5a1862cd36
commit
0b115f57aa
|
@ -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()));
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue