use crate::core::config::{Language, ResolvedCrateConfig};
pub(crate) fn render_core_dep(crate_name: &str, rel_path: &str, features: &str, version: &str) -> String {
if version.is_empty() {
format!("{crate_name} = {{ path = \"{rel_path}\"{features} }}")
} else {
format!("{crate_name} = {{ version = \"{version}\", path = \"{rel_path}\"{features} }}")
}
}
pub(crate) fn render_core_dep_with_overrides(
crate_name: &str,
rel_path: &str,
default_features: &str,
version: &str,
overrides: &[crate::core::config::FfiTargetDepOverride],
) -> (String, String) {
if overrides.is_empty() {
return (
render_core_dep(crate_name, rel_path, default_features, version),
String::new(),
);
}
let combined_cfg = if overrides.len() == 1 {
overrides[0].cfg.clone()
} else {
let cfgs: Vec<String> = overrides.iter().map(|o| o.cfg.clone()).collect();
format!("any({})", cfgs.join(", "))
};
let mut entries: Vec<(String, String)> = vec![(
format!("not({combined_cfg})"),
render_core_dep(crate_name, rel_path, default_features, version),
)];
for override_ in overrides {
let default_block = if override_.default_features {
String::new()
} else {
", default-features = false".to_string()
};
let feats = if override_.features.is_empty() {
String::new()
} else {
let quoted: Vec<String> = override_.features.iter().map(|f| format!("\"{f}\"")).collect();
format!(", features = [{}]", quoted.join(", "))
};
entries.push((
override_.cfg.clone(),
render_core_dep(crate_name, rel_path, &format!("{default_block}{feats}"), version),
));
}
(String::new(), join_sorted_target_dep_blocks(entries))
}
pub(crate) fn join_sorted_target_dep_blocks(mut entries: Vec<(String, String)>) -> String {
entries.sort_by(|a, b| a.0.cmp(&b.0));
entries
.into_iter()
.map(|(cfg, dep_line)| format!("[target.'cfg({cfg})'.dependencies]\n{dep_line}\n"))
.collect::<Vec<_>>()
.join("\n")
}
pub(crate) fn dependency_sort_key(line: &str) -> String {
let mut name = String::new();
let mut quote: Option<char> = None;
let mut escaped = false;
for character in line.chars() {
if escaped {
name.push(character);
escaped = false;
continue;
}
match quote {
Some('"') if character == '\\' => escaped = true,
Some(open) if character == open => quote = None,
Some(_) => name.push(character),
None if character == '"' || character == '\'' => quote = Some(character),
None if character == '.' || character == '=' => break,
None => name.push(character),
}
}
name.trim().to_owned()
}
pub(crate) fn sort_dependency_lines(lines: &mut [String]) {
lines.sort_by_key(|a| dependency_sort_key(a));
}
pub(crate) fn render_extra_deps(config: &ResolvedCrateConfig, lang: Language) -> String {
let deps = config.extra_deps_for_language(lang);
if deps.is_empty() {
return String::new();
}
let member_versions = workspace_member_versions(config);
let ws_dep_specs = workspace_dep_specs(config);
let mut lines: Vec<String> = deps
.iter()
.map(|(name, value)| match value {
toml::Value::String(version) => format!("{name} = \"{version}\""),
toml::Value::Table(table) => {
if table.get("workspace").and_then(|v| v.as_bool()) == Some(true) {
if let Some(concrete) = ws_dep_specs.get(name) {
return format!("{name} = {concrete}");
}
return format!("{name} = {value}");
}
let needs_version = table.contains_key("path") && !table.contains_key("version");
if let (true, Some(member_version)) = (needs_version, member_versions.get(name)) {
let mut injected = table.clone();
injected.insert("version".to_string(), toml::Value::String(member_version.clone()));
format!("{name} = {}", toml::Value::Table(injected))
} else {
format!("{name} = {value}")
}
}
other => format!("{name} = {other}"),
})
.collect();
sort_dependency_lines(&mut lines);
lines.join("\n")
}
fn workspace_member_versions(config: &ResolvedCrateConfig) -> std::collections::BTreeMap<String, String> {
let Some(root) = config.workspace_root.as_deref() else {
return std::collections::BTreeMap::new();
};
match crate::publish::workspace::workspace_member_crates(root) {
Ok(members) => members.versions,
Err(_) => std::collections::BTreeMap::new(),
}
}
fn workspace_dep_specs(config: &ResolvedCrateConfig) -> std::collections::BTreeMap<String, toml::Value> {
let start = config.workspace_root.clone().or_else(|| std::env::current_dir().ok());
let Some(mut dir) = start else {
return std::collections::BTreeMap::new();
};
if !dir.is_absolute()
&& let Ok(abs) = std::fs::canonicalize(&dir)
{
dir = abs;
}
loop {
let cargo_path = dir.join("Cargo.toml");
if let Ok(contents) = std::fs::read_to_string(&cargo_path)
&& let Ok(doc) = contents.parse::<toml_edit::DocumentMut>()
&& let Some(workspace) = doc.get("workspace")
&& let Some(dependencies) = workspace.get("dependencies")
&& let Some(table) = dependencies.as_table()
{
let mut result = std::collections::BTreeMap::new();
for (key, value) in table.iter() {
let val_str = value.to_string().trim().to_string();
let wrapped = format!("x = {}", val_str);
if let Ok(map) = toml::from_str::<std::collections::HashMap<String, toml::Value>>(&wrapped)
&& let Some(v) = map.get("x")
{
result.insert(key.to_string(), v.clone());
}
}
return result;
}
if !dir.pop() {
return std::collections::BTreeMap::new();
}
}
}
pub(crate) fn render_workspace_dep_or(config: &ResolvedCrateConfig, name: &str, fallback: &str) -> String {
if workspace_dep_specs(config).contains_key(name) {
format!("{name}.workspace = true")
} else {
format!("{name} = {fallback}")
}
}