use crate::manifest::{DEFAULT_EDITION, DEPS_SECTION, YogToml, to_snake_name};
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
pub fn generate_cargo_toml(meta: &YogToml, benches: &[crate::bench::BenchFile]) -> String {
let authors_toml = authors_line(meta);
let edition = meta.edition.as_deref().unwrap_or(DEFAULT_EDITION);
let patch = local_patch(meta);
let deps_lines = dependency_lines(meta);
format!(
r#"# Generated by yog from yog.toml — do not edit.
[package]
name = "{id}"
version = "{version}"
edition = "{edition}"
description = {description:?}
{authors_line}license = {license:?}
[lib]
name = "{lib_name}"
crate-type = {crate_types}
path = "../src/lib.rs"
[dependencies]
{api_dep}
{deps}
{bench}{patch}"#,
id = meta.id,
lib_name = to_snake_name(&meta.id),
version = meta.version,
edition = edition,
description = meta.description,
authors_line = authors_toml,
license = meta.license,
api_dep = meta.api_dep(),
deps = deps_lines.join("\n"),
patch = patch,
crate_types = if benches.is_empty() {
r#"["cdylib"]"#
} else {
r#"["cdylib", "rlib"]"#
},
bench = crate::bench::cargo_sections(meta, benches, &test_support(meta)),
)
}
fn authors_line(meta: &YogToml) -> String {
if meta.authors.is_empty() {
return String::new();
}
let list = meta
.authors
.iter()
.map(|a| format!("{a:?}"))
.collect::<Vec<_>>()
.join(", ");
format!("authors = [{list}]\n")
}
fn local_patch(meta: &YogToml) -> String {
let env_path = std::env::var(yog_ids::env::API_PATH).ok();
let Some(api_path) = meta.yog_api_path.as_deref().or(env_path.as_deref()) else {
return String::new();
};
let mut extra: Vec<&str> = meta
.dependencies
.iter()
.map(|(name, _)| name.as_str())
.filter(|n| n.starts_with(YOG_CRATE))
.collect();
if !extra.is_empty() {
extra.push(YOG_API);
}
local_api_patch(Path::new(api_path), &extra)
}
use yog_ids::rust_crate::{API as YOG_API, PREFIX as YOG_CRATE};
fn dependency_lines(meta: &YogToml) -> Vec<String> {
let mut lines: Vec<String> = meta
.dependencies
.iter()
.map(|(name, spec)| format!("{name} = {spec}"))
.collect();
for (name, spec) in &meta.mod_dependencies {
let exports_name = crate::exports::crate_name(name);
let inner = spec
.strip_prefix('{')
.map(|s| s.trim_end_matches('}').trim());
lines.push(match inner {
Some("") => format!("{name} = {{ package = \"{exports_name}\" }}"),
Some(inner) => format!("{name} = {{ package = \"{exports_name}\", {inner} }}"),
None => format!("{name} = {{ package = \"{exports_name}\", version = {spec} }}"),
});
}
lines
}
const TEST_SUPPORT: &[&str] = &["yog-kit"];
const TESTING_FEATURE: &str = "testing";
fn test_support(meta: &YogToml) -> Vec<String> {
meta.dependencies
.iter()
.filter(|(name, _)| TEST_SUPPORT.contains(&name.as_str()))
.map(|(name, spec)| format!("{name} = {}", with_feature(spec, TESTING_FEATURE)))
.collect()
}
fn with_feature(spec: &str, feature: &str) -> String {
let spec = spec.trim();
let features = format!("features = [{feature:?}]");
match spec.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
Some(inner) if inner.trim().is_empty() => format!("{{ {features} }}"),
Some(inner) => format!("{{ {}, {features} }}", inner.trim()),
None => format!("{{ version = {spec}, {features} }}"),
}
}
pub fn yog_dependencies(cargo_toml: &str) -> Vec<String> {
let mut in_deps = false;
let mut out = Vec::new();
for line in cargo_toml.lines() {
let line = line.trim();
if line.starts_with('[') {
in_deps = line == DEPS_SECTION;
continue;
}
if !in_deps {
continue;
}
let Some((name, _)) = line.split_once('=') else {
continue;
};
let name = name.split('.').next().unwrap_or(name).trim();
if name.starts_with("yog-") && !out.iter().any(|n| n == name) {
out.push(name.to_string());
}
}
out
}
pub fn local_api_patch(api_path: &Path, extra: &[&str]) -> String {
let crates_dir = api_path
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("crates"));
let mut names = std::fs::read_to_string(api_path.join("Cargo.toml"))
.map(|t| yog_dependencies(&t))
.unwrap_or_default();
if !names.iter().any(|n| n == "yog-interop") {
names.push("yog-interop".into());
}
for name in extra {
if !names.iter().any(|n| n == name) {
names.push((*name).to_string());
}
}
let mut i = 0;
while i < names.len() {
let deps = std::fs::read_to_string(crates_dir.join(&names[i]).join("Cargo.toml"))
.map(|t| yog_dependencies(&t))
.unwrap_or_default();
for dep in deps {
if !names.contains(&dep) {
names.push(dep);
}
}
i += 1;
}
let mut out = String::from("\n[patch.crates-io]\n");
for name in names {
let dir = crates_dir.join(&name);
if !dir.join("Cargo.toml").is_file() && name != "yog-interop" {
continue;
}
let abs = dir.canonicalize().unwrap_or(dir);
let _ = writeln!(out, "{name} = {{ path = {:?} }}", abs.to_string_lossy());
}
out
}
#[cfg(test)]
mod tests {
#[test]
fn yog_dependencies_reads_the_dependencies_table() {
let toml = "[package]\nname = \"yog-api\"\n\n[dependencies]\nyog-abi = \"0.25\"\nyog-ui = { version = \"0.35\" }\nserde = \"1\"\n\n[dev-dependencies]\nyog-test = \"1\"\n";
assert_eq!(super::yog_dependencies(toml), ["yog-abi", "yog-ui"]);
}
#[test]
fn yog_dependencies_reads_workspace_inherited_lines() {
let toml = "[dependencies]\nyog-abi = { workspace = true }\nyog-ui = { workspace = true }\nlibc = { workspace = true }\n";
assert_eq!(super::yog_dependencies(toml), ["yog-abi", "yog-ui"]);
}
}