use crate::core::config::{Language, ResolvedCrateConfig};
pub(crate) fn core_dep_features(config: &ResolvedCrateConfig, lang: Language) -> String {
core_dep_features_excluding(config, lang, &std::collections::HashSet::new())
}
pub(crate) fn core_dep_features_excluding(
config: &ResolvedCrateConfig,
lang: Language,
excluded: &std::collections::HashSet<&str>,
) -> String {
let features: Vec<&str> = config
.features_for_language(lang)
.iter()
.map(String::as_str)
.filter(|f| !excluded.contains(f))
.collect();
if features.is_empty() {
String::new()
} else {
let quoted: Vec<String> = features.iter().map(|f| format!("\"{f}\"")).collect();
format!(", features = [{}]", quoted.join(", "))
}
}
pub(crate) fn core_crate_manifest_path(config: &ResolvedCrateConfig) -> Option<std::path::PathBuf> {
let workspace_root = config.workspace_root.as_deref()?;
let first_source = config.sources.first()?;
let mut current = std::path::Path::new(first_source).parent();
while let Some(dir) = current {
if dir.file_name().is_some_and(|n| n == "src") {
let crate_dir = dir.parent()?;
return Some(workspace_root.join(crate_dir).join("Cargo.toml"));
}
current = dir.parent();
}
None
}
fn resolve_core_aggregate_features(
features_table: &toml::value::Table,
aggregate: &str,
) -> Option<std::collections::BTreeSet<String>> {
let _ = features_table.get(aggregate)?;
let mut reachable: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
let mut queue: Vec<String> = vec![aggregate.to_string()];
let mut visited: std::collections::HashSet<String> = std::collections::HashSet::new();
while let Some(name) = queue.pop() {
if !visited.insert(name.clone()) {
continue;
}
let Some(members) = features_table.get(&name).and_then(|v| v.as_array()) else {
continue;
};
for member in members {
let Some(token) = member.as_str() else { continue };
if token.starts_with("dep:") || token.contains('/') {
continue;
}
reachable.insert(token.to_string());
if features_table.contains_key(token) {
queue.push(token.to_string());
}
}
}
Some(reachable)
}
pub(crate) fn core_feature_closure(
config: &ResolvedCrateConfig,
requested: &[String],
) -> (std::collections::BTreeSet<String>, std::collections::BTreeSet<String>) {
let mut active: std::collections::BTreeSet<String> = requested.iter().cloned().collect();
let no_defaults = std::collections::BTreeSet::new();
let Some(manifest_path) = core_crate_manifest_path(config) else {
return (active, no_defaults);
};
let Ok(contents) = std::fs::read_to_string(&manifest_path) else {
return (active, no_defaults);
};
let Ok(doc) = toml::from_str::<toml::Value>(&contents) else {
return (active, no_defaults);
};
let Some(features_table) = doc.get("features").and_then(|v| v.as_table()) else {
return (active, no_defaults);
};
for name in requested {
if let Some(members) = resolve_core_aggregate_features(features_table, name) {
active.extend(members);
}
}
let defaults: std::collections::BTreeSet<String> = features_table
.get("default")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str()).map(String::from).collect())
.unwrap_or_default();
(active, defaults)
}
pub(crate) fn android_target_feature_line(
config: &ResolvedCrateConfig,
passthrough_feature_names: &[&str],
) -> Option<String> {
android_target_feature_line_for_dep(config, &config.name, passthrough_feature_names)
}
pub(crate) fn android_target_feature_line_for_dep(
config: &ResolvedCrateConfig,
core_dep_key: &str,
passthrough_feature_names: &[&str],
) -> Option<String> {
let manifest_path = core_crate_manifest_path(config)?;
let contents = std::fs::read_to_string(&manifest_path).ok()?;
let doc = toml::from_str::<toml::Value>(&contents).ok()?;
let features_table = doc.get("features")?.as_table()?;
let aggregate_members = resolve_core_aggregate_features(features_table, "android-target")?;
let mut selected: Vec<&str> = passthrough_feature_names
.iter()
.copied()
.filter(|name| *name != "full" && aggregate_members.contains(*name))
.collect();
selected.sort_unstable();
selected.dedup();
let mut tokens: Vec<String> = vec![format!("{core_dep_key}/android-target")];
tokens.extend(selected.iter().map(|name| (*name).to_string()));
let list = tokens.iter().map(|t| format!("\"{t}\"")).collect::<Vec<_>>().join(", ");
Some(format!("android-target = [{list}]"))
}