use std::collections::{BTreeMap, HashSet};
use displaydoc::Display;
use error_stack::Report;
use thiserror::Error;
use tracing::{debug, trace};
use crate::{fields, manifest::dependency::DependencyCursor};
#[derive(Debug, Error, Display)]
pub enum FetchFeaturesError {
MetadataFetchError,
NotFound,
RootNotFound,
ResolutionNotFound,
}
#[tracing::instrument(skip(metadata), fields(root_package, dependency))]
pub fn fetch_features<'a>(
metadata: &'a cargo_metadata::Metadata,
cursor: &DependencyCursor,
) -> Result<&'a FeaturesList, Report<FetchFeaturesError>> {
let resolve = &metadata
.resolve
.as_ref()
.ok_or(FetchFeaturesError::RootNotFound)?;
let root_package = metadata
.root_package()
.ok_or(FetchFeaturesError::ResolutionNotFound)?;
fields!(root_package = ?root_package);
let root_dependency = root_package
.dependencies
.iter()
.find(|dep| {
dep.rename.as_ref().is_some_and(|r| *r == cursor.name) || dep.name == cursor.name
})
.ok_or(FetchFeaturesError::NotFound)?;
let resolver_root = resolve
.nodes
.iter()
.find(|node| node.id == root_package.id)
.ok_or(FetchFeaturesError::RootNotFound)?;
let package = resolver_root
.dependencies
.iter()
.find_map(|resolver_dependency_id| {
metadata.packages.iter().find(|package| {
package.id == *resolver_dependency_id && package.name == root_dependency.name
})
})
.ok_or(FetchFeaturesError::NotFound)?;
Ok(&package.features)
}
pub type FeaturesList = BTreeMap<String, Vec<String>>;
pub type FeaturesGraph<'a> = BTreeMap<&'a str, Vec<&'a str>>;
pub fn create_implied_graph<'a>(
features: &'a FeaturesList,
enabled: impl IntoIterator<Item = &'a str>,
default_enabled: bool,
) -> FeaturesGraph<'a> {
let mut implicit_from: FeaturesGraph<'a> = features
.keys()
.map(|k| (k.as_str(), vec![]))
.collect::<FeaturesGraph<'a>>();
let mut via_chain: Vec<&'a str> = Vec::new();
let mut stack: Vec<(&'a str, usize)> = enabled.into_iter().map(|f| (f, 0)).collect();
let mut visited = HashSet::new();
if default_enabled {
stack.push(("default", 0));
}
while let Some((feature, via_chain_length)) = stack.pop()
&& visited.insert(feature)
&& let Some(deps) = features.get(feature)
{
trace!(?via_chain, "popped feature: {}", feature);
via_chain.truncate(via_chain_length);
via_chain.push(feature);
debug!(
?via_chain,
?deps,
"Updating via_chain for feature: {}",
feature
);
let filtered = deps
.iter()
.filter(|dep| !dep.starts_with("dep:") && !dep.contains('/'));
for dep in filtered {
trace!(?via_chain, "filtered dep: {}", dep);
implicit_from.entry(dep).insert_entry(via_chain.clone());
stack.push((dep.as_str(), via_chain.len()));
}
}
implicit_from
}