use super::*;
pub fn merge_missing_cfg_features(
existing: &str,
api: &ApiSurface,
wrapper_default_features: &[String],
core_crate_name: &str,
core_declared_features: &BTreeSet<String>,
excluded_default_features: &HashSet<&str>,
) -> anyhow::Result<Option<String>> {
validate_wrapper_default_features(
wrapper_default_features,
core_declared_features,
excluded_default_features,
)?;
let mut doc = existing
.parse::<toml_edit::DocumentMut>()
.context("existing manifest is not valid TOML")?;
let features_table_ref = doc.get("features").and_then(toml_edit::Item::as_table);
let declared: BTreeSet<String> = features_table_ref
.map(|table| table.iter().map(|(key, _)| key.to_string()).collect())
.unwrap_or_default();
let enabled_by_default = features_reachable_from_default(|name| {
features_table_ref
.and_then(|table| table.get(name))
.and_then(toml_edit::Item::as_array)
.into_iter()
.flat_map(toml_edit::Array::iter)
.filter_map(toml_edit::Value::as_str)
.filter(|member| !member.contains('/'))
.map(str::to_owned)
.collect()
});
let mut referenced: BTreeSet<String> = collect_cfg_features(api)
.into_iter()
.filter(|feature| core_declared_features.contains(feature))
.collect();
referenced.extend(wrapper_default_features.iter().filter(|name| !name.is_empty()).cloned());
let needs_declaration: BTreeSet<String> = referenced.difference(&declared).cloned().collect();
let needs_default: BTreeSet<String> = referenced
.difference(&enabled_by_default)
.filter(|name| !excluded_default_features.contains(name.as_str()))
.cloned()
.collect();
if needs_declaration.is_empty() && needs_default.is_empty() {
return Ok(None);
}
let features_table = doc
.entry("features")
.or_insert_with(|| toml_edit::Item::Table(toml_edit::Table::new()))
.as_table_mut()
.context("[features] exists in the manifest but is not a table")?;
for feature in &needs_declaration {
let mut forwarded = toml_edit::Array::new();
forwarded.push(format!("{core_crate_name}/{feature}"));
features_table.insert(feature, toml_edit::Item::Value(toml_edit::Value::Array(forwarded)));
}
if !needs_default.is_empty() {
append_default_features(features_table, &needs_default)?;
}
Ok(Some(doc.to_string()))
}
fn append_default_features(
features_table: &mut toml_edit::Table,
needs_default: &BTreeSet<String>,
) -> anyhow::Result<()> {
let default_array = features_table
.entry("default")
.or_insert_with(|| toml_edit::Item::Value(toml_edit::Value::Array(toml_edit::Array::new())))
.as_array_mut()
.context("features.default exists but is not an array")?;
let already_listed: BTreeSet<String> = default_array
.iter()
.filter_map(toml_edit::Value::as_str)
.map(str::to_owned)
.collect();
for feature in needs_default {
if !already_listed.contains(feature) {
default_array.push(feature.clone());
}
}
Ok(())
}
fn validate_wrapper_default_features(
wrapper_default_features: &[String],
core_declared_features: &BTreeSet<String>,
excluded_default_features: &HashSet<&str>,
) -> anyhow::Result<()> {
for feature in wrapper_default_features {
crate::core::config::validation::validate_wrapper_default_feature_name(feature)
.map_err(|error| anyhow::anyhow!("invalid wrapper default feature `{feature}`: {error}"))?;
if !core_declared_features.contains(feature) {
anyhow::bail!("wrapper default feature `{feature}` is not declared by the core crate");
}
if excluded_default_features.contains(feature.as_str()) {
anyhow::bail!("wrapper default feature `{feature}` cannot also be listed in excluded_default_features");
}
}
Ok(())
}