use crate::{core_config::SubsetConfig, Result, SystemError};
use camino::Utf8PathBuf;
use guppy::{
graph::{
cargo::{CargoOptions, CargoResolverVersion, CargoSet},
feature::{FeatureFilter, FeatureSet, StandardFeatures},
DependencyDirection, PackageGraph, PackageMetadata, PackageSet,
},
PackageId,
};
use serde::Deserialize;
use std::{collections::BTreeMap, fs, path::Path};
use toml::de;
#[derive(Clone, Debug)]
pub struct WorkspaceSubsets<'g> {
default_members: WorkspaceSubset<'g>,
subsets: BTreeMap<String, WorkspaceSubset<'g>>,
}
impl<'g> WorkspaceSubsets<'g> {
pub fn new(
graph: &'g PackageGraph,
project_root: &Path,
config: &BTreeMap<String, SubsetConfig>,
) -> Result<Self> {
let mut cargo_opts = CargoOptions::new();
cargo_opts
.set_version(CargoResolverVersion::V2)
.set_include_dev(false);
let default_members = Self::read_default_members(project_root)?;
let initial_packages = graph
.resolve_workspace_paths(&default_members)
.map_err(|err| SystemError::guppy("querying default members", err))?;
let default_members =
WorkspaceSubset::new(&initial_packages, StandardFeatures::Default, &cargo_opts);
let subsets = config
.iter()
.map(|(name, config)| {
let initial_packages = graph
.resolve_workspace_names(&config.root_members)
.map_err(|err| {
SystemError::guppy(format!("querying members for subset '{}'", name), err)
})?;
let subset =
WorkspaceSubset::new(&initial_packages, StandardFeatures::Default, &cargo_opts);
Ok((name.clone(), subset))
})
.collect::<Result<_, _>>()?;
Ok(Self {
default_members,
subsets,
})
}
pub fn default_members(&self) -> &WorkspaceSubset<'g> {
&self.default_members
}
pub fn get(&self, name: impl AsRef<str>) -> Option<&WorkspaceSubset<'g>> {
self.subsets.get(name.as_ref())
}
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a WorkspaceSubset<'g>)> + 'a {
self.subsets
.iter()
.map(|(name, subset)| (name.as_str(), subset))
}
fn read_default_members(project_root: &Path) -> Result<Vec<Utf8PathBuf>> {
#[derive(Deserialize)]
struct RootToml {
workspace: Workspace,
}
#[derive(Deserialize)]
struct Workspace {
#[serde(rename = "default-members")]
default_members: Vec<Utf8PathBuf>,
}
let root_toml = project_root.join("Cargo.toml");
let contents =
fs::read(&root_toml).map_err(|err| SystemError::io("reading root Cargo.toml", err))?;
let contents: RootToml = de::from_slice(&contents)
.map_err(|err| SystemError::de("deserializing root Cargo.toml", err))?;
Ok(contents.workspace.default_members)
}
}
#[derive(Clone, Debug)]
pub struct WorkspaceSubset<'g> {
build_set: CargoSet<'g>,
unified_set: FeatureSet<'g>,
}
impl<'g> WorkspaceSubset<'g> {
pub fn new<'a>(
initial_packages: &PackageSet<'g>,
feature_filter: impl FeatureFilter<'g>,
cargo_opts: &CargoOptions<'_>,
) -> Self {
let build_set = initial_packages
.to_feature_set(feature_filter)
.into_cargo_set(cargo_opts)
.expect("into_cargo_set should always succeed");
let unified_set = build_set.host_features().union(build_set.target_features());
Self {
build_set,
unified_set,
}
}
pub fn initials(&self) -> &FeatureSet<'g> {
self.build_set.initials()
}
pub fn status_of(&self, package_id: &PackageId) -> WorkspaceStatus {
if self
.build_set
.initials()
.contains_package(package_id)
.unwrap_or(false)
{
WorkspaceStatus::RootMember
} else if self
.unified_set
.features_for(package_id)
.unwrap_or(None)
.is_some()
{
WorkspaceStatus::Dependency
} else {
WorkspaceStatus::Absent
}
}
pub fn root_members<'a>(&'a self) -> impl Iterator<Item = PackageMetadata<'g>> + 'a {
self.build_set
.initials()
.packages_with_features(DependencyDirection::Forward)
.map(|f| *f.package())
}
pub fn build_set(&self) -> &CargoSet<'g> {
&self.build_set
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum WorkspaceStatus {
RootMember,
Dependency,
Absent,
}