espforge_lib/generate/
cargo.rs1use super::manifest;
2use anyhow::{Context, Result};
3use log::{debug, info, warn};
4use std::fs;
5use std::path::Path;
6use toml_edit::{Array, DocumentMut, Item, Table, Value};
7
8const CARGO_TEMPLATE_NAME: &str = "Cargo.toml.tera";
9
10pub fn update_manifest(
11 project_path: &Path,
12 config_dir: &Path,
13 chip: &str,
14 context: &tera::Context
15) -> Result<()> {
16 let cargo_path = project_path.join("Cargo.toml");
17 let cargo_content = fs::read_to_string(&cargo_path)
18 .with_context(|| format!("Failed to read {}", cargo_path.display()))?;
19
20 let mut doc = cargo_content.parse::<DocumentMut>()
21 .context("Failed to parse Cargo.toml")?;
22
23 if let Some(dynamic_file) = crate::template_utils::get_templates()
25 .get_file("_dynamic/Cargo.toml.tera")
26 {
27 let content = dynamic_file.contents_utf8()
28 .context("_dynamic/Cargo.toml.tera is invalid UTF-8")?;
29 let rendered = tera::Tera::one_off(content, context, true)
30 .context("Failed to render _dynamic/Cargo.toml.tera")?;
31 let dynamic_doc = rendered.parse::<DocumentMut>()
32 .context("Failed to parse rendered _dynamic cargo template")?;
33
34 merge_documents(&mut doc, &dynamic_doc);
35 }
36
37 let user_tera = config_dir.join("Cargo.toml.tera");
39 if user_tera.exists() {
40 let content = fs::read_to_string(&user_tera)?;
41 let rendered = tera::Tera::one_off(&content, context, true)
42 .context("Failed to render user Cargo.toml.tera")?;
43 let user_doc = rendered.parse::<DocumentMut>()
44 .context("Failed to parse user Cargo.toml.tera")?;
45
46 merge_documents(&mut doc, &user_doc);
47 }
48
49 add_chip_features(&mut doc, chip)?;
51 ensure_workspace_exists(&mut doc);
52 merge_device_dependencies(&mut doc)?;
53
54 fs::write(&cargo_path, doc.to_string())
55 .with_context(|| format!("Failed to write {}", cargo_path.display()))?;
56
57 info!("Updated Cargo.toml");
58 Ok(())
59}
60
61fn merge_documents(target: &mut DocumentMut, source: &DocumentMut) {
62 for section in ["dependencies", "build-dependencies", "dev-dependencies", "features"] {
64 if let Some(source_table) = source.get(section).and_then(|i| i.as_table()) {
65 if !target.contains_key(section) {
66 target[section] = Item::Table(Table::new());
67 }
68 let target_table = target[section].as_table_mut().unwrap();
69
70 for (k, v) in source_table.iter() {
71 if !target_table.contains_key(k) {
73 target_table.insert(k, v.clone());
74 }
75 }
76 }
77 }
78}
79
80fn add_chip_features(doc: &mut DocumentMut, chip: &str) -> Result<()> {
81 if !doc.contains_key("features") {
82 doc["features"] = Item::Table(Table::new());
83 }
84
85 let features = doc["features"]
86 .as_table_mut()
87 .context("'features' is not a table")?;
88
89 let default_item = features.entry("default")
94 .or_insert(Item::Value(Value::Array(Array::new())));
95
96 let default_array = default_item
97 .as_value_mut()
98 .context("'default' feature is not a value")?
99 .as_array_mut()
100 .context("'default' feature is not an array")?;
101
102 let exists = default_array.iter().any(|v| v.as_str() == Some(chip));
104 if !exists {
105 default_array.push(chip);
106 }
107
108 if !features.contains_key(chip) {
109 features[chip] = Item::Value(Value::Array(Array::new()));
110 }
111
112 debug!("Added feature for chip: {}", chip);
113 Ok(())
114}
115
116fn ensure_workspace_exists(doc: &mut DocumentMut) {
117 if !doc.contains_key("workspace") {
118 doc["workspace"] = Item::Table(Table::new());
119 }
120}
121
122fn merge_device_dependencies(doc: &mut DocumentMut) -> Result<()> {
123 if !doc.contains_key("dependencies") {
124 doc["dependencies"] = Item::Table(Table::new());
125 }
126
127 let root_deps = doc["dependencies"]
128 .as_table_mut()
129 .context("'dependencies' is not a table")?;
130
131 for subdir in manifest::devices_dir().dirs() {
132 if let Some(cargo_file) = find_cargo_template(subdir) {
133 let device_toml_str = cargo_file
134 .contents_utf8()
135 .context("Device Cargo.toml.tera is not valid UTF-8")?;
136
137 let device_doc = device_toml_str.parse::<DocumentMut>()
138 .with_context(|| {
139 format!("Failed to parse Cargo.toml.tera for device {:?}", subdir.path())
140 })?;
141
142 if let Some(deps) = device_doc.get("dependencies").and_then(|d| d.as_table()) {
143 for (key, value) in deps.iter() {
144 if root_deps.contains_key(key) {
145 warn!("Dependency '{}' already exists, skipping from device", key);
146 } else {
147 root_deps.insert(key, value.clone());
148 debug!("Added dependency: {}", key);
149 }
150 }
151 }
152 }
153 }
154
155 Ok(())
156}
157
158fn find_cargo_template<'a>(subdir: &'a include_dir::Dir<'a>) -> Option<&'a include_dir::File<'a>> {
159 subdir.files()
160 .find(|f| f.path().file_name().and_then(|n| n.to_str()) == Some(CARGO_TEMPLATE_NAME))
161}
162