use crate::cargo::feature_scanner::FeatureScanner;
use crate::cargo::multi_target_metadata::MultiTargetMetadata;
use crate::cargo::unify_types::{MemberEdit, OptionalFeature, PrunedFeature};
use crate::config::UnifyConfig;
use crate::progress;
use rustc_hash::FxHashMap;
use std::sync::Arc;
pub struct FeaturePruner<'a> {
metadata: &'a MultiTargetMetadata,
config: &'a UnifyConfig,
}
impl<'a> FeaturePruner<'a> {
pub fn new(metadata: &'a MultiTargetMetadata, config: &'a UnifyConfig) -> Self {
Self { metadata, config }
}
pub fn scan(&self) -> (Vec<PrunedFeature>, Vec<OptionalFeature>) {
let mut pruned = Vec::new();
let mut optional = Vec::new();
let results = FeatureScanner::analyze_workspace(self.metadata);
for result in &results {
for feature_name in &result.dead_features {
if self.config.should_preserve_feature(feature_name) {
continue;
}
pruned.push(PrunedFeature {
crate_name: Arc::from(result.crate_name.as_str()),
feature_name: Arc::from(feature_name.as_str()),
});
}
for feature_name in &result.optional_features {
let enables: Vec<Arc<str>> = self
.metadata
.workspace_packages()
.iter()
.find(|p| p.name == result.crate_name)
.and_then(|p| p.features.get(feature_name))
.map(|v| v.iter().map(|s| Arc::from(s.as_str())).collect())
.unwrap_or_default();
optional.push(OptionalFeature {
crate_name: Arc::from(result.crate_name.as_str()),
feature_name: Arc::from(feature_name.as_str()),
enables,
});
}
}
if !pruned.is_empty() || !optional.is_empty() {
let crate_count = results.len();
if !pruned.is_empty() {
progress!(
" Found {} dead features (empty no-ops) across {} crates",
pruned.len(),
crate_count
);
}
if !optional.is_empty() {
progress!(
" Found {} optional features (user-facing, not removed)",
optional.len()
);
}
}
(pruned, optional)
}
pub fn generate_prune_edits(&self, pruned: &[PrunedFeature]) -> FxHashMap<Arc<str>, Vec<MemberEdit>> {
let mut edits: FxHashMap<Arc<str>, Vec<MemberEdit>> = FxHashMap::default();
edits.reserve(pruned.len().min(32));
for pf in pruned {
let entry = edits.entry(Arc::clone(&pf.crate_name));
entry.or_default().push(MemberEdit::RemoveFeature {
feature_name: Arc::clone(&pf.feature_name),
});
}
edits
}
}