From 1fe2becfd105d3ab97a89beb08db4aa06a3b636a Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Mon, 15 Jul 2024 22:38:47 +1200 Subject: [PATCH 1/6] seperate manifest module --- tools/check_crate_updates/src/main.rs | 53 ++++++++------- tools/check_crate_updates/src/manifest.rs | 81 +++++++++++++++++++++++ 2 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 tools/check_crate_updates/src/manifest.rs diff --git a/tools/check_crate_updates/src/main.rs b/tools/check_crate_updates/src/main.rs index fb2b7e36..98557e8d 100644 --- a/tools/check_crate_updates/src/main.rs +++ b/tools/check_crate_updates/src/main.rs @@ -1,9 +1,16 @@ +//! This tool loops over each manifest for the images and for each Bevy crate, checks the support +//! table in its readme to see if it can be updated, if so it updates the manifest. + use anyhow::anyhow; use cached::proc_macro::cached; -use std::{env, fs}; +use std::{env, fs, str::FromStr}; use table_extract::Table; use ureq::{Agent, AgentBuilder}; +use crate::manifest::Manifest; + +mod manifest; + const EXCLUDE_CRATES: &[&str] = &["bevy", "rand", "rand_chacha", "wasm-bindgen"]; fn main() -> anyhow::Result<()> { @@ -26,29 +33,23 @@ fn main() -> anyhow::Result<()> { for path in manifest_paths { let manifest_str = fs::read_to_string(&path)?; - let mut manifest = manifest_str - .parse::() + let mut manifest = Manifest::from_str(&manifest_str) .map_err(|e| anyhow!("Failed to parse manifest at {path:?}\n{e}"))?; - // skip if no version - using main branch - if !manifest["dependencies"]["bevy"] - .as_inline_table() - .unwrap() - .contains_key("version") - { - println!("Skipping {path:?}"); + let Some(bevy) = manifest.get_dependency("bevy") else { + eprintln!("Skipping {path:?}, manifest does not contain Bevy"); continue; - } + }; - let bevy_version = manifest["dependencies"]["bevy"]["version"] - .as_str() - .unwrap(); + let Some(bevy_version) = bevy.get_version() else { + // Most likely on main branch + eprintln!("Skipping {path:?}, invalid Bevy version"); + continue; + }; - let crates = manifest["dependencies"] - .as_table() - .unwrap() - .iter() - .map(|(name, _)| name) + let crates = manifest + .get_dependency_names() + .unwrap() // we know bevy exists so it can't be empty .filter(|name| !EXCLUDE_CRATES.contains(name)) .map(|name| fetch_crate(name, agent.clone())) .inspect(|res| { @@ -124,10 +125,12 @@ fn main() -> anyhow::Result<()> { } for (name, version) in newest_versions { - if let Some(table) = manifest["dependencies"][&name].as_inline_table_mut() { - table["version"] = version.into(); - } else { - manifest["dependencies"][name] = toml_edit::value(version); + if !manifest + .get_dependency_mut(&name) + .unwrap() // name is a result from dep list so it must exist + .set_version(&version) + { + eprintln!("Failed to set value of {name} tp {version}"); } } @@ -145,7 +148,7 @@ fn main() -> anyhow::Result<()> { result = true, ty = "cached::SizedCache", create = "{ cached::SizedCache::with_size(20) }", - convert = r#"{ name.to_string() }"# + convert = r#"{ name.to_owned() }"# )] fn fetch_crate(name: &str, agent: Agent) -> anyhow::Result { agent @@ -160,7 +163,7 @@ fn fetch_crate(name: &str, agent: Agent) -> anyhow::Result { result = true, ty = "cached::SizedCache", create = "{ cached::SizedCache::with_size(20) }", - convert = r#"{ c.data.name.to_string() }"# + convert = r#"{ c.data.name.clone() }"# )] fn fetch_readme(c: &CrateResponse, agent: Agent) -> anyhow::Result { let path = &c.versions[0].readme_path; // index 0 is latest diff --git a/tools/check_crate_updates/src/manifest.rs b/tools/check_crate_updates/src/manifest.rs new file mode 100644 index 00000000..df017d64 --- /dev/null +++ b/tools/check_crate_updates/src/manifest.rs @@ -0,0 +1,81 @@ +use std::{fmt::Display, str::FromStr}; + +pub struct Manifest(toml_edit::DocumentMut); + +impl Manifest { + /// Gets the dependency from the manifest if it exists + pub fn get_dependency(&self, name: &str) -> Option { + self.0 + .get("dependencies") + .and_then(|dep| dep.get(name)) + .map(|dep| Dependency(dep)) + } + + /// Gets the dependency mutably from the manifest if it exists + pub fn get_dependency_mut(&mut self, name: &str) -> Option { + self.0 + .get_mut("dependencies") + .and_then(|dep| dep.get_mut(name)) + .map(|dep| DependencyMut(dep)) + } + + /// Gets the names of all the dependencies from the manifest + pub fn get_dependency_names<'a>(&'a self) -> Option> { + self.0 + .get("dependencies") + .and_then(|item| item.as_table()) + .map(|table| table.iter().map(|(name, _)| name)) + } +} + +pub struct Dependency<'a>(&'a toml_edit::Item); +pub struct DependencyMut<'a>(&'a mut toml_edit::Item); + +impl<'a> Dependency<'a> { + /// Gets the version of the dependency by searching first + /// the value of the entry and then as a inline table + pub fn get_version(&self) -> Option<&'a str> { + if let Some(version) = self.0.get("version").and_then(|i| i.as_str()) { + Some(version) + } else if let Some(table) = self.0.as_inline_table() { + table.get("version").and_then(|i| i.as_str()) + } else { + None + } + } +} + +impl<'a> DependencyMut<'a> { + /// Sets the version of the dependency by searching first + /// the value of the entry and then as a inline table. + /// Returns whether it was successful + pub fn set_version(&mut self, version: &str) -> bool { + if let Some(value) = self.0.get_mut("version").and_then(|i| i.as_value_mut()) { + *value = version.into(); + true + } else if let Some(value) = self + .0 + .as_inline_table_mut() + .and_then(|table| table.get_mut("version")) + { + *value = version.into(); + true + } else { + false + } + } +} + +impl FromStr for Manifest { + type Err = toml_edit::TomlError; + + fn from_str(s: &str) -> Result { + s.parse::().map(|doc| Manifest(doc)) + } +} + +impl Display for Manifest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0.to_string()) + } +} From f7d7d6a19fa087a36dccdb9b94d95b801f1cd193 Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Mon, 15 Jul 2024 22:41:33 +1200 Subject: [PATCH 2/6] ty clippy --- tools/check_crate_updates/src/manifest.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/check_crate_updates/src/manifest.rs b/tools/check_crate_updates/src/manifest.rs index df017d64..8454ddd0 100644 --- a/tools/check_crate_updates/src/manifest.rs +++ b/tools/check_crate_updates/src/manifest.rs @@ -8,7 +8,7 @@ impl Manifest { self.0 .get("dependencies") .and_then(|dep| dep.get(name)) - .map(|dep| Dependency(dep)) + .map(Dependency) } /// Gets the dependency mutably from the manifest if it exists @@ -16,11 +16,11 @@ impl Manifest { self.0 .get_mut("dependencies") .and_then(|dep| dep.get_mut(name)) - .map(|dep| DependencyMut(dep)) + .map(DependencyMut) } /// Gets the names of all the dependencies from the manifest - pub fn get_dependency_names<'a>(&'a self) -> Option> { + pub fn get_dependency_names(&self) -> Option> { self.0 .get("dependencies") .and_then(|item| item.as_table()) @@ -70,7 +70,7 @@ impl FromStr for Manifest { type Err = toml_edit::TomlError; fn from_str(s: &str) -> Result { - s.parse::().map(|doc| Manifest(doc)) + s.parse::().map(Manifest) } } From c40a79cb69a1d3e98fc4427c85ca07a663d00ec9 Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Mon, 15 Jul 2024 23:14:29 +1200 Subject: [PATCH 3/6] update main.rs --- tools/check_crate_updates/src/main.rs | 196 +++++++++++++------------- 1 file changed, 95 insertions(+), 101 deletions(-) diff --git a/tools/check_crate_updates/src/main.rs b/tools/check_crate_updates/src/main.rs index 98557e8d..4e818f81 100644 --- a/tools/check_crate_updates/src/main.rs +++ b/tools/check_crate_updates/src/main.rs @@ -1,14 +1,13 @@ //! This tool loops over each manifest for the images and for each Bevy crate, checks the support //! table in its readme to see if it can be updated, if so it updates the manifest. -use anyhow::anyhow; +use crate::manifest::Manifest; +use anyhow::{anyhow, Context}; use cached::proc_macro::cached; -use std::{env, fs, str::FromStr}; +use std::{env, fs, path, str::FromStr}; use table_extract::Table; use ureq::{Agent, AgentBuilder}; -use crate::manifest::Manifest; - mod manifest; const EXCLUDE_CRATES: &[&str] = &["bevy", "rand", "rand_chacha", "wasm-bindgen"]; @@ -32,116 +31,111 @@ fn main() -> anyhow::Result<()> { .filter(|path| path.to_string_lossy().ends_with(".Cargo.toml")); for path in manifest_paths { - let manifest_str = fs::read_to_string(&path)?; - let mut manifest = Manifest::from_str(&manifest_str) - .map_err(|e| anyhow!("Failed to parse manifest at {path:?}\n{e}"))?; - - let Some(bevy) = manifest.get_dependency("bevy") else { - eprintln!("Skipping {path:?}, manifest does not contain Bevy"); - continue; + if let Err(e) = handle_manifest(&path, agent.clone()) { + eprintln!("[ERROR] Error handling {path:?}: {e}"); }; + } - let Some(bevy_version) = bevy.get_version() else { - // Most likely on main branch - eprintln!("Skipping {path:?}, invalid Bevy version"); - continue; - }; + println!("Complete"); - let crates = manifest - .get_dependency_names() - .unwrap() // we know bevy exists so it can't be empty - .filter(|name| !EXCLUDE_CRATES.contains(name)) - .map(|name| fetch_crate(name, agent.clone())) - .inspect(|res| { - if let Err(e) = res { - eprintln!("Error getting crate: {e}"); - } - }) - .filter_map(|res| res.ok()); - - let mut newest_versions = Vec::new(); - - println!("Bevy: {bevy_version}"); - for c in crates { - let readme = match fetch_readme(&c, agent.clone()) { - Ok(r) => r, - Err(e) => { - eprintln!("Error getting readme: {e}"); - continue; - } - }; - - let table = match find_support_table(&readme) { - Ok(t) => t, - Err(e) => { - eprintln!("{e}"); - continue; - } - }; - - // currently assuming the bevy column is first - let mut matching = Vec::new(); - for row in table.iter().map(|r| r.as_slice()) { - let bevy = extract_version_from_cell(&row[0]); - let others = extract_versions_from_cell(&row[1]); - for other in others { - if bevy.starts_with(bevy_version) { - matching.push((bevy.clone(), other)); - } - } - } + Ok(()) +} - if matching.is_empty() { - eprintln!("{} has no matches for {bevy_version}", c.data.name); - continue; +fn handle_manifest(path: &path::PathBuf, agent: Agent) -> anyhow::Result<()> { + let manifest_str = fs::read_to_string(path)?; + let mut manifest = Manifest::from_str(&manifest_str) + .map_err(|e| anyhow!("Failed to parse manifest at {path:?}\n{e}"))?; + + let bevy_version = manifest + .get_dependency("bevy") + .ok_or_else(|| anyhow!("Manifest does not contain Bevy"))? + .get_version() + .ok_or_else(|| anyhow!("Invalid Bevy version"))? + .to_owned(); + + let newest_versions = manifest + .get_dependency_names() + .unwrap() // we know bevy exists so it can't be empty + .filter(|name| !EXCLUDE_CRATES.contains(name)) + .map(|name| fetch_crate(name, agent.clone())) + .inspect(|res| { + if let Err(e) = res { + eprintln!("Error getting crate: {e}"); } - - let newest = matching - .iter() - .map(|(_, other)| other.parse::()) - .inspect(|res| { - if let Err(e) = res { - eprintln!("Failed to parse: {e}"); - } - }) - .filter_map(Result::ok) - .map(|semver| { - c.versions - .iter() - .map(|v| v.version.parse::().unwrap()) - .filter(|v| semver.matches(v)) - .max() - .unwrap() - }) - .max() - .unwrap(); - - println!( - "The most recent version for {} compatible with Bevy {bevy_version} is {newest}", - c.data.name - ); - - newest_versions.push((c.data.name, format!("={newest}"))); + }) + .filter_map(|res| res.ok()) + .map(|c| { + ( + c.data.name.clone(), + get_newest_version(c, &bevy_version, agent.clone()), + ) + }) + .filter_map(|(name, version)| version.map(|v| (name, v)).ok()) + .collect::>(); + + for (name, version) in newest_versions { + if !manifest + .get_dependency_mut(&name) + .unwrap() // name is a result from dep list so it must exist + .set_version(&version) + { + eprintln!("[WARNING] Failed to set value of {name} to {version}"); } + } - for (name, version) in newest_versions { - if !manifest - .get_dependency_mut(&name) - .unwrap() // name is a result from dep list so it must exist - .set_version(&version) - { - eprintln!("Failed to set value of {name} tp {version}"); + Ok(()) +} + +fn get_newest_version( + c: CrateResponse, + bevy_version: &str, + agent: Agent, +) -> anyhow::Result { + let readme = fetch_readme(&c, agent.clone()).with_context(|| "Failed to get readme")?; + let table = find_support_table(&readme).with_context(|| "Failed to find support table")?; + + // currently assuming the bevy column is first + let mut matching = Vec::new(); + for row in table.iter().map(|r| r.as_slice()) { + let bevy = extract_version_from_cell(&row[0]); + let others = extract_versions_from_cell(&row[1]); + for other in others { + if bevy.starts_with(bevy_version) { + matching.push((bevy.clone(), other)); } } + } - if let Err(e) = fs::write(&path, manifest.to_string()) { - eprintln!("Failed to write to {path:?}: {e}"); - } + if matching.is_empty() { + return Err(anyhow!("{} has no matches for {bevy_version}", c.data.name)); } - println!("Complete"); + let newest = matching + .iter() + .map(|(_, other)| other.parse::()) + .inspect(|res| { + if let Err(e) = res { + eprintln!("[WARNING] Failed to parse: {e}"); + } + }) + .filter_map(Result::ok) + .map(|semver| { + c.versions + .iter() + .map(|v| v.version.parse::().unwrap()) + .filter(|v| semver.matches(v)) + .max() + .unwrap() + }) + .max() + .unwrap(); - Ok(()) + println!( + "[INFO] The most recent version for {} compatible with Bevy {bevy_version} is {newest}", + c.data.name + ); + + Ok(format!("={newest}")) } #[cached( From 1f6ec0d68edea0345d62243ae1750d0daedd8c53 Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Mon, 15 Jul 2024 23:27:15 +1200 Subject: [PATCH 4/6] might help if i write the file to disk --- tools/check_crate_updates/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check_crate_updates/src/main.rs b/tools/check_crate_updates/src/main.rs index 4e818f81..d25d0e41 100644 --- a/tools/check_crate_updates/src/main.rs +++ b/tools/check_crate_updates/src/main.rs @@ -83,7 +83,7 @@ fn handle_manifest(path: &path::PathBuf, agent: Agent) -> anyhow::Result<()> { } } - Ok(()) + fs::write(&path, manifest.to_string()).with_context(|| "Failed to write manifest to disk") } fn get_newest_version( From 6aff7c5dc2ff2c32d3cd1ecc391527b6285e408e Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Mon, 15 Jul 2024 23:27:33 +1200 Subject: [PATCH 5/6] fix order --- tools/check_crate_updates/src/manifest.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/check_crate_updates/src/manifest.rs b/tools/check_crate_updates/src/manifest.rs index 8454ddd0..4875db54 100644 --- a/tools/check_crate_updates/src/manifest.rs +++ b/tools/check_crate_updates/src/manifest.rs @@ -50,16 +50,16 @@ impl<'a> DependencyMut<'a> { /// the value of the entry and then as a inline table. /// Returns whether it was successful pub fn set_version(&mut self, version: &str) -> bool { - if let Some(value) = self.0.get_mut("version").and_then(|i| i.as_value_mut()) { - *value = version.into(); - true - } else if let Some(value) = self + if let Some(value) = self .0 .as_inline_table_mut() .and_then(|table| table.get_mut("version")) { *value = version.into(); true + } else if let Some(value) = self.0.as_value_mut() { + *value = version.into(); + true } else { false } From fa5c124583b00c5c1a261eb1a00377e8f9406fe8 Mon Sep 17 00:00:00 2001 From: Liam Gallagher Date: Mon, 15 Jul 2024 23:28:03 +1200 Subject: [PATCH 6/6] clippy --- tools/check_crate_updates/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check_crate_updates/src/main.rs b/tools/check_crate_updates/src/main.rs index d25d0e41..c932eb08 100644 --- a/tools/check_crate_updates/src/main.rs +++ b/tools/check_crate_updates/src/main.rs @@ -83,7 +83,7 @@ fn handle_manifest(path: &path::PathBuf, agent: Agent) -> anyhow::Result<()> { } } - fs::write(&path, manifest.to_string()).with_context(|| "Failed to write manifest to disk") + fs::write(path, manifest.to_string()).with_context(|| "Failed to write manifest to disk") } fn get_newest_version(