use std::collections::{BTreeSet, HashSet};
use std::path::{Path, PathBuf};
use cargo_metadata::{MetadataCommand, TargetKind};
use serde_json::Value;
use crate::error::{EachError, LoadMetadataError};
#[derive(Debug, Clone)]
pub(crate) struct Workspace {
pub(crate) members: Vec<Member>,
pub(crate) default_member_names: HashSet<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct Member {
pub(crate) name: String,
pub(crate) version: String,
pub(crate) manifest_path: PathBuf,
pub(crate) has_lib: bool,
pub(crate) has_bin: bool,
pub(crate) dependencies: BTreeSet<String>,
pub(crate) metadata: Value,
}
impl Member {
#[must_use]
pub(crate) fn spec(&self) -> String {
format!("{}@{}", self.name, self.version)
}
#[must_use]
pub(crate) fn manifest_dir(&self) -> &Path {
self.manifest_path
.parent()
.expect("cargo-metadata always reports a manifest file path with a parent directory")
}
}
impl Workspace {
#[ohno::enrich_err("failed to load cargo workspace metadata")]
pub(crate) fn load(manifest_path: Option<&Path>) -> Result<Self, EachError> {
let mut cmd = MetadataCommand::new();
cmd.no_deps();
if let Some(path) = manifest_path {
cmd.manifest_path(path);
}
let metadata = cmd.exec().map_err(LoadMetadataError::caused_by)?;
let mut members: Vec<Member> = metadata
.workspace_packages()
.iter()
.map(|pkg| {
let has_lib = pkg.targets.iter().any(is_lib_target);
let has_bin = pkg.targets.iter().any(is_bin_target);
let dependencies = pkg.dependencies.iter().map(|d| d.name.clone()).collect();
Member {
name: pkg.name.to_string(),
version: pkg.version.to_string(),
manifest_path: pkg.manifest_path.clone().into_std_path_buf(),
has_lib,
has_bin,
dependencies,
metadata: pkg.metadata.clone(),
}
})
.collect();
members.sort_by(|a, b| a.name.cmp(&b.name));
let default_member_names = metadata
.workspace_default_packages()
.iter()
.map(|pkg| pkg.name.to_string())
.collect();
Ok(Self {
members,
default_member_names,
})
}
}
fn is_lib_target(target: &cargo_metadata::Target) -> bool {
target.kind.iter().any(|k| matches!(k, TargetKind::Lib))
}
fn is_bin_target(target: &cargo_metadata::Target) -> bool {
target.kind.iter().any(|k| matches!(k, TargetKind::Bin))
}