Skip to main content

cargo_fixit/util/
cli.rs

1use clap::Parser;
2
3use crate::CargoResult;
4
5#[derive(Debug, Parser)]
6pub struct CheckFlags {
7    /// Package(s) to fix
8    #[arg(short, long, value_name = "SPEC", help_heading = "Package Selection")]
9    package: Vec<String>,
10
11    /// Fix all packages in the workspace
12    #[arg(long, help_heading = "Package Selection")]
13    workspace: bool,
14
15    /// Exclude packages from the fixes
16    #[arg(long, value_name = "SPEC", help_heading = "Package Selection")]
17    exclude: Vec<String>,
18
19    /// Alias for --workspace (deprecated)
20    #[arg(long, help_heading = "Package Selection")]
21    all: bool,
22
23    /// Fix only this package's library
24    #[arg(long, help_heading = "Target Selection")]
25    lib: bool,
26
27    /// Fix all binaries
28    #[arg(long, help_heading = "Target Selection")]
29    bins: bool,
30
31    /// Fix only the specified binary
32    #[arg(long, value_name = "NAME", help_heading = "Target Selection")]
33    bin: Option<String>,
34
35    /// Fix all examples
36    #[arg(long, help_heading = "Target Selection")]
37    examples: bool,
38
39    /// Fix only the specified binary
40    #[arg(long, value_name = "NAME", help_heading = "Target Selection")]
41    example: Option<String>,
42
43    /// Fix all tests
44    #[arg(long, help_heading = "Target Selection")]
45    tests: bool,
46
47    /// Fix only the specified test
48    #[arg(long, value_name = "NAME", help_heading = "Target Selection")]
49    test: Option<String>,
50
51    /// Fix all benches
52    #[arg(long, help_heading = "Target Selection")]
53    benches: bool,
54
55    /// Fix only the specified bench
56    #[arg(long, value_name = "NAME", help_heading = "Target Selection")]
57    bench: Option<String>,
58
59    /// Fix all targets
60    #[arg(long, help_heading = "Target Selection")]
61    all_targets: bool,
62
63    /// Space or comma separated list of features to activate
64    #[arg(
65        short = 'F',
66        long,
67        value_name = "FEATURES",
68        help_heading = "Feature Selection"
69    )]
70    features: Vec<String>,
71
72    /// Activate all available features
73    #[arg(long, help_heading = "Feature Selection")]
74    all_features: bool,
75
76    /// Do not activate the `default` feature
77    #[arg(long, help_heading = "Feature Selection")]
78    no_default_features: bool,
79
80    /// Unstable (nightly-only) flags
81    #[arg(short = 'Z', value_name = "FLAG")]
82    unstable_flags: Vec<String>,
83
84    /// Number of parallel jobs, defaults to # of CPUs.
85    #[arg(long, value_name = "N", help_heading = "Compilation Options")]
86    jobs: Option<usize>,
87
88    /// Fix artifacts in release mode, with optimizations
89    #[arg(long, help_heading = "Compilation Options")]
90    release: bool,
91
92    /// Build artifacts with the specified profile
93    #[arg(
94        long,
95        value_name = "PROFILE-NAME",
96        help_heading = "Compilation Options"
97    )]
98    profile: Option<String>,
99
100    /// Fix for the target triple
101    #[arg(long, value_name = "TRIPLE", help_heading = "Compilation Options")]
102    target: Vec<String>,
103
104    /// Directory for all generated artifacts
105    #[arg(long, value_name = "DIRECTORY", help_heading = "Compilation Options")]
106    target_dir: Option<String>,
107
108    /// Path to Cargo.toml
109    #[arg(long, value_name = "PATH", help_heading = "Manifest Options")]
110    manifest_path: Option<String>,
111
112    /// Path to Cargo.lock (unstable)
113    #[arg(long, value_name = "PATH", help_heading = "Manifest Options")]
114    lockfile_path: Option<String>,
115
116    /// Ignore `rust-version` specification in packages
117    #[arg(long, help_heading = "Manifest Options")]
118    ignore_rust_version: bool,
119
120    /// Assert that `Cargo.lock` will remain unchanged
121    #[arg(long, help_heading = "Manifest Options")]
122    locked: bool,
123
124    /// Run without accessing the network
125    #[arg(long, help_heading = "Manifest Options")]
126    offline: bool,
127
128    /// Equivalent to specifying both --locked and --offline
129    #[arg(long, help_heading = "Manifest Options")]
130    frozen: bool,
131}
132
133/// Package selectors that determine which workspace packages Cargo treats as primary.
134#[derive(Debug)]
135pub(crate) enum PackageSelection<'a> {
136    Default,
137    Workspace { exclude: &'a [String] },
138    Packages(&'a [String]),
139}
140
141impl CheckFlags {
142    pub(crate) fn package_selection(&self) -> PackageSelection<'_> {
143        if self.workspace || self.all {
144            PackageSelection::Workspace {
145                exclude: &self.exclude,
146            }
147        } else if self.package.is_empty() {
148            debug_assert!(self.exclude.is_empty());
149            PackageSelection::Default
150        } else {
151            debug_assert!(self.exclude.is_empty());
152            PackageSelection::Packages(&self.package)
153        }
154    }
155
156    /// Whether one of this package's targets is explicitly selected for fixing.
157    pub(crate) fn selects_package_targets(
158        &self,
159        package: &cargo_metadata::Package,
160    ) -> CargoResult<bool> {
161        if self.all_targets || !self.has_target_selection() {
162            return Ok(true);
163        }
164
165        for target in &package.targets {
166            if self.selects_target(target)? {
167                return Ok(true);
168            }
169        }
170
171        Ok(false)
172    }
173
174    fn has_target_selection(&self) -> bool {
175        self.lib
176            || self.bins
177            || self.bin.is_some()
178            || self.examples
179            || self.example.is_some()
180            || self.tests
181            || self.test.is_some()
182            || self.benches
183            || self.bench.is_some()
184    }
185
186    fn selects_target(&self, target: &cargo_metadata::Target) -> CargoResult<bool> {
187        let is_lib = target.kind.iter().any(|kind| {
188            matches!(
189                kind,
190                cargo_metadata::TargetKind::Lib
191                    | cargo_metadata::TargetKind::RLib
192                    | cargo_metadata::TargetKind::DyLib
193                    | cargo_metadata::TargetKind::CDyLib
194                    | cargo_metadata::TargetKind::StaticLib
195                    | cargo_metadata::TargetKind::ProcMacro
196            )
197        });
198
199        if self.lib && is_lib {
200            return Ok(true);
201        }
202        if self.bins && target.is_bin() {
203            return Ok(true);
204        }
205        if target.is_bin() && matches_target_name(self.bin.as_deref(), &target.name)? {
206            return Ok(true);
207        }
208        if self.examples && target.is_example() {
209            return Ok(true);
210        }
211        if target.is_example() && matches_target_name(self.example.as_deref(), &target.name)? {
212            return Ok(true);
213        }
214        if self.tests && (target.is_test() || target.test) {
215            return Ok(true);
216        }
217        if target.is_test() && matches_target_name(self.test.as_deref(), &target.name)? {
218            return Ok(true);
219        }
220        if self.benches && target.is_bench() {
221            // HACK: no `target.bench` in `cargo metadata` output
222            return Ok(true);
223        }
224        if target.is_bench() && matches_target_name(self.bench.as_deref(), &target.name)? {
225            return Ok(true);
226        }
227
228        Ok(false)
229    }
230
231    pub fn to_flags(&self) -> Vec<String> {
232        let mut out = Vec::new();
233
234        for spec in self.package.clone() {
235            out.push("--package".to_owned());
236            out.push(spec);
237        }
238        if self.workspace {
239            out.push("--workspace".to_owned());
240        }
241        for spec in self.exclude.clone() {
242            out.push("--exclude".to_owned());
243            out.push(spec);
244        }
245        if self.all {
246            out.push("--all".to_owned());
247        }
248
249        if self.lib {
250            out.push("--lib".to_owned());
251        }
252
253        if self.bins {
254            out.push("--bins".to_owned());
255        }
256        if let Some(b) = self.bin.clone() {
257            out.push("--bin".to_owned());
258            out.push(b);
259        }
260
261        if self.examples {
262            out.push("--examples".to_owned());
263        }
264        if let Some(b) = self.example.clone() {
265            out.push("--example".to_owned());
266            out.push(b);
267        }
268
269        if self.tests {
270            out.push("--tests".to_owned());
271        }
272        if let Some(b) = self.test.clone() {
273            out.push("--test".to_owned());
274            out.push(b);
275        }
276
277        if self.benches {
278            out.push("--benches".to_owned());
279        }
280        if let Some(b) = self.bench.clone() {
281            out.push("--bench".to_owned());
282            out.push(b);
283        }
284
285        if self.all_targets {
286            out.push("--all-targets".to_owned());
287        }
288
289        for i in self.features.clone() {
290            out.push("--features".to_owned());
291            out.push(i);
292        }
293        if self.all_features {
294            out.push("--all-features".to_owned());
295        }
296        if self.no_default_features {
297            out.push("--no-default-features".to_owned());
298        }
299
300        for i in self.unstable_flags.clone() {
301            out.push("-Z".to_owned());
302            out.push(i);
303        }
304
305        if let Some(b) = self.jobs {
306            out.push("--jobs".to_owned());
307            out.push(b.to_string());
308        }
309        if self.release {
310            out.push("--release".to_owned());
311        }
312        if let Some(b) = self.profile.clone() {
313            out.push("--profile".to_owned());
314            out.push(b);
315        }
316
317        for spec in self.target.clone() {
318            out.push("--target".to_owned());
319            out.push(spec);
320        }
321        if let Some(b) = self.target_dir.clone() {
322            out.push("--target-dir".to_owned());
323            out.push(b);
324        }
325
326        if let Some(b) = self.manifest_path.clone() {
327            out.push("--manifest-path".to_owned());
328            out.push(b);
329        }
330        if let Some(b) = self.lockfile_path.clone() {
331            out.push("--lockfile-path".to_owned());
332            out.push(b);
333        }
334        if self.ignore_rust_version {
335            out.push("--ignore-rust-version".to_owned());
336        }
337        if self.locked {
338            out.push("--locked".to_owned());
339        }
340        if self.offline {
341            out.push("--offline".to_owned());
342        }
343        if self.frozen {
344            out.push("--frozen".to_owned());
345        }
346        out
347    }
348
349    /// Returns flags that can affect dependency resolution.
350    ///
351    /// Package and target filters are omitted so the resulting graph stays conservative.
352    pub(crate) fn to_metadata_flags(&self) -> Vec<String> {
353        let mut out = Vec::new();
354
355        for feature in &self.features {
356            out.push("--features".to_owned());
357            out.push(feature.clone());
358        }
359        if self.all_features {
360            out.push("--all-features".to_owned());
361        }
362        if self.no_default_features {
363            out.push("--no-default-features".to_owned());
364        }
365
366        for flag in &self.unstable_flags {
367            out.push("-Z".to_owned());
368            out.push(flag.clone());
369        }
370
371        if let Some(path) = &self.manifest_path {
372            out.push("--manifest-path".to_owned());
373            out.push(path.clone());
374        }
375        if let Some(path) = &self.lockfile_path {
376            out.push("--lockfile-path".to_owned());
377            out.push(path.clone());
378        }
379        if self.locked {
380            out.push("--locked".to_owned());
381        }
382        if self.offline {
383            out.push("--offline".to_owned());
384        }
385        if self.frozen {
386            out.push("--frozen".to_owned());
387        }
388
389        out
390    }
391}
392
393fn matches_target_name(requested: Option<&str>, actual: &str) -> CargoResult<bool> {
394    requested
395        .map(|pattern| {
396            glob::Pattern::new(pattern)
397                .map(|pattern| pattern.matches(actual))
398                .map_err(Into::into)
399        })
400        .unwrap_or(Ok(false))
401}