x_core/
workspace_subset.rs1use crate::{core_config::SubsetConfig, Result, SystemError};
5use camino::Utf8PathBuf;
6use guppy::{
7 graph::{
8 cargo::{CargoOptions, CargoResolverVersion, CargoSet},
9 feature::{FeatureFilter, FeatureSet, StandardFeatures},
10 DependencyDirection, PackageGraph, PackageMetadata, PackageSet,
11 },
12 PackageId,
13};
14use serde::Deserialize;
15use std::{collections::BTreeMap, fs, path::Path};
16use toml::de;
17
18#[derive(Clone, Debug)]
20pub struct WorkspaceSubsets<'g> {
21 default_members: WorkspaceSubset<'g>,
23 subsets: BTreeMap<String, WorkspaceSubset<'g>>,
24}
25
26impl<'g> WorkspaceSubsets<'g> {
27 pub fn new(
34 graph: &'g PackageGraph,
35 project_root: &Path,
36 config: &BTreeMap<String, SubsetConfig>,
37 ) -> Result<Self> {
38 let mut cargo_opts = CargoOptions::new();
39 cargo_opts
40 .set_version(CargoResolverVersion::V2)
41 .set_include_dev(false);
42
43 let default_members = Self::read_default_members(project_root)?;
44
45 let initial_packages = graph
47 .resolve_workspace_paths(&default_members)
48 .map_err(|err| SystemError::guppy("querying default members", err))?;
49 let default_members =
50 WorkspaceSubset::new(&initial_packages, StandardFeatures::Default, &cargo_opts);
51
52 let subsets = config
54 .iter()
55 .map(|(name, config)| {
56 let initial_packages = graph
57 .resolve_workspace_names(&config.root_members)
58 .map_err(|err| {
59 SystemError::guppy(format!("querying members for subset '{}'", name), err)
60 })?;
61 let subset =
62 WorkspaceSubset::new(&initial_packages, StandardFeatures::Default, &cargo_opts);
63 Ok((name.clone(), subset))
64 })
65 .collect::<Result<_, _>>()?;
66
67 Ok(Self {
68 default_members,
69 subsets,
70 })
71 }
72
73 pub fn default_members(&self) -> &WorkspaceSubset<'g> {
75 &self.default_members
76 }
77
78 pub fn get(&self, name: impl AsRef<str>) -> Option<&WorkspaceSubset<'g>> {
80 self.subsets.get(name.as_ref())
81 }
82
83 pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a WorkspaceSubset<'g>)> + 'a {
85 self.subsets
86 .iter()
87 .map(|(name, subset)| (name.as_str(), subset))
88 }
89
90 fn read_default_members(project_root: &Path) -> Result<Vec<Utf8PathBuf>> {
95 #[derive(Deserialize)]
96 struct RootToml {
97 workspace: Workspace,
98 }
99
100 #[derive(Deserialize)]
101 struct Workspace {
102 #[serde(rename = "default-members")]
103 default_members: Vec<Utf8PathBuf>,
104 }
105
106 let root_toml = project_root.join("Cargo.toml");
107 let contents =
108 fs::read(&root_toml).map_err(|err| SystemError::io("reading root Cargo.toml", err))?;
109 let contents: RootToml = de::from_slice(&contents)
110 .map_err(|err| SystemError::de("deserializing root Cargo.toml", err))?;
111 Ok(contents.workspace.default_members)
112 }
113}
114
115#[derive(Clone, Debug)]
119pub struct WorkspaceSubset<'g> {
120 build_set: CargoSet<'g>,
121 unified_set: FeatureSet<'g>,
122}
123
124impl<'g> WorkspaceSubset<'g> {
125 pub fn new<'a>(
128 initial_packages: &PackageSet<'g>,
129 feature_filter: impl FeatureFilter<'g>,
130 cargo_opts: &CargoOptions<'_>,
131 ) -> Self {
132 let build_set = initial_packages
134 .to_feature_set(feature_filter)
135 .into_cargo_set(cargo_opts)
136 .expect("into_cargo_set should always succeed");
137 let unified_set = build_set.host_features().union(build_set.target_features());
138
139 Self {
140 build_set,
141 unified_set,
142 }
143 }
144
145 pub fn initials(&self) -> &FeatureSet<'g> {
147 self.build_set.initials()
148 }
149
150 pub fn status_of(&self, package_id: &PackageId) -> WorkspaceStatus {
152 if self
153 .build_set
154 .initials()
155 .contains_package(package_id)
156 .unwrap_or(false)
157 {
158 WorkspaceStatus::RootMember
159 } else if self
160 .unified_set
161 .features_for(package_id)
162 .unwrap_or(None)
163 .is_some()
164 {
165 WorkspaceStatus::Dependency
166 } else {
167 WorkspaceStatus::Absent
168 }
169 }
170
171 pub fn root_members<'a>(&'a self) -> impl Iterator<Item = PackageMetadata<'g>> + 'a {
173 self.build_set
174 .initials()
175 .packages_with_features(DependencyDirection::Forward)
176 .map(|f| *f.package())
177 }
178
179 pub fn build_set(&self) -> &CargoSet<'g> {
184 &self.build_set
185 }
186}
187
188#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
190pub enum WorkspaceStatus {
191 RootMember,
193 Dependency,
195 Absent,
197}