multiversx_sc_meta_lib/cargo_toml/
cargo_toml_contents.rs1use std::{
2 fs,
3 io::Write,
4 path::{Path, PathBuf},
5};
6
7use toml::{value::Table, Value};
8
9use crate::contract::sc_config::ContractVariantProfile;
10
11use super::DependencyRawValue;
12
13pub const CARGO_TOML_DEPENDENCIES: &str = "dependencies";
14pub const CARGO_TOML_DEV_DEPENDENCIES: &str = "dev-dependencies";
15pub const PACKAGE: &str = "package";
16pub const AUTHORS: &str = "authors";
17const AUTO_GENERATED: &str = "# Code generated by the multiversx-sc build system. DO NOT EDIT.
18
19# ##########################################
20# ############## AUTO-GENERATED #############
21# ##########################################
22
23";
24
25#[derive(Clone, Debug)]
32pub struct CargoTomlContents {
33 pub path: PathBuf,
34 pub toml_value: toml::Value,
35 pub prepend_auto_generated_comment: bool,
36}
37
38impl CargoTomlContents {
39 pub fn parse_string(raw_str: &str, path: &Path) -> Self {
40 let toml_value = raw_str.parse::<toml::Value>().unwrap_or_else(|e| {
41 panic!(
42 "failed to parse Cargo.toml toml format, path:{}, error: {e}",
43 path.display()
44 )
45 });
46 CargoTomlContents {
47 path: path.to_owned(),
48 toml_value,
49 prepend_auto_generated_comment: false,
50 }
51 }
52
53 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Self {
54 let path_ref = path.as_ref();
55 let cargo_toml_content = fs::read(path_ref).expect("failed to open Cargo.toml file");
56 let cargo_toml_content_str =
57 String::from_utf8(cargo_toml_content).expect("error decoding Cargo.toml utf-8");
58 Self::parse_string(&cargo_toml_content_str, path_ref)
59 }
60
61 pub fn new() -> Self {
62 CargoTomlContents {
63 path: PathBuf::new(),
64 toml_value: toml::Value::Table(Table::new()),
65 prepend_auto_generated_comment: false,
66 }
67 }
68
69 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) {
70 let cargo_toml_content_str = &self.to_string_pretty();
71 let mut file = std::fs::File::create(path).expect("failed to create Cargo.toml file");
72 file.write_all(cargo_toml_content_str.as_bytes())
73 .expect("failed to write Cargo.toml contents to file");
74 }
75
76 pub fn package_name(&self) -> String {
77 self.toml_value
78 .get(PACKAGE)
79 .expect("missing package in Cargo.toml")
80 .get("name")
81 .expect("missing package name in Cargo.toml")
82 .as_str()
83 .expect("package name not a string value")
84 .to_string()
85 }
86
87 pub fn package_edition(&self) -> String {
88 self.toml_value
89 .get(PACKAGE)
90 .expect("missing package in Cargo.toml")
91 .get("edition")
92 .expect("missing package name in Cargo.toml")
93 .as_str()
94 .expect("package name not a string value")
95 .to_string()
96 }
97
98 pub fn dependency_raw_value(&self, crate_name: &str) -> Option<DependencyRawValue> {
100 self.dependency(crate_name)
101 .map(DependencyRawValue::parse_toml_value)
102 }
103
104 pub fn insert_dependency_raw_value(&mut self, crate_name: &str, raw_value: DependencyRawValue) {
105 self.dependencies_mut()
106 .insert(crate_name.to_owned(), raw_value.into_toml_value());
107 }
108
109 pub fn change_package_name(&mut self, new_package_name: String) {
111 let package = self
112 .toml_value
113 .get_mut("package")
114 .expect("missing package in Cargo.toml");
115 package
116 .as_table_mut()
117 .expect("malformed package in Cargo.toml")
118 .insert("name".to_string(), toml::Value::String(new_package_name));
119 }
120
121 pub fn dependencies_table(&self) -> Option<&Table> {
122 if let Some(deps) = self.toml_value.get(CARGO_TOML_DEPENDENCIES) {
123 deps.as_table()
124 } else if let Some(deps) = self.toml_value.get(CARGO_TOML_DEV_DEPENDENCIES) {
125 deps.as_table()
126 } else {
127 None
128 }
129 }
130
131 pub fn dependency(&self, dep_name: &str) -> Option<&Value> {
132 if let Some(deps_map) = self.dependencies_table() {
133 deps_map.get(dep_name)
134 } else {
135 None
136 }
137 }
138
139 pub fn has_dependencies(&self) -> bool {
140 self.toml_value.get(CARGO_TOML_DEPENDENCIES).is_some()
141 }
142
143 pub fn dependencies_mut(&mut self) -> &mut Table {
144 self.toml_value
145 .as_table_mut()
146 .expect("add deps cargo toml error wasm adapter")
147 .entry(CARGO_TOML_DEPENDENCIES)
148 .or_insert(toml::Value::Table(toml::map::Map::new()))
149 .as_table_mut()
150 .expect("malformed crate Cargo.toml")
151 }
152
153 pub fn has_dev_dependencies(&self) -> bool {
154 self.toml_value.get(CARGO_TOML_DEV_DEPENDENCIES).is_some()
155 }
156
157 pub fn change_author(&mut self, authors: String) -> bool {
158 let package = self
159 .toml_value
160 .get_mut(PACKAGE)
161 .unwrap_or_else(|| panic!("no dependencies found in crate {}", self.path.display()))
162 .as_table_mut()
163 .expect("missing package in Cargo.toml");
164
165 package.remove(AUTHORS);
166
167 package.insert(
168 AUTHORS.to_owned(),
169 toml::Value::Array(vec![toml::Value::String(authors)]),
170 );
171
172 true
173 }
174
175 pub fn dev_dependencies_mut(&mut self) -> &mut Table {
176 self.toml_value
177 .get_mut(CARGO_TOML_DEV_DEPENDENCIES)
178 .unwrap_or_else(|| panic!("no dependencies found in crate {}", self.path.display()))
179 .as_table_mut()
180 .expect("malformed crate Cargo.toml")
181 }
182
183 pub fn add_crate_type(&mut self, crate_type: &str) {
184 let mut value = toml::map::Map::new();
185 let array = vec![toml::Value::String(crate_type.to_string())];
186 let members = toml::Value::Array(array);
187 value.insert("crate-type".to_string(), members);
188
189 self.toml_value
190 .as_table_mut()
191 .expect("malformed package in Cargo.toml")
192 .insert("lib".to_string(), toml::Value::Table(value));
193 }
194
195 pub fn add_package_info(
196 &mut self,
197 name: &String,
198 version: String,
199 current_edition: String,
200 publish: bool,
201 ) {
202 let mut value = toml::map::Map::new();
203 value.insert("name".to_string(), Value::String(name.to_string()));
204 value.insert("version".to_string(), Value::String(version));
205 value.insert("edition".to_string(), Value::String(current_edition));
206 value.insert("publish".to_string(), Value::Boolean(publish));
207
208 self.toml_value
209 .as_table_mut()
210 .expect("malformed package in Cargo.toml / add_package")
211 .insert("package".to_string(), toml::Value::Table(value));
212 }
213
214 pub fn add_contract_variant_profile(&mut self, contract_profile: &ContractVariantProfile) {
215 let mut profile_props = toml::map::Map::new();
216 profile_props.insert(
217 "codegen-units".to_string(),
218 Value::Integer(contract_profile.codegen_units.into()),
219 );
220 profile_props.insert(
221 "opt-level".to_string(),
222 Value::String(contract_profile.opt_level.to_owned()),
223 );
224 profile_props.insert("lto".to_string(), Value::Boolean(contract_profile.lto));
225 profile_props.insert("debug".to_string(), Value::Boolean(contract_profile.debug));
226 profile_props.insert(
227 "panic".to_string(),
228 Value::String(contract_profile.panic.to_owned()),
229 );
230 profile_props.insert(
231 "overflow-checks".to_string(),
232 Value::Boolean(contract_profile.overflow_checks),
233 );
234
235 let mut toml_table = toml::map::Map::new();
237 toml_table.insert("release".to_string(), toml::Value::Table(profile_props));
238
239 let mut dev_value = toml::map::Map::new();
241 dev_value.insert("panic".to_string(), Value::String("abort".to_string()));
242 toml_table.insert("dev".to_string(), toml::Value::Table(dev_value));
243
244 self.toml_value
245 .as_table_mut()
246 .expect("malformed package in Cargo.toml")
247 .insert("profile".to_string(), toml::Value::Table(toml_table));
248 }
249
250 pub fn add_workspace(&mut self, members: &[&str]) {
251 let array: Vec<toml::Value> = members
252 .iter()
253 .map(|s| toml::Value::String(s.to_string()))
254 .collect();
255 let members_toml = toml::Value::Array(array);
256
257 let mut workspace = toml::Value::Table(toml::map::Map::new());
258 workspace
259 .as_table_mut()
260 .expect("malformed package in Cargo.toml")
261 .insert("members".to_string(), members_toml);
262
263 self.toml_value
264 .as_table_mut()
265 .expect("malformed package in Cargo.toml")
266 .insert("workspace".to_string(), workspace);
267 }
268
269 pub fn local_dependency_paths(&self, ignore_deps: &[&str]) -> Vec<String> {
270 let mut result = Vec::new();
271 if let Some(deps_map) = self.dependencies_table() {
272 for (key, value) in deps_map {
273 if ignore_deps.contains(&key.as_str()) {
274 continue;
275 }
276
277 if let Some(path) = value.get("path") {
278 result.push(path.as_str().expect("path is not a string").to_string());
279 }
280 }
281 }
282 result
283 }
284
285 pub fn change_features_for_parent_crate_dep(
286 &mut self,
287 features: &[String],
288 default_features: Option<bool>,
289 ) {
290 let deps_mut = self.dependencies_mut();
291 for (_, dep) in deps_mut {
292 if is_dep_path_above(dep) {
293 let feature_values = features
294 .iter()
295 .map(|feature| Value::String(feature.clone()))
296 .collect();
297 let deps_table = dep.as_table_mut().expect("malformed crate Cargo.toml");
298 deps_table.insert("features".to_string(), Value::Array(feature_values));
299 if let Some(default_features_value) = default_features {
300 deps_table.insert(
301 "default-features".to_string(),
302 Value::Boolean(default_features_value),
303 );
304 }
305 }
306 }
307 }
308
309 pub fn to_string_pretty(&self) -> String {
310 let toml_string =
311 toml::to_string_pretty(&self.toml_value).expect("failed to format Cargo.toml contents");
312 if self.prepend_auto_generated_comment {
313 return format!("{}{}", AUTO_GENERATED, toml_string);
314 }
315
316 toml_string
317 }
318}
319
320fn is_dep_path_above(dep: &Value) -> bool {
322 if let Some(path) = dep.get("path") {
323 if let Some(s) = path.as_str() {
324 return s == "..";
325 }
326 }
327
328 false
329}
330
331pub fn change_from_base_to_adapter_path(base_path: &Path) -> PathBuf {
332 let path = base_path.to_string_lossy().replace("base", "wasm-adapter");
333
334 Path::new("..").join(path)
335}
336
337#[allow(unused)]
339fn remove_quotes(var: &Value) -> String {
340 var.to_string().replace('\"', "")
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use crate::cargo_toml::{DependencyReference, GitCommitReference, VersionReq};
347
348 #[test]
349 fn test_change_from_base_to_adapter_path() {
350 let base_path = Path::new("..")
351 .join("..")
352 .join("..")
353 .join("framework")
354 .join("base");
355 let adapter_path = Path::new("..")
356 .join("..")
357 .join("..")
358 .join("..")
359 .join("framework")
360 .join("wasm-adapter");
361
362 assert_eq!(
363 super::change_from_base_to_adapter_path(&base_path),
364 adapter_path
365 );
366 }
367
368 const CARGO_TOML_RAW: &str = r#"
369[dependencies.by-version-1]
370version = "0.54.0"
371
372[dependencies.by-version-1-strict]
373version = "=0.54.1"
374
375[dependencies.by-git-commit-1]
376git = "https://github.com/multiversx/repo1"
377rev = "85c31b9ce730bd5ffe41589c353d935a14baaa96"
378
379[dependencies.by-path-1]
380path = "a/b/c"
381
382[dependencies]
383by-version-2 = "0.54.2"
384by-version-2-strict = "=0.54.3"
385by-path-2 = { path = "d/e/f" }
386by-git-commit-2 = { git = "https://github.com/multiversx/repo2", rev = "e990be823f26d1e7f59c71536d337b7240dc3fa2" }
387 "#;
388
389 #[test]
390 fn test_dependency_value() {
391 let cargo_toml = CargoTomlContents::parse_string(CARGO_TOML_RAW, Path::new("test"));
392
393 let raw_value = cargo_toml.dependency_raw_value("by-version-1").unwrap();
395 assert_eq!(
396 raw_value,
397 DependencyRawValue {
398 version: Some("0.54.0".to_owned()),
399 ..Default::default()
400 },
401 );
402 assert_eq!(
403 raw_value.interpret(),
404 DependencyReference::Version(VersionReq::from_version_str("0.54.0").unwrap()),
405 );
406
407 let raw_value = cargo_toml
409 .dependency_raw_value("by-version-1-strict")
410 .unwrap();
411 assert_eq!(
412 raw_value,
413 DependencyRawValue {
414 version: Some("=0.54.1".to_owned()),
415 ..Default::default()
416 },
417 );
418 assert_eq!(
419 raw_value.interpret(),
420 DependencyReference::Version(VersionReq::from_version_str("0.54.1").unwrap().strict()),
421 );
422
423 let raw_value = cargo_toml.dependency_raw_value("by-version-2").unwrap();
425 assert_eq!(
426 raw_value,
427 DependencyRawValue {
428 version: Some("0.54.2".to_owned()),
429 ..Default::default()
430 },
431 );
432 assert_eq!(
433 raw_value.interpret(),
434 DependencyReference::Version(VersionReq::from_version_str("0.54.2").unwrap()),
435 );
436
437 let raw_value = cargo_toml
439 .dependency_raw_value("by-version-2-strict")
440 .unwrap();
441 assert_eq!(
442 raw_value,
443 DependencyRawValue {
444 version: Some("=0.54.3".to_owned()),
445 ..Default::default()
446 },
447 );
448 assert_eq!(
449 raw_value.interpret(),
450 DependencyReference::Version(VersionReq::from_version_str("0.54.3").unwrap().strict()),
451 );
452
453 let raw_value = cargo_toml.dependency_raw_value("by-git-commit-1").unwrap();
455 assert_eq!(
456 raw_value,
457 DependencyRawValue {
458 git: Some("https://github.com/multiversx/repo1".to_owned()),
459 rev: Some("85c31b9ce730bd5ffe41589c353d935a14baaa96".to_owned()),
460 ..Default::default()
461 },
462 );
463 assert_eq!(
464 raw_value.interpret(),
465 DependencyReference::GitCommit(GitCommitReference {
466 git: "https://github.com/multiversx/repo1".to_owned(),
467 rev: "85c31b9ce730bd5ffe41589c353d935a14baaa96".to_owned(),
468 })
469 );
470
471 let raw_value = cargo_toml.dependency_raw_value("by-git-commit-2").unwrap();
473 assert_eq!(
474 raw_value,
475 DependencyRawValue {
476 git: Some("https://github.com/multiversx/repo2".to_owned()),
477 rev: Some("e990be823f26d1e7f59c71536d337b7240dc3fa2".to_owned()),
478 ..Default::default()
479 },
480 );
481 assert_eq!(
482 raw_value.interpret(),
483 DependencyReference::GitCommit(GitCommitReference {
484 git: "https://github.com/multiversx/repo2".to_owned(),
485 rev: "e990be823f26d1e7f59c71536d337b7240dc3fa2".to_owned(),
486 })
487 );
488
489 let raw_value = cargo_toml.dependency_raw_value("by-path-1").unwrap();
491 let path = Path::new("a").join("b").join("c");
492 assert_eq!(
493 raw_value,
494 DependencyRawValue {
495 path: Some(path.clone()),
496 ..Default::default()
497 },
498 );
499 assert_eq!(raw_value.interpret(), DependencyReference::Path(path),);
500
501 let raw_value = cargo_toml.dependency_raw_value("by-path-2").unwrap();
503 let path = Path::new("d").join("e").join("f");
504 assert_eq!(
505 raw_value,
506 DependencyRawValue {
507 path: Some(path.clone()),
508 ..Default::default()
509 },
510 );
511 assert_eq!(raw_value.interpret(), DependencyReference::Path(path),);
512 }
513}