flowlang/builder/
cargo.rs1use std::collections::HashMap;
5use std::fs::create_dir_all;
6use std::path::PathBuf;
7
8use ndata::dataobject::DataObject;
9
10use super::util::{get_project_top_level_path, read_lines_from_file, write_lines_to_file};
11
12pub(crate) fn update_cargo_toml(cargo_toml_path: &PathBuf, cargo_config: &DataObject, lib_name: &str, default_package_name: &str, is_ffi: bool) -> bool {
15 let mut file_was_created = false;
16 if !cargo_toml_path.exists() {
17 if default_package_name != "main_project" {
18 println!("Cargo.toml not found at {:?} for sub-project '{}' (library {}), creating default.", cargo_toml_path, default_package_name, lib_name);
19
20 let (flowlang_dep_line, ndata_dep_line) = get_core_dependency_lines();
22
23 let crate_types_str = if is_ffi {
24 "[\"cdylib\", \"rlib\"]".to_string()
25 } else if cargo_config.has("crate_types") {
26 let crate_types_da = cargo_config.get_array("crate_types");
27 let types: Vec<String> = crate_types_da.objects().iter()
28 .map(|val| val.string())
29 .collect();
30 if !types.is_empty() {
31 format!("[{}]", types.iter().map(|s| format!("\"{}\"", s)).collect::<Vec<String>>().join(", "))
32 } else {
33 "[\"rlib\"]".to_string()
34 }
35 } else {
36 "[\"rlib\"]".to_string()
37 };
38
39 let default_content = format!(
40r#"[package]
41name = "{}"
42version = "0.1.0"
43edition = "2021"
44
45[lib]
46crate-type = {}
47
48[dependencies]
49{}
50{}
51serde = {{ version = "1.0", features = ["derive"], optional = true }}
52serde_json = {{ version = "1.0", optional = true }}
53
54[features]
55reload = []
56default = []
57"# , default_package_name, crate_types_str, flowlang_dep_line, ndata_dep_line);
58
59 if let Some(parent_dir) = cargo_toml_path.parent() {
60 create_dir_all(parent_dir).expect("Failed to create parent directory for new Cargo.toml");
61 }
62 std::fs::write(&cargo_toml_path, default_content)
63 .expect(&format!("Failed to write default Cargo.toml to {:?}", cargo_toml_path));
64 file_was_created = true;
65 } else {
66 println!("WARNING: Main project Cargo.toml not found at {:?}, cannot perform updates.", cargo_toml_path);
67 return false;
68 }
69 }
70
71 let mut config_caused_modification = false;
72 let mut lines = read_lines_from_file(&cargo_toml_path)
73 .expect(&format!("Failed to read Cargo.toml at {:?}", cargo_toml_path));
74
75 if cargo_config.has("dependencies") {
76 let mut dependencies_map = HashMap::new();
77 let dependencies_insertion_line = find_section_insertion_line(&lines, "[dependencies]", &mut dependencies_map);
78 let new_dependencies = cargo_config.get_object("dependencies");
79 if new_dependencies.clone().keys().len() > 0 {
80 if update_cargo_section_lines(
81 &mut lines,
82 &new_dependencies,
83 &mut dependencies_map,
84 dependencies_insertion_line,
85 "Dependency",
86 lib_name,
87 ).0 {
88 config_caused_modification = true;
89 }
90 }
91 }
92
93 if config_caused_modification {
94 println!("Rewriting {}", cargo_toml_path.display());
95 write_lines_to_file(&cargo_toml_path, &lines)
96 .expect(&format!("Failed to write to Cargo.toml at {:?}", cargo_toml_path));
97 }
98
99 file_was_created || config_caused_modification
100}
101
102fn get_core_dependency_lines() -> (String, String) {
104 let root_cargo_path = get_project_top_level_path().join("Cargo.toml");
105 let fallback_flowlang = "flowlang = { version = \"0.3.25\" }".to_string();
106 let fallback_ndata = "ndata = { version = \"0.3.14\" }".to_string();
107
108 if !root_cargo_path.exists() {
109 println!("WARNING: Root Cargo.toml not found. Falling back to default dependency versions.");
110 return (fallback_flowlang, fallback_ndata);
111 }
112
113 let lines = read_lines_from_file(&root_cargo_path).unwrap_or_else(|_| {
114 println!("WARNING: Could not read root Cargo.toml. Falling back to default dependency versions.");
115 vec![]
116 });
117
118 let mut flowlang_line = None;
119 let mut ndata_line = None;
120 let mut in_deps_section = false;
121
122 for line in lines {
123 let trimmed_line = line.trim();
124 if trimmed_line == "[dependencies]" {
125 in_deps_section = true;
126 continue;
127 }
128 if in_deps_section && trimmed_line.starts_with('[') {
129 break; }
131 if in_deps_section {
132 if trimmed_line.starts_with("flowlang") {
133 flowlang_line = Some(line.clone());
134 } else if trimmed_line.starts_with("ndata") {
135 ndata_line = Some(line.clone());
136 }
137 }
138 if flowlang_line.is_some() && ndata_line.is_some() {
139 break;
140 }
141 }
142 (flowlang_line.unwrap_or(fallback_flowlang), ndata_line.unwrap_or(fallback_ndata))
143}
144
145pub(crate) fn find_section_insertion_line(
147 lines: &[String],
148 section_marker: &str,
149 existing_items_map: &mut HashMap<String, String>,
150) -> usize {
151 let mut section_start_idx: Option<usize> = None;
152 let mut in_section = false;
153
154 for (i, line_content) in lines.iter().enumerate() {
155 let trimmed_line = line_content.trim();
156 if trimmed_line == section_marker {
157 section_start_idx = Some(i);
158 in_section = true;
159 existing_items_map.clear();
160 continue;
161 }
162
163 if trimmed_line.starts_with('[') && in_section {
164 break;
165 }
166
167 if in_section {
168 if let Some(eq_offset) = trimmed_line.find('=') {
169 let key = trimmed_line[..eq_offset].trim().to_string();
170 let value_part = trimmed_line[eq_offset + 1..].trim().to_string();
171 existing_items_map.insert(key, value_part);
172 }
173 }
174 }
175 section_start_idx.map_or(lines.len(), |idx| idx + 1)
176}
177
178
179pub(crate) fn update_cargo_section_lines(
181 cargo_lines: &mut Vec<String>,
182 new_items_config: &DataObject,
183 section_items_map: &mut HashMap<String, String>,
184 mut current_insertion_idx: usize,
185 item_type_name: &str,
186 lib_name: &str,
187) -> (bool, usize) {
188 let mut section_modified = false;
189
190 if current_insertion_idx > cargo_lines.len() {
191 current_insertion_idx = cargo_lines.len();
192 }
193
194 for (key, value_obj) in new_items_config.objects() {
195 let value_from_meta = value_obj.string();
196
197 let new_line_content = if value_from_meta.trim().starts_with('{') {
198 format!("{} = {}", key, value_from_meta)
199 } else {
200 let normalized_version = value_from_meta.trim().trim_matches('"');
201 format!("{} = \"{}\"", key, normalized_version)
202 };
203
204 if let Some(existing_line_value) = section_items_map.get(&key) {
205 if existing_line_value.contains("path") {
206 continue;
207 }
208
209 let normalized_existing = existing_line_value.trim().trim_matches('"');
210 let normalized_meta = value_from_meta.trim().trim_matches('"');
211
212 if normalized_existing != normalized_meta {
213 println!(
214 "WARNING: {} '{}' in Cargo.toml for library \"{}\" (current value: {}) does not match config value ({}). Updating.",
215 item_type_name, key, lib_name, existing_line_value, value_from_meta
216 );
217
218 let mut updated = false;
219 for line_idx in (0..cargo_lines.len()).rev() {
220 if cargo_lines[line_idx].trim().starts_with(&format!("{} =", key)) {
221 cargo_lines[line_idx] = new_line_content.clone();
222 section_modified = true;
223 updated = true;
224 break;
225 }
226 }
227 if !updated {
228 println!("WARNING: Could not find existing {} line for '{}' in Cargo.toml to update.", item_type_name, key);
229 }
230 }
231
232 } else {
233 println!("Adding new {} to Cargo.toml for library \"{}\": {}", item_type_name, lib_name, new_line_content);
234 cargo_lines.insert(current_insertion_idx, new_line_content.clone());
235 section_modified = true;
236 current_insertion_idx += 1;
237 }
238 }
239 (section_modified, current_insertion_idx)
240}