anza_xtask/commands/
update_crate.rs1use {
2 crate::utils,
3 anyhow::Result,
4 clap::Args,
5 log::{debug, info},
6 std::{fs, path::PathBuf},
7 toml_edit::{value, DocumentMut, Item},
8};
9
10#[derive(Args)]
11pub struct CommandArgs {
12 #[arg(long, default_value = ".")]
13 pub root_path: PathBuf,
14 #[arg(long, short, required = true)]
15 pub package: String,
16 #[arg(long, required = true)]
17 pub from: String,
18 #[arg(long, required = true)]
19 pub to: String,
20 #[arg(long, default_value = "[]", value_delimiter = ',')]
21 pub exclude_paths: Vec<PathBuf>,
22}
23
24pub fn run(args: CommandArgs) -> Result<()> {
25 let all_cargo_tomls = utils::recursive_find_files(&args.root_path, "Cargo.toml", |_| true)?;
26 let dependency_targets: &[(&[&str], &str)] = &[
27 (&["workspace", "dependencies"], "workspace.dependencies"),
28 (&["dependencies"], "dependencies"),
29 (&["dev-dependencies"], "dev-dependencies"),
30 ];
31
32 'MAIN_LOOP: for cargo_toml in all_cargo_tomls {
33 info!("[{}]", cargo_toml.display());
34
35 for exclude_path in &args.exclude_paths {
36 if cargo_toml
37 .to_string_lossy()
38 .contains(exclude_path.to_string_lossy().as_ref())
39 {
40 info!(" ⏩ skipped (exclude path)");
41 continue 'MAIN_LOOP;
42 }
43 }
44
45 let content = fs::read_to_string(&cargo_toml)?;
46 let mut doc = content.parse::<DocumentMut>()?;
47 let mut need_to_write = false;
48
49 for (path, label) in dependency_targets {
50 if update_dependency_at(&mut doc, path, &args.package, &args.from, &args.to) {
51 need_to_write = true;
52 info!(" ✅ updated {label}");
53 }
54 }
55
56 if need_to_write {
57 fs::write(&cargo_toml, doc.to_string())?;
58 } else {
59 info!(" ⏩ skipped (no changes)");
60 }
61 }
62 Ok(())
63}
64
65fn update_dependency_at(
66 doc: &mut DocumentMut,
67 path: &[&str],
68 package: &str,
69 from: &str,
70 to: &str,
71) -> bool {
72 let mut table = doc.as_table_mut();
73
74 for key in path {
75 let Some(next_table) = table.get_mut(key).and_then(Item::as_table_mut) else {
76 return false;
77 };
78 table = next_table;
79 }
80
81 table
82 .get_mut(package)
83 .is_some_and(|dep_spec| update_dependency_spec(dep_spec, from, to))
84}
85
86fn update_dependency_spec(dep_spec: &mut Item, from: &str, to: &str) -> bool {
87 debug!("dep_spec: {dep_spec:?}");
88 if let Some(current_version) = dep_spec.as_str() {
89 if current_version == from || current_version == format!("={from}") {
90 let new_version = current_version.replace(from, to);
91 *dep_spec = value(&new_version);
92 return true;
93 }
94 } else if let Some(current_version) = dep_spec
95 .as_inline_table()
96 .and_then(|table| table.get("version").and_then(|version| version.as_str()))
97 {
98 if current_version == from || current_version == format!("={from}") {
99 let new_version = current_version.replace(from, to);
100 dep_spec["version"] = value(&new_version);
101 return true;
102 }
103 }
104
105 false
106}
107
108#[cfg(test)]
109mod tests {
110 use {super::*, toml_edit::Table};
111
112 #[test]
113 fn test_update_dependency_spec_string() {
114 let mut dep_spec = value("1.2.3");
116 assert!(update_dependency_spec(&mut dep_spec, "1.2.3", "1.2.4"));
117 assert_eq!(dep_spec.as_str(), Some("1.2.4"));
118
119 let mut dep_spec = value("1.2.3");
121 assert!(!update_dependency_spec(&mut dep_spec, "1.2.4", "1.2.5"));
122 assert_eq!(dep_spec.as_str(), Some("1.2.3"));
123
124 let mut dep_spec = value("=1.2.3");
126 assert!(update_dependency_spec(&mut dep_spec, "1.2.3", "1.2.4"));
127 assert_eq!(dep_spec.as_str(), Some("=1.2.4"));
128 }
129
130 #[test]
131 fn test_update_dependency_spec_table() {
132 let mut table = Table::default();
134 table["version"] = value("1.2.3");
135 let mut dep_spec: Item = value(table.into_inline_table());
136 assert!(update_dependency_spec(&mut dep_spec, "1.2.3", "1.2.4"));
137 assert_eq!(dep_spec["version"].as_str(), Some("1.2.4"));
138
139 let mut table = Table::default();
141 table["version"] = value("1.2.3");
142 let mut dep_spec = table.into();
143 assert!(!update_dependency_spec(&mut dep_spec, "1.2.4", "1.2.5"));
144 assert_eq!(dep_spec["version"].as_str(), Some("1.2.3"));
145
146 let mut table = Table::default();
148 table["version"] = value("=1.2.3".to_string());
149 let mut dep_spec: Item = value(table.into_inline_table());
150 assert!(update_dependency_spec(&mut dep_spec, "1.2.3", "1.2.4"));
151 assert_eq!(dep_spec["version"].as_str(), Some("=1.2.4"));
152 }
153}