1 | //! Cargo flags for selecting crates in a workspace. |
2 | |
3 | #[derive (Default, Clone, Debug, PartialEq, Eq)] |
4 | #[cfg_attr (feature = "clap" , derive(clap::Args))] |
5 | #[non_exhaustive ] |
6 | pub struct Workspace { |
7 | #[cfg_attr (feature = "clap" , arg(short, long, value_name = "SPEC" ))] |
8 | /// Package to process (see `cargo help pkgid`) |
9 | pub package: Vec<String>, |
10 | #[cfg_attr (feature = "clap" , arg(long))] |
11 | /// Process all packages in the workspace |
12 | pub workspace: bool, |
13 | #[cfg_attr ( |
14 | feature = "clap" , |
15 | arg(long, hide_short_help(true), hide_long_help(true)) |
16 | )] |
17 | /// Process all packages in the workspace |
18 | pub all: bool, |
19 | #[cfg_attr (feature = "clap" , arg(long, value_name = "SPEC" ))] |
20 | /// Exclude packages from being processed |
21 | pub exclude: Vec<String>, |
22 | } |
23 | |
24 | #[cfg (feature = "cargo_metadata" )] |
25 | impl Workspace { |
26 | /// Partition workspace members into those selected and those excluded. |
27 | /// |
28 | /// Notes: |
29 | /// - Requires the features `cargo_metadata`. |
30 | /// - Requires not calling `MetadataCommand::no_deps` |
31 | pub fn partition_packages<'m>( |
32 | &self, |
33 | meta: &'m cargo_metadata::Metadata, |
34 | ) -> ( |
35 | Vec<&'m cargo_metadata::Package>, |
36 | Vec<&'m cargo_metadata::Package>, |
37 | ) { |
38 | let selection = |
39 | Packages::from_flags(self.workspace || self.all, &self.exclude, &self.package); |
40 | let workspace_members: std::collections::HashSet<_> = |
41 | meta.workspace_members.iter().collect(); |
42 | let base_ids: std::collections::HashSet<_> = match selection { |
43 | Packages::Default => { |
44 | // Deviating from cargo because Metadata doesn't have default members |
45 | let resolve = meta.resolve.as_ref().expect("no-deps is unsupported" ); |
46 | match &resolve.root { |
47 | Some(root) => { |
48 | let mut base_ids = std::collections::HashSet::new(); |
49 | base_ids.insert(root); |
50 | base_ids |
51 | } |
52 | None => workspace_members, |
53 | } |
54 | } |
55 | Packages::All => workspace_members, |
56 | Packages::OptOut(_) => workspace_members, // Deviating from cargo by only checking workspace members |
57 | Packages::Packages(patterns) => { |
58 | meta.packages |
59 | .iter() |
60 | // Deviating from cargo by not supporting patterns |
61 | // Deviating from cargo by only checking workspace members |
62 | .filter(|p| workspace_members.contains(&p.id) && patterns.contains(&p.name)) |
63 | .map(|p| &p.id) |
64 | .collect() |
65 | } |
66 | }; |
67 | |
68 | meta.packages |
69 | .iter() |
70 | // Deviating from cargo by not supporting patterns |
71 | .partition(|p| base_ids.contains(&p.id) && !self.exclude.contains(&p.name)) |
72 | } |
73 | } |
74 | |
75 | // See cargo's src/cargo/ops/cargo_compile.rs |
76 | #[derive (Clone, PartialEq, Eq, Debug)] |
77 | #[cfg (feature = "cargo_metadata" )] |
78 | #[allow (clippy::enum_variant_names)] |
79 | enum Packages<'p> { |
80 | Default, |
81 | All, |
82 | OptOut(&'p [String]), |
83 | Packages(&'p [String]), |
84 | } |
85 | |
86 | #[cfg (feature = "cargo_metadata" )] |
87 | impl<'p> Packages<'p> { |
88 | pub fn from_flags(all: bool, exclude: &'p [String], package: &'p [String]) -> Self { |
89 | match (all, exclude.len(), package.len()) { |
90 | (false, 0, 0) => Packages::Default, |
91 | (false, 0, _) => Packages::Packages(package), |
92 | (false, _, 0) => Packages::OptOut(exclude), // Deviating from cargo because we don't do error handling |
93 | (false, _, _) => Packages::Packages(package), // Deviating from cargo because we don't do error handling |
94 | (true, 0, _) => Packages::All, |
95 | (true, _, _) => Packages::OptOut(exclude), |
96 | } |
97 | } |
98 | } |
99 | |
100 | #[cfg (test)] |
101 | mod test { |
102 | use super::*; |
103 | |
104 | #[test ] |
105 | #[cfg (feature = "clap" )] |
106 | fn verify_app() { |
107 | #[derive (Debug, clap::Parser)] |
108 | struct Cli { |
109 | #[command(flatten)] |
110 | workspace: Workspace, |
111 | } |
112 | |
113 | use clap::CommandFactory; |
114 | Cli::command().debug_assert() |
115 | } |
116 | |
117 | #[test ] |
118 | #[cfg (feature = "clap" )] |
119 | fn parse_multiple_occurrences() { |
120 | use clap::Parser; |
121 | |
122 | #[derive (PartialEq, Eq, Debug, Parser)] |
123 | struct Args { |
124 | positional: Option<String>, |
125 | #[command(flatten)] |
126 | workspace: Workspace, |
127 | } |
128 | |
129 | assert_eq!( |
130 | Args { |
131 | positional: None, |
132 | workspace: Workspace { |
133 | package: vec![], |
134 | workspace: false, |
135 | all: false, |
136 | exclude: vec![], |
137 | } |
138 | }, |
139 | Args::parse_from(["test" ]) |
140 | ); |
141 | assert_eq!( |
142 | Args { |
143 | positional: Some("baz" .to_owned()), |
144 | workspace: Workspace { |
145 | package: vec!["foo" .to_owned(), "bar" .to_owned()], |
146 | workspace: false, |
147 | all: false, |
148 | exclude: vec![], |
149 | } |
150 | }, |
151 | Args::parse_from(["test" , "--package" , "foo" , "--package" , "bar" , "baz" ]) |
152 | ); |
153 | assert_eq!( |
154 | Args { |
155 | positional: Some("baz" .to_owned()), |
156 | workspace: Workspace { |
157 | package: vec![], |
158 | workspace: false, |
159 | all: false, |
160 | exclude: vec!["foo" .to_owned(), "bar" .to_owned()], |
161 | } |
162 | }, |
163 | Args::parse_from(["test" , "--exclude" , "foo" , "--exclude" , "bar" , "baz" ]) |
164 | ); |
165 | } |
166 | |
167 | #[cfg (feature = "cargo_metadata" )] |
168 | #[cfg (test)] |
169 | mod partition_default { |
170 | use super::*; |
171 | |
172 | #[test ] |
173 | fn single_crate() { |
174 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
175 | metadata.manifest_path("tests/fixtures/simple/Cargo.toml" ); |
176 | let metadata = metadata.exec().unwrap(); |
177 | |
178 | let workspace = Workspace { |
179 | ..Default::default() |
180 | }; |
181 | let (included, excluded) = workspace.partition_packages(&metadata); |
182 | assert_eq!(included.len(), 1); |
183 | assert_eq!(excluded.len(), 0); |
184 | } |
185 | |
186 | #[test ] |
187 | fn mixed_ws_root() { |
188 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
189 | metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml" ); |
190 | let metadata = metadata.exec().unwrap(); |
191 | |
192 | let workspace = Workspace { |
193 | ..Default::default() |
194 | }; |
195 | let (included, excluded) = workspace.partition_packages(&metadata); |
196 | assert_eq!(included.len(), 1); |
197 | assert_eq!(excluded.len(), 2); |
198 | } |
199 | |
200 | #[test ] |
201 | fn mixed_ws_leaf() { |
202 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
203 | metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml" ); |
204 | let metadata = metadata.exec().unwrap(); |
205 | |
206 | let workspace = Workspace { |
207 | ..Default::default() |
208 | }; |
209 | let (included, excluded) = workspace.partition_packages(&metadata); |
210 | assert_eq!(included.len(), 1); |
211 | assert_eq!(excluded.len(), 2); |
212 | } |
213 | |
214 | #[test ] |
215 | fn pure_ws_root() { |
216 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
217 | metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml" ); |
218 | let metadata = metadata.exec().unwrap(); |
219 | |
220 | let workspace = Workspace { |
221 | ..Default::default() |
222 | }; |
223 | let (included, excluded) = workspace.partition_packages(&metadata); |
224 | assert_eq!(included.len(), 3); |
225 | assert_eq!(excluded.len(), 0); |
226 | } |
227 | |
228 | #[test ] |
229 | fn pure_ws_leaf() { |
230 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
231 | metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml" ); |
232 | let metadata = metadata.exec().unwrap(); |
233 | |
234 | let workspace = Workspace { |
235 | ..Default::default() |
236 | }; |
237 | let (included, excluded) = workspace.partition_packages(&metadata); |
238 | assert_eq!(included.len(), 1); |
239 | assert_eq!(excluded.len(), 2); |
240 | } |
241 | } |
242 | |
243 | #[cfg (feature = "cargo_metadata" )] |
244 | #[cfg (test)] |
245 | mod partition_all { |
246 | use super::*; |
247 | |
248 | #[test ] |
249 | fn single_crate() { |
250 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
251 | metadata.manifest_path("tests/fixtures/simple/Cargo.toml" ); |
252 | let metadata = metadata.exec().unwrap(); |
253 | |
254 | let workspace = Workspace { |
255 | all: true, |
256 | ..Default::default() |
257 | }; |
258 | let (included, excluded) = workspace.partition_packages(&metadata); |
259 | assert_eq!(included.len(), 1); |
260 | assert_eq!(excluded.len(), 0); |
261 | } |
262 | |
263 | #[test ] |
264 | fn mixed_ws_root() { |
265 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
266 | metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml" ); |
267 | let metadata = metadata.exec().unwrap(); |
268 | |
269 | let workspace = Workspace { |
270 | all: true, |
271 | ..Default::default() |
272 | }; |
273 | let (included, excluded) = workspace.partition_packages(&metadata); |
274 | assert_eq!(included.len(), 3); |
275 | assert_eq!(excluded.len(), 0); |
276 | } |
277 | |
278 | #[test ] |
279 | fn mixed_ws_leaf() { |
280 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
281 | metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml" ); |
282 | let metadata = metadata.exec().unwrap(); |
283 | |
284 | let workspace = Workspace { |
285 | all: true, |
286 | ..Default::default() |
287 | }; |
288 | let (included, excluded) = workspace.partition_packages(&metadata); |
289 | assert_eq!(included.len(), 3); |
290 | assert_eq!(excluded.len(), 0); |
291 | } |
292 | |
293 | #[test ] |
294 | fn pure_ws_root() { |
295 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
296 | metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml" ); |
297 | let metadata = metadata.exec().unwrap(); |
298 | |
299 | let workspace = Workspace { |
300 | all: true, |
301 | ..Default::default() |
302 | }; |
303 | let (included, excluded) = workspace.partition_packages(&metadata); |
304 | assert_eq!(included.len(), 3); |
305 | assert_eq!(excluded.len(), 0); |
306 | } |
307 | |
308 | #[test ] |
309 | fn pure_ws_leaf() { |
310 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
311 | metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml" ); |
312 | let metadata = metadata.exec().unwrap(); |
313 | |
314 | let workspace = Workspace { |
315 | all: true, |
316 | ..Default::default() |
317 | }; |
318 | let (included, excluded) = workspace.partition_packages(&metadata); |
319 | assert_eq!(included.len(), 3); |
320 | assert_eq!(excluded.len(), 0); |
321 | } |
322 | } |
323 | |
324 | #[cfg (feature = "cargo_metadata" )] |
325 | #[cfg (test)] |
326 | mod partition_package { |
327 | use super::*; |
328 | |
329 | #[test ] |
330 | fn single_crate() { |
331 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
332 | metadata.manifest_path("tests/fixtures/simple/Cargo.toml" ); |
333 | let metadata = metadata.exec().unwrap(); |
334 | |
335 | let workspace = Workspace { |
336 | package: vec!["simple" .to_owned()], |
337 | ..Default::default() |
338 | }; |
339 | let (included, excluded) = workspace.partition_packages(&metadata); |
340 | assert_eq!(included.len(), 1); |
341 | assert_eq!(excluded.len(), 0); |
342 | } |
343 | |
344 | #[test ] |
345 | fn mixed_ws_root() { |
346 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
347 | metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml" ); |
348 | let metadata = metadata.exec().unwrap(); |
349 | |
350 | let workspace = Workspace { |
351 | package: vec!["a" .to_owned()], |
352 | ..Default::default() |
353 | }; |
354 | let (included, excluded) = workspace.partition_packages(&metadata); |
355 | assert_eq!(included.len(), 1); |
356 | assert_eq!(excluded.len(), 2); |
357 | } |
358 | |
359 | #[test ] |
360 | fn mixed_ws_leaf() { |
361 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
362 | metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml" ); |
363 | let metadata = metadata.exec().unwrap(); |
364 | |
365 | let workspace = Workspace { |
366 | package: vec!["a" .to_owned()], |
367 | ..Default::default() |
368 | }; |
369 | let (included, excluded) = workspace.partition_packages(&metadata); |
370 | assert_eq!(included.len(), 1); |
371 | assert_eq!(excluded.len(), 2); |
372 | } |
373 | |
374 | #[test ] |
375 | fn pure_ws_root() { |
376 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
377 | metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml" ); |
378 | let metadata = metadata.exec().unwrap(); |
379 | |
380 | let workspace = Workspace { |
381 | package: vec!["a" .to_owned()], |
382 | ..Default::default() |
383 | }; |
384 | let (included, excluded) = workspace.partition_packages(&metadata); |
385 | assert_eq!(included.len(), 1); |
386 | assert_eq!(excluded.len(), 2); |
387 | } |
388 | |
389 | #[test ] |
390 | fn pure_ws_leaf() { |
391 | let mut metadata = cargo_metadata::MetadataCommand::new(); |
392 | metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml" ); |
393 | let metadata = metadata.exec().unwrap(); |
394 | |
395 | let workspace = Workspace { |
396 | package: vec!["a" .to_owned()], |
397 | ..Default::default() |
398 | }; |
399 | let (included, excluded) = workspace.partition_packages(&metadata); |
400 | assert_eq!(included.len(), 1); |
401 | assert_eq!(excluded.len(), 2); |
402 | } |
403 | } |
404 | } |
405 | |