use std::collections::{HashMap, HashSet};
use cargo_metadata::{Metadata, Package};
use itertools::{Either, Itertools};
#[derive(Clone, Debug)]
pub enum FeatureEffect {
EnableDepFeature {
feature: String,
dependency: String,
enable_optional: bool,
},
EnableFeature(String),
EnableOptionalDep(String),
}
#[derive(Clone, Debug)]
pub struct Feature {
name: String,
effects: Vec<FeatureEffect>,
}
#[derive(Clone, Debug)]
pub struct Crate {
pub name: String,
pub features: Vec<Feature>,
pub optional_dependencies: Vec<String>,
pub other_dependencies: Vec<String>,
}
impl Eq for Crate {}
impl PartialEq for Crate {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl std::hash::Hash for Crate {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
pub enum Depdenency {
Optional(String),
Required(String),
}
#[derive(Debug)]
pub struct FeatureGraph {
workspace_root: String,
crates: Vec<Crate>,
}
impl FeatureGraph {
pub fn dependencies_for_features(
&self,
features: &[String],
include_default: bool,
) -> Vec<&str> {
let root = self
.crates
.iter()
.find(|c| c.name == self.workspace_root)
.unwrap();
let mut buffer = Default::default();
self.dependencies_for_features_on_crate(root, features, include_default, &mut buffer);
buffer.iter().map(|c| c.name.as_str()).collect()
}
fn dependencies_for_features_on_crate<'a>(
&'a self,
crate_: &'a Crate,
features: &[String],
include_default: bool,
buffer: &mut HashSet<&'a Crate>,
) {
let active_features = features
.iter()
.map(|f| {
crate_
.features
.iter()
.find(|cf| cf.name == *f || (include_default && cf.name == "default"))
.unwrap_or_else(|| panic!("Feature '{}' not found in crate {}", f, crate_.name))
})
.collect::<Vec<_>>();
let effects = Self::normalize_features(active_features.as_slice(), &crate_.features);
let mut deps: HashMap<&Crate, Vec<String>> = Default::default();
for e in &effects {
match e {
FeatureEffect::EnableDepFeature {
feature,
dependency,
enable_optional,
} => {
if *enable_optional {
deps.entry(self.crates.iter().find(|c| c.name == *dependency).unwrap())
.or_default()
.push(feature.to_owned());
}
}
FeatureEffect::EnableOptionalDep(d) => {
_ = deps
.entry(self.crates.iter().find(|c| c.name == *d).unwrap())
.or_default()
}
_ => unreachable!(),
};
}
deps.extend(crate_.other_dependencies.iter().filter_map(|d| {
self.crates
.iter()
.find(|c| c.name == *d)
.map(|c| (c, vec![]))
}));
for (dep, features) in deps.iter() {
buffer.insert(dep);
self.dependencies_for_features_on_crate(dep, features, include_default, buffer);
}
}
fn normalize_features(
active_features: &[&Feature],
features: &[Feature],
) -> Vec<FeatureEffect> {
let mut stack = active_features.to_vec();
let mut result = Vec::new();
while let Some(feature) = stack.pop() {
for effect in &feature.effects {
match effect {
FeatureEffect::EnableFeature(name) => {
stack.push(
features
.iter()
.chain(features.iter())
.find(|f| f.name == *name)
.unwrap(),
);
}
_ => result.push(effect.clone()),
}
}
}
result
}
pub fn from_metadata(meta: &Metadata, workspace_root: &str) -> Self {
let crates = meta
.workspace_packages()
.iter()
.map(|p| Self::process_crate(p))
.collect();
Self {
workspace_root: workspace_root.to_owned(),
crates,
}
}
fn process_crate(meta: &Package) -> Crate {
let (optional_dependencies, other_dependencies) = meta
.dependencies
.iter()
.map(|f| (f.name.clone(), f.optional))
.partition_map(|(name, opt)| {
if opt {
Either::Left(name)
} else {
Either::Right(name)
}
});
let features = meta
.features
.iter()
.map(|(name, effects)| {
let effects = effects
.iter()
.map(|effect| {
if let Some(name) = effect.strip_prefix("dep:") {
FeatureEffect::EnableOptionalDep(name.to_string())
} else {
let parts = effect.split('/').collect::<Vec<_>>();
if parts.len() > 2 {
panic!("Invalid feature effect: {}", effect);
} else if parts.len() == 1 {
return FeatureEffect::EnableFeature(parts[0].to_owned());
} else {
return FeatureEffect::EnableDepFeature {
feature: parts[1].to_owned(),
dependency: parts[0]
.strip_suffix('?')
.unwrap_or(parts[0])
.to_owned(),
enable_optional: !parts[0].ends_with('?'),
};
}
}
})
.collect();
Feature {
name: name.clone(),
effects,
}
})
.collect();
Crate {
name: meta.name.clone(),
features,
optional_dependencies,
other_dependencies,
}
}
}