1use crate::manifest::{
4 DEFAULT_EDITION, DEPS_SECTION, YogToml, to_snake_name, workspace_rkyv_version,
5};
6use std::fmt::Write as _;
7use std::path::{Path, PathBuf};
8
9pub fn generate_cargo_toml(meta: &YogToml, benches: &[crate::bench::BenchFile]) -> String {
13 let authors_toml = if meta.authors.is_empty() {
14 String::new()
15 } else {
16 let list = meta
17 .authors
18 .iter()
19 .map(|a| format!("{a:?}"))
20 .collect::<Vec<_>>()
21 .join(", ");
22 format!("authors = [{list}]\n")
23 };
24 let edition = meta.edition.as_deref().unwrap_or(DEFAULT_EDITION);
25
26 let patch = if meta.yog_api_path.is_some() || std::env::var("YOG_API_PATH").is_ok() {
30 let env_path = std::env::var("YOG_API_PATH").ok();
31 let api_path = meta
32 .yog_api_path
33 .as_deref()
34 .or(env_path.as_deref())
35 .unwrap_or("");
36 let mut extra: Vec<&str> = meta
40 .dependencies
41 .iter()
42 .map(|(name, _)| name.as_str())
43 .filter(|n| n.starts_with("yog-"))
44 .collect();
45 if !extra.is_empty() {
46 extra.push("yog-api");
47 }
48 local_api_patch(Path::new(api_path), &extra)
49 } else {
50 String::new()
51 };
52
53 let mut deps_lines: Vec<String> = Vec::new();
56 for (name, spec) in &meta.dependencies {
57 deps_lines.push(format!("{} = {}", name, spec));
58 }
59
60 for (name, spec) in &meta.mod_dependencies {
69 let exports_name = format!("{}_exports", name.replace('-', "_"));
70 let dep = if spec.starts_with('{') {
71 let inner = spec.trim_start_matches('{').trim_end_matches('}').trim();
72 if inner.is_empty() {
73 format!("{name} = {{ package = \"{exports_name}\" }}")
74 } else {
75 format!("{name} = {{ package = \"{exports_name}\", {inner} }}")
76 }
77 } else {
78 format!("{name} = {{ package = \"{exports_name}\", version = {spec} }}")
79 };
80 deps_lines.push(dep);
81 }
82
83 format!(
84 r#"# Generated by yog from yog.toml — do not edit.
85[package]
86name = "{id}"
87version = "{version}"
88edition = "{edition}"
89description = {description:?}
90{authors_line}license = {license:?}
91
92[lib]
93name = "{lib_name}"
94crate-type = {crate_types}
95path = "../src/lib.rs"
96
97[dependencies]
98{api_dep}
99rkyv = "{rkyv_ver}"
100{deps}
101{bench}{patch}"#,
102 id = meta.id,
103 rkyv_ver = workspace_rkyv_version(),
104 lib_name = to_snake_name(&meta.id),
105 version = meta.version,
106 edition = edition,
107 description = meta.description,
108 authors_line = authors_toml,
109 license = meta.license,
110 api_dep = meta.api_dep(),
111 deps = deps_lines.join("\n"),
112 patch = patch,
113 crate_types = if benches.is_empty() {
114 r#"["cdylib"]"#
115 } else {
116 r#"["cdylib", "rlib"]"#
117 },
118 bench = crate::bench::cargo_sections(meta, benches),
119 )
120}
121
122pub fn yog_dependencies(cargo_toml: &str) -> Vec<String> {
124 let mut in_deps = false;
125 let mut out = Vec::new();
126 for line in cargo_toml.lines() {
127 let line = line.trim();
128 if line.starts_with('[') {
129 in_deps = line == DEPS_SECTION;
130 continue;
131 }
132 if !in_deps {
133 continue;
134 }
135 let Some((name, _)) = line.split_once('=') else {
136 continue;
137 };
138 let name = name.trim();
139 if name.starts_with("yog-") && !out.iter().any(|n| n == name) {
140 out.push(name.to_string());
141 }
142 }
143 out
144}
145
146pub fn local_api_patch(api_path: &Path, extra: &[&str]) -> String {
151 let crates_dir = api_path
152 .parent()
153 .map(Path::to_path_buf)
154 .unwrap_or_else(|| PathBuf::from("crates"));
155 let mut names = std::fs::read_to_string(api_path.join("Cargo.toml"))
156 .map(|t| yog_dependencies(&t))
157 .unwrap_or_default();
158 if !names.iter().any(|n| n == "yog-interop") {
159 names.push("yog-interop".into());
160 }
161 for name in extra {
162 if !names.iter().any(|n| n == name) {
163 names.push((*name).to_string());
164 }
165 }
166 let mut i = 0;
169 while i < names.len() {
170 let deps = std::fs::read_to_string(crates_dir.join(&names[i]).join("Cargo.toml"))
171 .map(|t| yog_dependencies(&t))
172 .unwrap_or_default();
173 for dep in deps {
174 if !names.contains(&dep) {
175 names.push(dep);
176 }
177 }
178 i += 1;
179 }
180 let mut out = String::from("\n[patch.crates-io]\n");
181 for name in names {
182 let dir = crates_dir.join(&name);
183 if !dir.join("Cargo.toml").is_file() && name != "yog-interop" {
184 continue;
185 }
186 let abs = dir.canonicalize().unwrap_or(dir);
187 let _ = writeln!(out, "{name} = {{ path = {:?} }}", abs.to_string_lossy());
188 }
189 out
190}
191
192#[cfg(test)]
193mod tests {
194 #[test]
195 fn yog_dependencies_reads_the_dependencies_table() {
196 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";
197 assert_eq!(super::yog_dependencies(toml), ["yog-abi", "yog-ui"]);
198 }
199}