xtask/cmd/power_set/
utils.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use anyhow::Context;
4
5#[derive(Debug, Clone, serde_derive::Deserialize)]
6#[serde(default, deny_unknown_fields)]
7pub struct XTaskMetadata {
8 #[serde(alias = "skip-feature-sets")]
11 pub skip_feature_sets: BTreeSet<BTreeSet<String>>,
12 #[serde(alias = "skip-optional-dependencies")]
14 pub skip_optional_dependencies: bool,
15 #[serde(alias = "always-include-features")]
17 pub always_include_features: BTreeSet<String>,
18 #[serde(alias = "max-combination-size")]
20 pub max_combination_size: Option<usize>,
21 #[serde(alias = "additive-features")]
24 pub additive_features: BTreeSet<String>,
25 #[serde(alias = "ignore-features")]
27 pub ignore_features: BTreeSet<String>,
28 pub skip: bool,
30}
31
32impl Default for XTaskMetadata {
33 fn default() -> Self {
34 Self {
35 skip_feature_sets: Default::default(),
36 skip_optional_dependencies: true,
37 always_include_features: Default::default(),
38 max_combination_size: None,
39 additive_features: Default::default(),
40 ignore_features: Default::default(),
41 skip: false,
42 }
43 }
44}
45
46impl XTaskMetadata {
47 pub fn from_package(package: &cargo_metadata::Package) -> anyhow::Result<Self> {
48 let Some(metadata) = package.metadata.get("xtask").and_then(|v| v.get("powerset")) else {
49 return Ok(Self::default());
50 };
51
52 serde_json::from_value(metadata.clone()).context("xtask")
53 }
54}
55
56fn find_permutations<'a>(
57 initial_start: BTreeSet<&'a str>,
58 remaining: usize,
59 permutations: &mut BTreeSet<BTreeSet<&'a str>>,
60 viable_features: &BTreeMap<&'a str, BTreeSet<&'a str>>,
61 skip_feature_sets: &BTreeSet<BTreeSet<&'a str>>,
62) {
63 let mut stack = vec![(initial_start, remaining)];
64
65 while let Some((start, rem)) = stack.pop() {
66 if skip_feature_sets.iter().any(|s| s.is_subset(&start)) || !permutations.insert(start.clone()) || rem == 0 {
67 continue;
68 }
69
70 let flattened: BTreeSet<_> = start
71 .iter()
72 .flat_map(|f| viable_features[f].iter().chain(std::iter::once(f)))
73 .collect();
74
75 for (feature, deps) in viable_features.iter() {
76 if flattened.contains(feature) {
77 continue;
78 }
79
80 let mut new_start = start.clone();
81 new_start.insert(feature);
82 for dep in deps {
83 new_start.remove(dep);
84 }
85
86 if permutations.contains(&new_start) || skip_feature_sets.contains(&new_start) {
87 continue;
88 }
89
90 stack.push((new_start, rem.saturating_sub(1)));
91 }
92 }
93}
94
95fn flatten_features<'a>(
96 deps: impl IntoIterator<Item = &'a str>,
97 package_features: &BTreeMap<&'a str, Vec<&'a str>>,
98) -> BTreeSet<&'a str> {
99 let mut next: Vec<_> = deps.into_iter().collect();
100
101 let mut features = BTreeSet::new();
102 while let Some((dep, deps)) = next.pop().and_then(|d| Some((d, package_features.get(d)?))) {
103 if features.insert(dep) {
104 next.extend(deps);
105 }
106 }
107
108 features
109}
110
111pub fn test_package_features<'a>(
112 package: &'a cargo_metadata::Package,
113 added_features: impl IntoIterator<Item = &'a str>,
114 excluded_features: impl IntoIterator<Item = &'a str>,
115 xtask_metadata: &'a XTaskMetadata,
116) -> anyhow::Result<BTreeSet<BTreeSet<&'a str>>> {
117 if package.features.is_empty() {
118 return Ok(BTreeSet::new());
119 }
120
121 let mut always_included_features: BTreeSet<_> = xtask_metadata
122 .always_include_features
123 .iter()
124 .map(|f| f.as_str())
125 .chain(added_features)
126 .collect();
127
128 let skip_feature_sets: BTreeSet<_> = excluded_features
129 .into_iter()
130 .map(|f| BTreeSet::from_iter(std::iter::once(f)))
131 .chain(
132 xtask_metadata
133 .skip_feature_sets
134 .iter()
135 .map(|s| s.iter().map(|f| f.as_str()).collect()),
136 )
137 .collect();
138
139 let mut package_features: BTreeMap<&str, Vec<&str>> = package
140 .features
141 .iter()
142 .map(|(k, v)| (k.as_str(), v.iter().map(|f| f.as_str()).collect()))
143 .collect();
144
145 if xtask_metadata.skip_optional_dependencies {
146 let mut implicit_features = BTreeSet::new();
147 let mut used_deps = BTreeSet::new();
148
149 for (feature, deps) in package.features.iter() {
150 for dep in deps.iter().filter_map(|f| f.strip_prefix("dep:")) {
151 if dep == feature && deps.len() == 1 {
152 implicit_features.insert(feature.as_str());
153 } else {
154 used_deps.insert(dep);
155 }
156 }
157 }
158
159 for feature in implicit_features {
160 if used_deps.contains(&feature) {
161 continue;
162 }
163
164 package_features.remove(feature);
165 }
166 }
167
168 let mut viable_features = BTreeMap::new();
169 let mut additive_features = BTreeMap::new();
170
171 for (feature, deps) in package_features.iter() {
172 if xtask_metadata.ignore_features.contains(*feature) {
173 continue;
174 }
175
176 let flattened = flatten_features(deps.iter().copied(), &package_features);
177
178 if !xtask_metadata.additive_features.contains(*feature) {
179 viable_features.insert(*feature, flattened);
180 } else {
181 additive_features.insert(*feature, flattened);
182 }
183 }
184
185 always_included_features.retain(|f| package_features.contains_key(f));
187
188 let mut non_additive_permutations = BTreeSet::new();
191
192 find_permutations(
194 always_included_features.clone(),
195 xtask_metadata.max_combination_size.unwrap_or(viable_features.len() + 1),
196 &mut non_additive_permutations,
197 &viable_features,
198 &skip_feature_sets,
199 );
200
201 let mut permutations = BTreeSet::new();
233 for mut permutation in non_additive_permutations {
234 let flattened: BTreeSet<_> = permutation
235 .clone()
236 .into_iter()
237 .flat_map(|f| viable_features[&f].iter().copied().chain(std::iter::once(f)))
238 .collect();
239
240 permutations.insert(permutation.clone());
241 for (feature, deps) in additive_features.iter() {
242 if flattened.contains(feature) {
243 continue;
244 }
245
246 permutation.insert(feature);
247 for dep in deps {
248 permutation.remove(dep);
249 }
250 permutations.insert(permutation.clone());
251 }
252 }
253
254 let flattened: BTreeSet<_> = always_included_features
255 .iter()
256 .flat_map(|f| viable_features[f].iter().chain(std::iter::once(f)))
257 .collect();
258
259 for feature in additive_features.keys() {
260 if flattened.contains(feature) {
261 continue;
262 }
263
264 let mut permutation = always_included_features.clone();
265 permutation.insert(feature);
266 permutations.insert(permutation);
267 }
268
269 Ok(permutations)
270}
271
272pub fn parse_features<'a>(
273 features: impl IntoIterator<Item = &'a str>,
274) -> (BTreeSet<&'a str>, BTreeMap<&'a str, BTreeSet<&'a str>>) {
275 let mut generic_features = BTreeSet::new();
276 let mut crate_features = BTreeMap::new();
277
278 for feature in features {
279 let mut splits = feature.splitn(2, '/');
280 let first = splits.next().unwrap();
281 if let Some(second) = splits.next() {
282 crate_features.entry(first).or_insert_with(BTreeSet::new).insert(second);
283 } else {
284 generic_features.insert(first);
285 }
286 }
287
288 (generic_features, crate_features)
289}