1use crate::manifest::{DEFAULT_EDITION, DEPS_SECTION, YogToml, to_snake_name};
4use std::fmt::Write as _;
5use std::path::{Path, PathBuf};
6
7pub fn generate_cargo_toml(meta: &YogToml, benches: &[crate::bench::BenchFile]) -> String {
11 let authors_toml = authors_line(meta);
12 let edition = meta.edition.as_deref().unwrap_or(DEFAULT_EDITION);
13 let patch = local_patch(meta);
14 let deps_lines = dependency_lines(meta);
15 format!(
16 r#"# Generated by yog from yog.toml — do not edit.
17[package]
18name = "{id}"
19version = "{version}"
20edition = "{edition}"
21description = {description:?}
22{authors_line}license = {license:?}
23
24[lib]
25name = "{lib_name}"
26crate-type = {crate_types}
27path = "../src/lib.rs"
28
29[dependencies]
30{api_dep}
31{deps}
32{bench}{patch}"#,
33 id = meta.id,
34 lib_name = to_snake_name(&meta.id),
35 version = meta.version,
36 edition = edition,
37 description = meta.description,
38 authors_line = authors_toml,
39 license = meta.license,
40 api_dep = meta.api_dep(),
41 deps = deps_lines.join("\n"),
42 patch = patch,
43 crate_types = if benches.is_empty() {
44 r#"["cdylib"]"#
45 } else {
46 r#"["cdylib", "rlib"]"#
47 },
48 bench = crate::bench::cargo_sections(meta, benches, &test_support(meta)),
49 )
50}
51
52fn authors_line(meta: &YogToml) -> String {
54 if meta.authors.is_empty() {
55 return String::new();
56 }
57 let list = meta
58 .authors
59 .iter()
60 .map(|a| format!("{a:?}"))
61 .collect::<Vec<_>>()
62 .join(", ");
63 format!("authors = [{list}]\n")
64}
65
66fn local_patch(meta: &YogToml) -> String {
71 let env_path = std::env::var(yog_ids::env::API_PATH).ok();
72 let Some(api_path) = meta.yog_api_path.as_deref().or(env_path.as_deref()) else {
73 return String::new();
74 };
75 let mut extra: Vec<&str> = meta
76 .dependencies
77 .iter()
78 .map(|(name, _)| name.as_str())
79 .filter(|n| n.starts_with(YOG_CRATE))
80 .collect();
81 if !extra.is_empty() {
82 extra.push(YOG_API);
83 }
84 local_api_patch(Path::new(api_path), &extra)
85}
86
87use yog_ids::rust_crate::{API as YOG_API, PREFIX as YOG_CRATE};
88
89fn dependency_lines(meta: &YogToml) -> Vec<String> {
94 let mut lines: Vec<String> = meta
95 .dependencies
96 .iter()
97 .map(|(name, spec)| format!("{name} = {spec}"))
98 .collect();
99 for (name, spec) in &meta.mod_dependencies {
100 let exports_name = crate::exports::crate_name(name);
101 let inner = spec
102 .strip_prefix('{')
103 .map(|s| s.trim_end_matches('}').trim());
104 lines.push(match inner {
105 Some("") => format!("{name} = {{ package = \"{exports_name}\" }}"),
106 Some(inner) => format!("{name} = {{ package = \"{exports_name}\", {inner} }}"),
107 None => format!("{name} = {{ package = \"{exports_name}\", version = {spec} }}"),
108 });
109 }
110 lines
111}
112
113const TEST_SUPPORT: &[&str] = &["yog-kit"];
115const TESTING_FEATURE: &str = "testing";
116
117fn test_support(meta: &YogToml) -> Vec<String> {
120 meta.dependencies
121 .iter()
122 .filter(|(name, _)| TEST_SUPPORT.contains(&name.as_str()))
123 .map(|(name, spec)| format!("{name} = {}", with_feature(spec, TESTING_FEATURE)))
124 .collect()
125}
126
127fn with_feature(spec: &str, feature: &str) -> String {
129 let spec = spec.trim();
130 let features = format!("features = [{feature:?}]");
131 match spec.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
132 Some(inner) if inner.trim().is_empty() => format!("{{ {features} }}"),
133 Some(inner) => format!("{{ {}, {features} }}", inner.trim()),
134 None => format!("{{ version = {spec}, {features} }}"),
135 }
136}
137
138pub fn yog_dependencies(cargo_toml: &str) -> Vec<String> {
140 let mut in_deps = false;
141 let mut out = Vec::new();
142 for line in cargo_toml.lines() {
143 let line = line.trim();
144 if line.starts_with('[') {
145 in_deps = line == DEPS_SECTION;
146 continue;
147 }
148 if !in_deps {
149 continue;
150 }
151 let Some((name, _)) = line.split_once('=') else {
152 continue;
153 };
154 let name = name.split('.').next().unwrap_or(name).trim();
156 if name.starts_with("yog-") && !out.iter().any(|n| n == name) {
157 out.push(name.to_string());
158 }
159 }
160 out
161}
162
163pub fn local_api_patch(api_path: &Path, extra: &[&str]) -> String {
168 let crates_dir = api_path
169 .parent()
170 .map(Path::to_path_buf)
171 .unwrap_or_else(|| PathBuf::from("crates"));
172 let mut names = std::fs::read_to_string(api_path.join("Cargo.toml"))
173 .map(|t| yog_dependencies(&t))
174 .unwrap_or_default();
175 if !names.iter().any(|n| n == "yog-interop") {
176 names.push("yog-interop".into());
177 }
178 for name in extra {
179 if !names.iter().any(|n| n == name) {
180 names.push((*name).to_string());
181 }
182 }
183 let mut i = 0;
186 while i < names.len() {
187 let deps = std::fs::read_to_string(crates_dir.join(&names[i]).join("Cargo.toml"))
188 .map(|t| yog_dependencies(&t))
189 .unwrap_or_default();
190 for dep in deps {
191 if !names.contains(&dep) {
192 names.push(dep);
193 }
194 }
195 i += 1;
196 }
197 let mut out = String::from("\n[patch.crates-io]\n");
198 for name in names {
199 let dir = crates_dir.join(&name);
200 if !dir.join("Cargo.toml").is_file() && name != "yog-interop" {
201 continue;
202 }
203 let abs = dir.canonicalize().unwrap_or(dir);
204 let _ = writeln!(out, "{name} = {{ path = {:?} }}", abs.to_string_lossy());
205 }
206 out
207}
208
209#[cfg(test)]
210mod tests {
211 #[test]
212 fn yog_dependencies_reads_the_dependencies_table() {
213 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";
214 assert_eq!(super::yog_dependencies(toml), ["yog-abi", "yog-ui"]);
215 }
216
217 #[test]
218 fn yog_dependencies_reads_workspace_inherited_lines() {
219 let toml = "[dependencies]\nyog-abi = { workspace = true }\nyog-ui = { workspace = true }\nlibc = { workspace = true }\n";
220 assert_eq!(super::yog_dependencies(toml), ["yog-abi", "yog-ui"]);
221 }
222}