use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::{DepKind, Dependency};
use crate::core::resolver::types::FeaturesSet;
use crate::core::resolver::Resolve;
use crate::core::{FeatureValue, InternedString, PackageId, PackageIdSpec, PackageSet, Workspace};
use crate::util::{CargoResult, Config};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::rc::Rc;
type ActivateMap = HashMap<(PackageId, bool), BTreeSet<InternedString>>;
pub struct ResolvedFeatures {
activated_features: ActivateMap,
legacy: Option<HashMap<PackageId, Vec<InternedString>>>,
opts: FeatureOpts,
}
#[derive(Default)]
struct FeatureOpts {
package_features: bool,
new_resolver: bool,
decouple_host_deps: bool,
decouple_dev_deps: bool,
ignore_inactive_targets: bool,
compare: bool,
}
#[derive(Copy, Clone, PartialEq)]
pub enum HasDevUnits {
Yes,
No,
}
#[derive(Debug, PartialEq)]
pub enum FeaturesFor {
NormalOrDev,
HostDep,
}
impl FeatureOpts {
fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> {
let mut opts = FeatureOpts::default();
let unstable_flags = config.cli_unstable();
opts.package_features = unstable_flags.package_features;
let mut enable = |feat_opts: &Vec<String>| {
opts.new_resolver = true;
for opt in feat_opts {
match opt.as_ref() {
"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_host_deps = true;
opts.decouple_dev_deps = true;
opts.ignore_inactive_targets = true;
}
"compare" => opts.compare = true,
"ws" => unimplemented!(),
s => anyhow::bail!("-Zfeatures flag `{}` is not supported", s),
}
}
Ok(())
};
if let Some(feat_opts) = unstable_flags.features.as_ref() {
enable(feat_opts)?;
}
if let Ok(env_opts) = std::env::var("__CARGO_FORCE_NEW_FEATURES") {
if env_opts == "1" {
opts.new_resolver = true;
} else {
let env_opts = env_opts.split(',').map(|s| s.to_string()).collect();
enable(&env_opts)?;
}
}
if let HasDevUnits::Yes = has_dev_units {
opts.decouple_dev_deps = false;
}
Ok(opts)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct RequestedFeatures {
pub features: FeaturesSet,
pub all_features: bool,
pub uses_default_features: bool,
}
impl RequestedFeatures {
pub fn from_command_line(
features: &[String],
all_features: bool,
uses_default_features: bool,
) -> RequestedFeatures {
RequestedFeatures {
features: Rc::new(RequestedFeatures::split_features(features)),
all_features,
uses_default_features,
}
}
pub fn new_all(all_features: bool) -> RequestedFeatures {
RequestedFeatures {
features: Rc::new(BTreeSet::new()),
all_features,
uses_default_features: true,
}
}
fn split_features(features: &[String]) -> BTreeSet<InternedString> {
features
.iter()
.flat_map(|s| s.split_whitespace())
.flat_map(|s| s.split(','))
.filter(|s| !s.is_empty())
.map(InternedString::new)
.collect::<BTreeSet<InternedString>>()
}
}
impl ResolvedFeatures {
pub fn activated_features(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
) -> Vec<InternedString> {
self.activated_features_int(pkg_id, features_for, true)
}
pub fn activated_features_unverified(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
) -> Vec<InternedString> {
self.activated_features_int(pkg_id, features_for, false)
}
fn activated_features_int(
&self,
pkg_id: PackageId,
features_for: FeaturesFor,
verify: bool,
) -> Vec<InternedString> {
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_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 {
panic!("features did not find {:?} {:?}", pkg_id, is_build)
} else {
Vec::new()
}
}
}
}
pub struct FeatureResolver<'a, 'cfg> {
ws: &'a Workspace<'cfg>,
target_data: &'a RustcTargetData,
requested_target: CompileKind,
resolve: &'a Resolve,
package_set: &'a PackageSet<'cfg>,
opts: FeatureOpts,
activated_features: ActivateMap,
processed_deps: HashSet<(PackageId, bool)>,
}
impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
pub fn resolve(
ws: &Workspace<'cfg>,
target_data: &RustcTargetData,
resolve: &Resolve,
package_set: &'a PackageSet<'cfg>,
requested_features: &RequestedFeatures,
specs: &[PackageIdSpec],
requested_target: CompileKind,
has_dev_units: HasDevUnits,
) -> CargoResult<ResolvedFeatures> {
use crate::util::profile;
let _p = profile::start("resolve features");
let opts = FeatureOpts::new(ws.config(), has_dev_units)?;
if !opts.new_resolver {
return Ok(ResolvedFeatures {
activated_features: HashMap::new(),
legacy: Some(resolve.features_clone()),
opts,
});
}
let mut r = FeatureResolver {
ws,
target_data,
requested_target,
resolve,
package_set,
opts,
activated_features: HashMap::new(),
processed_deps: HashSet::new(),
};
r.do_resolve(specs, requested_features)?;
log::debug!("features={:#?}", r.activated_features);
if r.opts.compare {
r.compare();
}
Ok(ResolvedFeatures {
activated_features: r.activated_features,
legacy: None,
opts: r.opts,
})
}
fn do_resolve(
&mut self,
specs: &[PackageIdSpec],
requested_features: &RequestedFeatures,
) -> CargoResult<()> {
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);
let for_host = self.opts.decouple_host_deps && self.is_proc_macro(member.package_id());
self.activate_pkg(member.package_id(), &fvs, for_host)?;
if for_host {
self.activate_pkg(member.package_id(), &fvs, false)?;
}
}
Ok(())
}
fn activate_pkg(
&mut self,
pkg_id: PackageId,
fvs: &[FeatureValue],
for_host: bool,
) -> CargoResult<()> {
self.activated_features
.entry((pkg_id, for_host))
.or_insert_with(BTreeSet::new);
for fv in fvs {
self.activate_fv(pkg_id, fv, for_host)?;
}
if !self.processed_deps.insert((pkg_id, for_host)) {
return Ok(());
}
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
for (dep, dep_for_host) in deps {
if dep.is_optional() {
continue;
}
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
self.activate_pkg(dep_pkg_id, &fvs, dep_for_host)?;
}
}
Ok(())
}
fn activate_fv(
&mut self,
pkg_id: PackageId,
fv: &FeatureValue,
for_host: bool,
) -> CargoResult<()> {
match fv {
FeatureValue::Feature(f) => {
self.activate_rec(pkg_id, *f, for_host)?;
}
FeatureValue::Crate(dep_name) => {
self.activate_rec(pkg_id, *dep_name, for_host)?;
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_host)?;
}
}
}
FeatureValue::CrateFeature(dep_name, dep_feature) => {
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() {
let fv = FeatureValue::Crate(*dep_name);
self.activate_fv(pkg_id, &fv, for_host)?;
}
let summary = self.resolve.summary(dep_pkg_id);
let fv = FeatureValue::new(*dep_feature, summary);
self.activate_fv(dep_pkg_id, &fv, dep_for_host)?;
}
}
}
}
Ok(())
}
fn activate_rec(
&mut self,
pkg_id: PackageId,
feature_to_enable: InternedString,
for_host: bool,
) -> CargoResult<()> {
let enabled = self
.activated_features
.entry((pkg_id, for_host))
.or_insert_with(BTreeSet::new);
if !enabled.insert(feature_to_enable) {
return Ok(());
}
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
let fvs = match feature_map.get(&feature_to_enable) {
Some(fvs) => fvs,
None => {
log::debug!(
"pkg {:?} does not define feature {}",
pkg_id,
feature_to_enable
);
return Ok(());
}
};
for fv in fvs {
self.activate_fv(pkg_id, fv, for_host)?;
}
Ok(())
}
fn fvs_from_dependency(&self, dep_id: PackageId, dep: &Dependency) -> Vec<FeatureValue> {
let summary = self.resolve.summary(dep_id);
let feature_map = summary.features();
let mut result: Vec<FeatureValue> = dep
.features()
.iter()
.map(|f| FeatureValue::new(*f, summary))
.collect();
let default = InternedString::new("default");
if dep.uses_default_features() && feature_map.contains_key(&default) {
result.push(FeatureValue::Feature(default));
}
result
}
fn fvs_from_requested(
&self,
pkg_id: PackageId,
requested_features: &RequestedFeatures,
) -> Vec<FeatureValue> {
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
if requested_features.all_features {
let mut fvs: Vec<FeatureValue> = feature_map
.keys()
.map(|k| FeatureValue::Feature(*k))
.collect();
for (_dep_pkg_id, deps) in self.deps(pkg_id, false) {
for (dep, _dep_for_host) in deps {
if dep.is_optional() {
fvs.push(FeatureValue::Crate(dep.name_in_toml()));
}
}
}
fvs
} else {
let mut result: Vec<FeatureValue> = requested_features
.features
.as_ref()
.iter()
.map(|f| FeatureValue::new(*f, summary))
.collect();
let default = InternedString::new("default");
if requested_features.uses_default_features && feature_map.contains_key(&default) {
result.push(FeatureValue::Feature(default));
}
result
}
}
fn deps(
&self,
pkg_id: PackageId,
for_host: bool,
) -> Vec<(PackageId, Vec<(&'a Dependency, bool)>)> {
let platform_activated = |dep: &Dependency| -> bool {
if for_host || dep.is_build() {
return self
.target_data
.dep_platform_activated(dep, CompileKind::Host);
}
self.target_data
.dep_platform_activated(dep, self.requested_target)
};
self.resolve
.deps(pkg_id)
.map(|(dep_id, deps)| {
let is_proc_macro = self.is_proc_macro(dep_id);
let deps = deps
.iter()
.filter(|dep| {
if dep.platform().is_some()
&& self.opts.ignore_inactive_targets
&& !platform_activated(dep)
{
return false;
}
if self.opts.decouple_dev_deps && dep.kind() == DepKind::Development {
return false;
}
true
})
.map(|dep| {
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)
})
.filter(|(_id, deps)| !deps.is_empty())
.collect()
}
fn compare(&self) {
let mut found = false;
for ((pkg_id, dep_kind), features) in &self.activated_features {
let r_features = self.resolve.features(*pkg_id);
if !r_features.iter().eq(features.iter()) {
eprintln!(
"{}/{:?} features mismatch\nresolve: {:?}\nnew: {:?}\n",
pkg_id, dep_kind, r_features, features
);
found = true;
}
}
if found {
panic!("feature mismatch");
}
}
fn is_proc_macro(&self, package_id: PackageId) -> bool {
self.package_set
.get_one(package_id)
.expect("packages downloaded")
.proc_macro()
}
}