use std::collections::HashSet;
use std::path::{Path, PathBuf};
use cargo_metadata::{DependencyKind, MetadataCommand};
#[derive(Clone, Debug)]
pub struct Crate {
pub name: String,
pub dir: PathBuf,
pub roots: Vec<PathBuf>,
pub dependencies: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct Workspace {
pub name: String,
pub root: PathBuf,
pub crates: Vec<Crate>,
}
pub fn read(dir: &Path) -> Result<Workspace, String> {
let metadata = MetadataCommand::new()
.current_dir(dir)
.no_deps()
.exec()
.map_err(|error| format!("cargo metadata failed: {error}"))?;
let members: HashSet<_> = metadata.workspace_members.iter().collect();
let mut packages: Vec<_> = metadata
.packages
.iter()
.filter(|package| members.contains(&package.id))
.collect();
packages.sort_by(|a, b| a.name.cmp(&b.name));
let names: HashSet<String> = packages.iter().map(|p| p.name.to_string()).collect();
let crates = packages
.iter()
.map(|package| {
let mut targets: Vec<_> = package
.targets
.iter()
.filter(|target| target.is_lib() || target.is_bin())
.collect();
targets.sort_by_key(|target| !target.is_lib());
let mut dependencies: Vec<String> = package
.dependencies
.iter()
.filter(|dep| dep.kind == DependencyKind::Normal && names.contains(&dep.name))
.map(|dep| dep.name.clone())
.collect();
dependencies.sort();
dependencies.dedup();
Crate {
name: package.name.to_string(),
dir: package
.manifest_path
.parent()
.map(|dir| dir.as_std_path().to_path_buf())
.unwrap_or_default(),
roots: targets
.iter()
.map(|target| target.src_path.as_std_path().to_path_buf())
.collect(),
dependencies,
}
})
.collect();
let root = metadata.workspace_root.as_std_path().to_path_buf();
let name = root
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| "workspace".into());
Ok(Workspace { name, root, crates })
}
#[cfg(test)]
mod tests {
use super::*;
fn this_workspace() -> Workspace {
let dir = Path::new(env!("CARGO_MANIFEST_DIR"));
read(dir).expect("this repository is a workspace")
}
#[test]
fn it_lists_the_members_sorted_by_name() {
let workspace = this_workspace();
let names: Vec<&str> = workspace.crates.iter().map(|c| c.name.as_str()).collect();
assert!(names.contains(&"codecraft"), "{names:?}");
assert!(names.contains(&"tanks"), "{names:?}");
let mut sorted = names.clone();
sorted.sort();
assert_eq!(names, sorted);
assert_eq!(workspace.name, "codecraft");
}
#[test]
fn examples_depend_on_the_engine_and_have_a_main() {
let workspace = this_workspace();
let tanks = workspace
.crates
.iter()
.find(|c| c.name == "tanks")
.expect("tanks");
assert_eq!(tanks.dependencies, vec!["codecraft"]);
assert!(tanks.roots.iter().any(|r| r.ends_with("main.rs")));
let engine = workspace
.crates
.iter()
.find(|c| c.name == "codecraft")
.expect("codecraft");
assert!(engine.roots[0].ends_with("lib.rs"), "library first");
assert!(engine.dependencies.is_empty());
}
}