diff options
Diffstat (limited to 'development/tools/cargo-workspace2/src/models')
5 files changed, 392 insertions, 0 deletions
diff --git a/development/tools/cargo-workspace2/src/models/_crate.rs b/development/tools/cargo-workspace2/src/models/_crate.rs new file mode 100644 index 000000000000..68d2baad2bad --- /dev/null +++ b/development/tools/cargo-workspace2/src/models/_crate.rs @@ -0,0 +1,112 @@ +use crate::models::{CargoCrate, CrateId, DepGraph}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + cmp::{self, Eq, Ord, PartialEq, PartialOrd}, + collections::BTreeSet, + path::PathBuf, +}; + +static ID_CTR: AtomicUsize = AtomicUsize::new(0); + +/// A crate in a cargo workspace +/// +/// Has a name, path (stored as the offset of the root), and set of +/// dependencies inside the workspace. To get the dependents of this +/// crate, query the dependency graph with the set of other crate IDs. +#[derive(Clone, Debug)] +pub struct Crate { + /// Numeric Id of this crate + pub id: CrateId, + /// Package name, not the folder name + pub name: String, + /// Path offset of the workspace root + pub cc: CargoCrate, + /// List of dependencies this crate has inside this workspace + pub dependencies: BTreeSet<CrateId>, +} + +impl PartialEq for Crate { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Crate {} + +impl Ord for Crate { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.id.cmp(&other.id) + } +} + +impl PartialOrd for Crate { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} + +/// Increment the monotonicly increasing Id +fn incr_id() -> usize { + ID_CTR.fetch_add(1, Ordering::Relaxed) +} + +impl Crate { + pub fn new(cc: CargoCrate) -> Self { + Self { + id: incr_id(), + name: cc.name(), + cc, + dependencies: BTreeSet::default(), + } + } + + /// Call this function once all crates have been loaded into scope + pub fn process(&mut self, g: &DepGraph) { + let deps: Vec<_> = self + .cc + .dependencies + .iter() + .filter_map(|d| g.find_crate(&d.name)) + .collect(); + + deps.into_iter().for_each(|cid| self.add_dependency(cid)); + } + + /// Get the crate name + pub fn name(&self) -> &String { + &self.name + } + + /// Get the crate path + pub fn path(&self) -> &PathBuf { + &self.cc.path + } + + /// Get the current version + pub fn version(&self) -> String { + self.cc.version() + } + + /// Add a dependency of this crate + pub fn add_dependency(&mut self, id: CrateId) { + self.dependencies.insert(id); + } + + /// Check if this crate has a particular dependency + pub fn has_dependency(&self, id: CrateId) -> bool { + self.dependencies.contains(&id) + } + + pub fn change_dependency(&mut self, dep: &String, new_ver: &String) { + self.cc.change_dep(dep, new_ver); + } + + /// Publish a new version of this crate + pub fn publish(&mut self, new_version: String) { + self.cc.set_version(new_version); + } + + pub fn sync(&mut self) { + self.cc.sync(); + } +} diff --git a/development/tools/cargo-workspace2/src/models/cargo.rs b/development/tools/cargo-workspace2/src/models/cargo.rs new file mode 100644 index 000000000000..b020e82e418d --- /dev/null +++ b/development/tools/cargo-workspace2/src/models/cargo.rs @@ -0,0 +1,132 @@ +use crate::cargo::{self, Dependency, Result}; +use std::{fmt, path::PathBuf}; +use toml_edit::{value, Document, Item, Value}; + +/// Initial representation of a crate, before being parsed +#[derive(Clone)] +pub struct CargoCrate { + pub doc: Document, + pub path: PathBuf, + pub dependencies: Vec<Dependency>, +} + +impl fmt::Debug for CargoCrate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.path.as_path().display()) + } +} + +impl CargoCrate { + /// Get the crate name from the inner document + pub fn name(&self) -> String { + match &self.doc["package"]["name"] { + Item::Value(Value::String(ref name)) => { + name.to_string().replace("\"", "").as_str().trim().into() + } + _ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)), + } + } + + /// Get the current version + pub fn version(&self) -> String { + match &self.doc["package"]["version"] { + Item::Value(Value::String(ref name)) => { + name.to_string().replace("\"", "").as_str().trim().into() + } + _ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)), + } + } + + /// Find a cargo dependency by name + pub fn dep_by_name(&self, name: &String) -> &Dependency { + self.dependencies + .iter() + .find(|c| &c.name == name) + .as_ref() + .unwrap() + } + + pub fn change_dep(&mut self, dep: &String, ver: &String) { + let dep = self + .dep_by_name(dep) + .alias() + .unwrap_or(dep.to_string()) + .clone(); + cargo::update_dependency(&mut self.doc, &dep, ver); + } + + pub fn all_deps_mut(&mut self) -> Vec<&mut Dependency> { + self.dependencies.iter_mut().collect() + } + + /// Check if this crate depends on a specific version of another + pub fn has_version(&self, name: &String) -> bool { + self.dep_by_name(name).has_version() + } + + /// Check if this crate depends on a specific path of another + pub fn has_path(&self, name: &String) -> bool { + self.dep_by_name(name).has_version() + } + + /// Set a new version for this crate + pub fn set_version(&mut self, version: String) { + self.doc["package"]["version"] = value(version); + } + + /// Sync any changes made to the document to disk + pub fn sync(&mut self) { + cargo::sync(&mut self.doc, self.path.join("Cargo.toml")).unwrap(); + } +} + +/// Initial representation of the workspate, before getting parsed +pub struct CargoWorkspace { + pub root: PathBuf, + pub crates: Vec<CargoCrate>, +} + +impl CargoWorkspace { + /// Open a workspace and parse dependency graph + /// + /// Point this to the root of the workspace, do the root + /// `Cargo.toml` file. + pub fn open(p: impl Into<PathBuf>) -> Result<Self> { + let path = p.into(); + + let root_cfg = cargo::parse_root_toml(path.join("Cargo.toml"))?; + let members = cargo::get_members(&root_cfg)?; + + let m_cfg: Vec<_> = members + .into_iter() + .filter_map( + |name| match cargo::parse_toml(path.join(&name).join("Cargo.toml")) { + Ok(doc) => Some(( + PathBuf::new().join(name), + cargo::parse_dependencies(&doc), + doc, + )), + Err(e) => { + eprintln!( + "Error occured while parsing member `{}`/`Cargo.toml`: {:?}", + name, e + ); + None + } + }, + ) + .collect(); + + Ok(Self { + root: path, + crates: m_cfg + .into_iter() + .map(|(path, dependencies, doc)| CargoCrate { + path, + dependencies, + doc, + }) + .collect(), + }) + } +} diff --git a/development/tools/cargo-workspace2/src/models/graph.rs b/development/tools/cargo-workspace2/src/models/graph.rs new file mode 100644 index 000000000000..867c463fb1e3 --- /dev/null +++ b/development/tools/cargo-workspace2/src/models/graph.rs @@ -0,0 +1,78 @@ +use crate::models::{CargoCrate, Crate, CrateId}; +use std::collections::{BTreeMap, BTreeSet}; +use std::path::PathBuf; + +/// Dependency graph in a workspace +pub struct DepGraph { + /// Mapping of crates in the workspace + members: BTreeMap<CrateId, Crate>, + /// Map of crates and the members that depend on them + dependents: BTreeMap<CrateId, BTreeSet<CrateId>>, +} + +impl DepGraph { + /// Create a new, empty dependency graph + pub fn new() -> Self { + Self { + members: Default::default(), + dependents: Default::default(), + } + } + + pub fn add_crate(&mut self, cc: CargoCrate) { + let cc = Crate::new(cc); + self.members.insert(cc.id, cc); + } + + /// Cache the dependents graph for all crates + pub fn finalise(&mut self) { + let mut members = self.members.clone(); + members.iter_mut().for_each(|(_, c)| c.process(&self)); + + members.iter().for_each(|(id, _crate)| { + _crate.dependencies.iter().for_each(|dep_id| { + self.dependents.entry(*dep_id).or_default().insert(*id); + }); + }); + let _ = std::mem::replace(&mut self.members, members); + } + + /// Get a crate by ID + pub fn get_crate(&self, id: CrateId) -> &Crate { + self.members.get(&id).as_ref().unwrap() + } + + /// Get mutable access to a crate by ID + pub fn mut_crate(&mut self, id: CrateId) -> &mut Crate { + self.members.get_mut(&id).unwrap() + } + + /// Find a crate via it's name + pub fn find_crate(&self, name: &String) -> Option<CrateId> { + self.members + .iter() + .find(|(_, c)| c.name() == name) + .map(|(id, _)| *id) + } + + /// Find a crate by it's path-offset in the workspace + pub fn find_crate_by_path(&self, name: &String) -> Option<CrateId> { + self.members + .iter() + .find(|(_, c)| c.path() == &PathBuf::new().join(name)) + .map(|(id, _)| *id) + } + + /// Get a crate's dependents + pub fn get_dependents(&self, id: CrateId) -> Vec<CrateId> { + self.dependents + .get(&id) + .as_ref() + .map(|set| set.iter().cloned().collect()) + .unwrap_or(vec![]) + } + + pub fn get_all(&self) -> Vec<&Crate> { + self.members.iter().map(|(_, c)| c).collect() + } +} diff --git a/development/tools/cargo-workspace2/src/models/mod.rs b/development/tools/cargo-workspace2/src/models/mod.rs new file mode 100644 index 000000000000..759b2703f9f0 --- /dev/null +++ b/development/tools/cargo-workspace2/src/models/mod.rs @@ -0,0 +1,50 @@ +//! Collection of cargo workspace data models. +//! +//! To start parsing types, construct a `CargoWorkspace`, which you +//! can then modify with commands found in [`ops`](../ops/index.html). + +mod cargo; +pub use cargo::{CargoCrate, CargoWorkspace}; + +mod _crate; +pub use _crate::Crate; + +mod publish; +pub use publish::{MutationSet, PubMutation}; + +mod graph; +pub use graph::DepGraph; + +pub type CrateId = usize; + +use crate::{ops::Op, query::Query}; +use std::path::PathBuf; + +/// A fully parsed workspace +pub struct Workspace { + pub root: PathBuf, + dgraph: DepGraph, +} + +impl Workspace { + /// Create a parsed workspace by passing in the stage1 parse data + pub fn process(cws: CargoWorkspace) -> Self { + let CargoWorkspace { root, crates } = cws; + + let mut dgraph = DepGraph::new(); + crates.into_iter().for_each(|cc| dgraph.add_crate(cc)); + dgraph.finalise(); + + Self { root, dgraph } + } + + /// Execute a query on this workspace to find crate IDs + pub fn query(&self, q: Query) -> Vec<CrateId> { + q.execute(&self.dgraph) + } + + /// Execute an operation on a set of crates this in workspace + pub fn execute(&mut self, op: Op, target: Vec<CrateId>) { + op.execute(target, self.root.clone(), &mut self.dgraph) + } +} diff --git a/development/tools/cargo-workspace2/src/models/publish.rs b/development/tools/cargo-workspace2/src/models/publish.rs new file mode 100644 index 000000000000..11984017d49f --- /dev/null +++ b/development/tools/cargo-workspace2/src/models/publish.rs @@ -0,0 +1,20 @@ +use crate::models::{Crate, CrateId}; + +/// A publishing mutation executed on the graph +pub struct PubMutation { + _crate: CrateId, + new_version: String, +} + +impl PubMutation { + /// Createa new motation from a crate a version string + pub fn new(c: &Crate, new_version: String) -> Self { + Self { + _crate: c.id, + new_version, + } + } +} + +/// A collection of mutations performed in a batch +pub struct MutationSet {} |