args.rs (94954B)
1 use super::{ 2 cargo::{CargoErr, Check, Clippy, Options, Test, Toolchain}, 3 manifest::PowerSet, 4 }; 5 use core::{ 6 fmt::{self, Display, Formatter}, 7 ops::IndexMut as _, 8 }; 9 use std::{ 10 ffi::OsString, 11 io::{self, Error, StderrLock, StdoutLock, Write as _}, 12 path::PathBuf, 13 time::Instant, 14 }; 15 /// Unit tests. 16 #[cfg(test)] 17 mod tests; 18 /// Help message. 19 pub(crate) const HELP_MSG: &str = "Continuous integration of all features using cargo 20 21 Usage: ci-cargo [COMMAND] [OPTIONS] [COMMAND] [OPTIONS] [COMMAND] [OPTIONS] 22 23 Commands: 24 check cargo check 25 clippy cargo clippy 26 help This message 27 test cargo test 28 version Prints version info 29 30 Global Options: 31 --allow-implied features Allow implied features from optional dependencies 32 --cargo-home <PATH> Set the storage directory used by cargo 33 --cargo-path <PATH> Set the path cargo is in. Defaults to cargo 34 --color --color always is passed to each command; otherwise --color never is 35 --default-toolchain cargo is invoked as is (i.e., cargo +stable is never used) 36 --dir <PATH> Set the working directory 37 --ignore-compile-errors compile_error!s are ignored 38 --ignore-features <feats> Ignore the provided comma-separated features 39 --ignore-msrv --ignore-rust-version is passed to each command for the default toolchain 40 --progress Writes the progress to stdout 41 --rustup-home <PATH> Set the storage directory used by rustup 42 --skip-msrv cargo +<MSRV> is not used 43 --summary Writes the toolchain(s) and combinations of features used to stdout on success 44 45 Check Options: 46 --all-targets --all-targets is passed 47 --benches --benches is passed 48 --bins --bins is passed 49 --examples --examples is passed 50 --lib --lib is passed 51 --tests --tests is passed 52 53 Clippy Options: 54 --all-targets --all-targets is passed 55 --benches --benches is passed 56 --bins --bins is passed 57 --deny-warnings -- -Dwarnings is passed 58 --examples --examples is passed 59 --lib --lib is passed 60 --tests --tests is passed 61 62 Test Options: 63 --all-targets cargo test --all-targets and cargo test --doc are run 64 --benches --benches is passed 65 --bins --bins is passed 66 --doc --doc is passed 67 --examples --examples is passed 68 --ignored -- --ignored is passed 69 --include-ignored -- --include-ignored is passed 70 --lib --lib is passed 71 --tests --tests is passed 72 73 The following conditions must be met: 74 75 * the help and version commands mustn't be combined with other commands or options 76 * any unique combination of check, clippy, and test can be used 77 * command-specific options must be unique for a given command 78 * global options are allowed after any command but must be unique 79 * command-specific options must follow the command 80 * the test options --ignored and --include-ignored are mutually exclusive 81 * --all-targets mustn't be combined with other targets (e.g., --lib) 82 * the test option --doc mustn't be combined with other targets 83 * --ignore-msrv is not allowed if the stable toolchain is used 84 85 cargo +stable will be used to run the command(s) if all of the following conditions are met: 86 87 * --default-toolchain was not passed 88 * rust-toolchain.toml does not exist in the package directory nor its ancestor directories 89 * --rustup-home was not passed for platforms that don't support rustup 90 91 If the above are not met, cargo will be used instead. cargo +<MSRV> will also be used 92 if all of the following conditions are met: 93 94 * --skip-msrv was not passed 95 * package has an MSRV defined via rust-version that is semantically less than the stable or not 96 equivalent to the default toolchain used 97 * --rustup-home was passed or the platform supports rustup 98 99 For the toolchain(s) used, the command(s) are run for each combination of features sans any provided 100 with --ignore-features. Features provided with --ignore-features must be unique and represent valid 101 features in the package. An empty value is interpreted as the empty set of features. 102 "; 103 /// `"help"`. 104 const HELP: &str = "help"; 105 /// `"version"`. 106 const VERSION: &str = "version"; 107 /// `"check"`. 108 const CHECK: &str = "check"; 109 /// `"clippy"`. 110 const CLIPPY: &str = "clippy"; 111 /// `"test"`. 112 const TEST: &str = "test"; 113 /// `"--all-targets"`. 114 const ALL_TARGETS: &str = "--all-targets"; 115 /// `"--allow-implied-features"`. 116 const ALLOW_IMPLIED_FEATURES: &str = "--allow-implied-features"; 117 /// `"--benches"`. 118 const BENCHES: &str = "--benches"; 119 /// `"--bins"`. 120 const BINS: &str = "--bins"; 121 /// `"--cargo-home"`. 122 const CARGO_HOME: &str = "--cargo-home"; 123 /// `"--cargo-path"`. 124 const CARGO_PATH: &str = "--cargo-path"; 125 /// `"--color"`. 126 const COLOR: &str = "--color"; 127 /// `"--default-toolchain"`. 128 const DEFAULT_TOOLCHAIN: &str = "--default-toolchain"; 129 /// `"--deny-warnings"`. 130 const DENY_WARNINGS: &str = "--deny-warnings"; 131 /// `"--dir"`. 132 const DIR: &str = "--dir"; 133 /// `"--doc"`. 134 const DOC: &str = "--doc"; 135 /// `"--examples"`. 136 const EXAMPLES: &str = "--examples"; 137 /// `"--ignore-compile-errors"`. 138 const IGNORE_COMPILE_ERRORS: &str = "--ignore-compile-errors"; 139 /// `"--ignore-features"`. 140 const IGNORE_FEATURES: &str = "--ignore-features"; 141 /// `"--ignore-msrv"`. 142 const IGNORE_MSRV: &str = "--ignore-msrv"; 143 /// `"--ignored"`. 144 const IGNORED: &str = "--ignored"; 145 /// `"--include-ignored"`. 146 const INCLUDE_IGNORED: &str = "--include-ignored"; 147 /// `"--lib"`. 148 const LIB: &str = "--lib"; 149 /// `"--progress"`. 150 const PROGRESS: &str = "--progress"; 151 /// `"--rustup-home"`. 152 const RUSTUP_HOME: &str = "--rustup-home"; 153 /// `"--skip-msrv"`. 154 const SKIP_MSRV: &str = "--skip-msrv"; 155 /// `"--summary"`. 156 const SUMMARY: &str = "--summary"; 157 /// `"--tests"`. 158 const TESTS: &str = "--tests"; 159 /// `"cargo"`. 160 const CARGO: &str = "cargo"; 161 /// `"test --doc"`. 162 const TEST_DOC: &str = "test --doc"; 163 /// `"test --all-targets"`. 164 const TEST_ALL_TARGETS: &str = "test --all-targets"; 165 /// `"1"`. 166 const ONE: &str = "1"; 167 /// `"2"`. 168 const TWO: &str = "2"; 169 /// `"3"`. 170 const THREE: &str = "3"; 171 /// `"4"`. 172 const FOUR: &str = "4"; 173 /// `"cargo "`. 174 const CARGO_SPACE: &str = "cargo "; 175 /// Error returned when parsing arguments passed to the application. 176 #[cfg_attr(test, derive(Debug, PartialEq))] 177 pub(crate) enum ArgsErr { 178 /// Error when no arguments exist. 179 NoArgs, 180 /// Error when no command was passed. 181 NoCommand, 182 /// Error when an unknown argument is passed. The contained [`OsString`] is the value of the unknown command 183 /// or option. 184 UnknownArg(OsString), 185 /// Error when an option is passed more than once. The contained [`OsString`] is the duplicate argument. 186 DuplicateOption(OsString), 187 /// Error when `help` is passed followed by a non-empty sequence of arguments. 188 HelpWithArgs, 189 /// Error when `version` is passed followed by a non-empty sequence of arguments. 190 VersionWithArgs, 191 /// Error when `--dir` is passed with no file path to the directory `ci-cargo` should run in. 192 MissingDirPath, 193 /// Error when `--cargo-path` is passed with no file path to the directory `cargo` should be located in. 194 MissingCargoPath, 195 /// Error when `--cargo-home` is passed with no file path to the storage directory `cargo` uses. 196 MissingCargoHome, 197 /// Error when `--rustup-home` is passed with no file path to the storage directory `rustup` uses. 198 MissingRustupHome, 199 /// Error when `--all-targets` is passed with other targets. 200 AllTargets, 201 /// Error when `--doc` is passed with other targets with the `test` command. 202 Doc, 203 /// Error when `--ignored` and `--include-ignored` are passed with the `test` command. 204 IgnoredIncludeIgnored, 205 /// Error when `--ignore-features` was not passed any features. 206 /// 207 /// Note to _only_ pass in the empty set in an interactive terminal, 208 /// you likely will need to use quotes. 209 MissingIgnoredFeatures, 210 /// Error when `--ignore-features` is passed duplicate features. 211 DuplicateIgnoredFeatures(OsString), 212 } 213 impl ArgsErr { 214 /// Writes `self` to `stderr`. 215 pub(crate) fn write(self, mut stderr: StderrLock<'_>) -> Result<(), Error> { 216 const FINAL_SENTENCE: &str = " See ci-cargo help for more information."; 217 match self { 218 Self::NoArgs => writeln!( 219 stderr, 220 "No arguments exist including the name of the process itself.{FINAL_SENTENCE}" 221 ), 222 Self::NoCommand => writeln!( 223 stderr, 224 "A command was not passed as the first argument.{FINAL_SENTENCE}" 225 ), 226 Self::UnknownArg(arg) => { 227 writeln!( 228 stderr, 229 "{} is an unknown argument.{FINAL_SENTENCE}", 230 arg.display() 231 ) 232 } 233 Self::DuplicateOption(arg) => { 234 writeln!( 235 stderr, 236 "{} was passed more than once.{FINAL_SENTENCE}", 237 arg.display() 238 ) 239 } 240 Self::HelpWithArgs => { 241 writeln!( 242 stderr, 243 "{HELP} was passed with one or more arguments.{FINAL_SENTENCE}", 244 ) 245 } 246 Self::VersionWithArgs => { 247 writeln!( 248 stderr, 249 "{VERSION} was passed with one or more arguments.{FINAL_SENTENCE}", 250 ) 251 } 252 Self::MissingDirPath => { 253 writeln!( 254 stderr, 255 "{DIR} was passed without a path to the directory ci-cargo should run in.{FINAL_SENTENCE}" 256 ) 257 } 258 Self::MissingCargoPath => { 259 writeln!( 260 stderr, 261 "{CARGO_PATH} was passed without a path to the directory cargo is located in.{FINAL_SENTENCE}" 262 ) 263 } 264 Self::MissingCargoHome => { 265 writeln!( 266 stderr, 267 "{CARGO_HOME} was passed without a path to the cargo storage directory.{FINAL_SENTENCE}" 268 ) 269 } 270 Self::MissingRustupHome => { 271 writeln!( 272 stderr, 273 "{RUSTUP_HOME} was passed without a path to the rustup storage directory.{FINAL_SENTENCE}" 274 ) 275 } 276 Self::AllTargets => { 277 writeln!( 278 stderr, 279 "{ALL_TARGETS} was passed with other targets.{FINAL_SENTENCE}" 280 ) 281 } 282 Self::Doc => { 283 writeln!( 284 stderr, 285 "{DOC} was passed with other targets.{FINAL_SENTENCE}" 286 ) 287 } 288 Self::IgnoredIncludeIgnored => { 289 writeln!( 290 stderr, 291 "{IGNORED} and {INCLUDE_IGNORED} were both passed.{FINAL_SENTENCE}" 292 ) 293 } 294 Self::MissingIgnoredFeatures => { 295 writeln!( 296 stderr, 297 "{IGNORE_FEATURES} was passed without any features to ignore.{FINAL_SENTENCE}" 298 ) 299 } 300 Self::DuplicateIgnoredFeatures(feats) => { 301 writeln!( 302 stderr, 303 "{IGNORE_FEATURES} was passed {} which contains at least one duplicate feature.{FINAL_SENTENCE}", 304 feats.display() 305 ) 306 } 307 } 308 } 309 } 310 /// Options to use for `cargo`. 311 #[expect( 312 clippy::struct_excessive_bools, 313 reason = "not a problem. arguable false positive based on its use" 314 )] 315 #[cfg_attr(test, derive(Debug, PartialEq))] 316 pub(crate) struct Opts { 317 /// The directory to run `ci-cargo` in. 318 pub exec_dir: Option<PathBuf>, 319 /// Storage directory for `rustup`. 320 pub rustup_home: Option<PathBuf>, 321 /// Path to `cargo`. 322 pub cargo_path: PathBuf, 323 /// Storage directory for `cargo`. 324 pub cargo_home: Option<PathBuf>, 325 /// `true` iff color should be outputted. 326 pub color: bool, 327 /// `true` iff `cargo` should be used instead of `cargo +stable`. 328 pub default_toolchain: bool, 329 /// `true` iff implied features should be allowed an tested. 330 pub allow_implied_features: bool, 331 /// `true` iff `compile_error`s should be ignored. 332 pub ignore_compile_errors: bool, 333 /// `true` iff `--ignore-rust-version` should be passed. 334 pub ignore_msrv: bool, 335 /// `true` iff progress should be written to `stdout`. 336 pub progress: bool, 337 /// `true` iff the MSRV toolchain should not be used. 338 pub skip_msrv: bool, 339 /// `true` iff the toolchains used and combinations of features run on should be written 340 /// to `stdout` upon success. 341 pub summary: bool, 342 /// The features to ignore. 343 /// 344 /// Note this is empty iff there are no features to ignore. The contained features 345 /// are distinct. The empty `String` corresponds to the empty set of features to 346 /// ignore (i.e., --no-default-features). 347 pub ignore_features: Vec<String>, 348 } 349 impl Default for Opts { 350 fn default() -> Self { 351 Self { 352 exec_dir: None, 353 rustup_home: None, 354 cargo_path: cargo_path(), 355 cargo_home: None, 356 color: false, 357 default_toolchain: false, 358 allow_implied_features: false, 359 ignore_compile_errors: false, 360 ignore_msrv: false, 361 progress: false, 362 skip_msrv: false, 363 summary: false, 364 ignore_features: Vec::new(), 365 } 366 } 367 } 368 /// Controls if `cargo test -- --ignored` or `cargo test --include-ignored` should be run. 369 #[cfg_attr(test, derive(Debug, PartialEq))] 370 #[derive(Clone, Copy, Default)] 371 pub(crate) enum Ignored { 372 /// Don't run any `ignore` tests. 373 #[default] 374 None, 375 /// Only run `ignore` tests. 376 Only, 377 /// Run all tests. 378 Include, 379 } 380 /// Positive `usize` or `usize::MAX + 1`. 381 /// 382 /// Since we don't use 0, it's repurposed as `usize::MAX + 1`. 383 #[cfg_attr(test, derive(Debug, PartialEq))] 384 #[derive(Clone, Copy)] 385 pub(crate) struct NonZeroUsizePlus1(usize); 386 impl NonZeroUsizePlus1 { 387 /// Returns `Self` containing `val`. 388 /// 389 /// Note calling code must know that `0` is treated liked 390 /// `usize::MAX + 1`. 391 pub(crate) const fn new(val: usize) -> Self { 392 Self(val) 393 } 394 } 395 impl Display for NonZeroUsizePlus1 { 396 #[expect(unsafe_code, reason = "comment justifies correctness")] 397 #[expect( 398 clippy::arithmetic_side_effects, 399 reason = "comment justifies correctness" 400 )] 401 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 402 /// Helper for `unlikely`. 403 #[inline(always)] 404 #[cold] 405 const fn cold_path() {} 406 /// Hint that a branch is unlikely. 407 #[expect( 408 clippy::inline_always, 409 reason = "purpose is for the compiler to not optimize" 410 )] 411 #[inline(always)] 412 const fn unlikely(b: bool) -> bool { 413 if b { 414 cold_path(); 415 true 416 } else { 417 false 418 } 419 } 420 if unlikely(self.0 == 0) { 421 let mut val = usize::MAX.to_string(); 422 // This won't underflow since the length is at least 1. 423 let idx = val.len() - 1; 424 // SAFETY: 425 // 2^n is even for all n != 0. When n = 0, 2^n = 1. This means we can always increment the last 426 // digit without carrying. 427 // We only mutate the last digit which is guaranteed to be valid ASCII; thus we can increment 428 // the `u8` since digits are consecutive in ASCII. 429 *unsafe { val.as_bytes_mut() }.index_mut(idx) += 1; 430 write!(f, "{val}") 431 } else { 432 write!(f, "{}", self.0) 433 } 434 } 435 } 436 /// Progress tracker for when `--progress` was passed. 437 struct Progress<'package, 'toolchain> { 438 /// The name of the package. 439 package: &'package str, 440 /// The current toolchain counter. 441 toolchain_counter: &'static str, 442 /// The total toolchains that will be used. 443 toolchain_total: &'static str, 444 /// `"cargo"` or `"cargo "`. 445 /// 446 /// Exists for consistent formatting with [`Self::toolchain`]. 447 cargo_cmd: &'toolchain str, 448 /// The current toolchain. 449 toolchain: &'toolchain str, 450 /// The current command counter. 451 cmd_counter: &'static str, 452 /// The total commands that will be used. 453 cmd_total: &'static str, 454 /// The current command. 455 cmd: &'static str, 456 /// The total number of features in the power set. 457 features_total: String, 458 /// The time in which we started. 459 time_started: Instant, 460 /// `stdout` stream. 461 /// 462 /// None iff we encountered any error when writing 463 /// to it. 464 stdout: Option<StdoutLock<'static>>, 465 } 466 impl<'package> Progress<'package, '_> { 467 /// Returns `Self` based on running check. 468 fn check( 469 package: &'package str, 470 toolchain: Toolchain<'_>, 471 use_msrv: bool, 472 features_total: NonZeroUsizePlus1, 473 ) -> Self { 474 Self::inner_new(package, CHECK, ONE, toolchain, use_msrv, features_total) 475 } 476 /// Returns `Self` based on running clippy. 477 fn clippy( 478 package: &'package str, 479 toolchain: Toolchain<'_>, 480 use_msrv: bool, 481 features_total: NonZeroUsizePlus1, 482 ) -> Self { 483 Self::inner_new(package, CLIPPY, ONE, toolchain, use_msrv, features_total) 484 } 485 /// Returns `Self` based on running test. 486 fn test( 487 package: &'package str, 488 toolchain: Toolchain<'_>, 489 use_msrv: bool, 490 features_total: NonZeroUsizePlus1, 491 all_targets: bool, 492 ) -> Self { 493 if all_targets { 494 Self::inner_new( 495 package, 496 TEST_ALL_TARGETS, 497 TWO, 498 toolchain, 499 use_msrv, 500 features_total, 501 ) 502 } else { 503 Self::inner_new(package, TEST, ONE, toolchain, use_msrv, features_total) 504 } 505 } 506 /// Returns `Self` based on running both check and clippy. 507 fn check_clippy( 508 package: &'package str, 509 toolchain: Toolchain<'_>, 510 use_msrv: bool, 511 features_total: NonZeroUsizePlus1, 512 ) -> Self { 513 Self::inner_new(package, CHECK, TWO, toolchain, use_msrv, features_total) 514 } 515 /// Returns `Self` based on running both check and test. 516 fn check_test( 517 package: &'package str, 518 toolchain: Toolchain<'_>, 519 use_msrv: bool, 520 features_total: NonZeroUsizePlus1, 521 test_all_targets: bool, 522 ) -> Self { 523 if test_all_targets { 524 Self::inner_new(package, CHECK, THREE, toolchain, use_msrv, features_total) 525 } else { 526 Self::inner_new(package, CHECK, TWO, toolchain, use_msrv, features_total) 527 } 528 } 529 /// Returns `Self` based on running both clippy and test. 530 fn clippy_test( 531 package: &'package str, 532 toolchain: Toolchain<'_>, 533 use_msrv: bool, 534 features_total: NonZeroUsizePlus1, 535 test_all_targets: bool, 536 ) -> Self { 537 if test_all_targets { 538 Self::inner_new(package, CLIPPY, THREE, toolchain, use_msrv, features_total) 539 } else { 540 Self::inner_new(package, CLIPPY, TWO, toolchain, use_msrv, features_total) 541 } 542 } 543 /// Returns `Self` based on running check, clippy, and test. 544 fn check_clippy_test( 545 package: &'package str, 546 toolchain: Toolchain<'_>, 547 use_msrv: bool, 548 features_total: NonZeroUsizePlus1, 549 test_all_targets: bool, 550 ) -> Self { 551 if test_all_targets { 552 Self::inner_new(package, CHECK, FOUR, toolchain, use_msrv, features_total) 553 } else { 554 Self::inner_new(package, CHECK, THREE, toolchain, use_msrv, features_total) 555 } 556 } 557 /// Returns `Self` based on the passed arguments. 558 fn inner_new( 559 package: &'package str, 560 cmd: &'static str, 561 cmd_total: &'static str, 562 tool: Toolchain<'_>, 563 use_msrv: bool, 564 features_total: NonZeroUsizePlus1, 565 ) -> Self { 566 let (cargo_cmd, toolchain) = if matches!(tool, Toolchain::Stable) { 567 (CARGO_SPACE, "+stable") 568 } else { 569 (CARGO, "") 570 }; 571 Self { 572 package, 573 toolchain_counter: ONE, 574 toolchain_total: if use_msrv { TWO } else { ONE }, 575 cargo_cmd, 576 toolchain, 577 cmd_counter: ONE, 578 cmd_total, 579 cmd, 580 features_total: features_total.to_string(), 581 time_started: Instant::now(), 582 stdout: Some(io::stdout().lock()), 583 } 584 } 585 /// Writes the progress so far to `stdout`. 586 /// 587 /// If writing to `stdout` errors, then `stdout` will never be written to. 588 fn write_to_stdout( 589 &mut self, 590 features: &str, 591 features_counter: usize, 592 features_skipped: usize, 593 ) { 594 if let Some(ref mut std) = self.stdout { 595 // Example: 596 // "Package: foo. Toolchain (1/2): cargo +stable. Features (18/128, 3 skipped): foo,bar. Command (1/2): clippy. Time running: 49 s."); 597 // Note `features_skipped` maxes at `usize::MAX` since the empty set is never skipped. 598 if writeln!(std, "Package: {}. Toolchain ({}/{}): {}{}. Features ({}/{}, {} skipped): {}. Command ({}/{}): {}. Time running: {} s.", self.package, self.toolchain_counter, self.toolchain_total, self.cargo_cmd, self.toolchain, NonZeroUsizePlus1(features_counter), self.features_total, features_skipped, if features.is_empty() { "<none>" } else { features }, self.cmd_counter, self.cmd_total, self.cmd, self.time_started.elapsed().as_secs()).is_err() { 599 drop(self.stdout.take()); 600 } 601 } 602 } 603 } 604 /// Target selection. 605 #[cfg_attr(test, derive(Debug, PartialEq))] 606 #[derive(Clone, Copy)] 607 pub(crate) enum Target { 608 /// `--benches`. 609 Benches, 610 /// `--bins`. 611 Bins, 612 /// `--examples`. 613 Examples, 614 /// `--lib`. 615 Lib, 616 /// `--tests`. 617 Tests, 618 } 619 impl Target { 620 /// Transfoms `self` into a `u8`. 621 const fn to_u8(self) -> u8 { 622 match self { 623 Self::Benches => 1, 624 Self::Bins => 2, 625 Self::Examples => 4, 626 Self::Lib => 8, 627 Self::Tests => 16, 628 } 629 } 630 /// Returns `self` as an `OsString`. 631 fn to_os_string(self) -> OsString { 632 match match self { 633 Self::Benches => BENCHES, 634 Self::Bins => BINS, 635 Self::Examples => EXAMPLES, 636 Self::Lib => LIB, 637 Self::Tests => TESTS, 638 } 639 .parse() 640 { 641 Ok(val) => val, 642 Err(e) => match e {}, 643 } 644 } 645 } 646 /// Combination of targets to select. 647 #[cfg_attr(test, derive(Debug, PartialEq))] 648 #[derive(Clone, Copy, Default)] 649 pub(crate) struct Targets(u8); 650 impl Targets { 651 /// Returns `Self` only containing `target`. 652 const fn new(target: Target) -> Self { 653 Self(target.to_u8()) 654 } 655 /// Adds `target` to `self` returning `true` iff `target` wasn't added before. 656 const fn add(&mut self, target: Target) -> bool { 657 let this = self.0 | target.to_u8(); 658 if this == self.0 { 659 false 660 } else { 661 self.0 = this; 662 true 663 } 664 } 665 /// Returns `true` iff `self` contains `target`. 666 pub(super) const fn contains(self, target: Target) -> bool { 667 let val = target.to_u8(); 668 self.0 & val == val 669 } 670 } 671 /// Test targets to select. 672 #[cfg_attr(test, derive(Debug, PartialEq))] 673 #[derive(Clone, Copy)] 674 pub(crate) enum CheckClippyTargets { 675 /// Use the default targets. 676 Default, 677 /// `--all-targets`. 678 All, 679 /// All targets. 680 Targets(Targets), 681 } 682 /// Test targets to select. 683 #[cfg_attr(test, derive(Debug, PartialEq))] 684 #[derive(Clone, Copy)] 685 pub(crate) enum TestTargets { 686 /// Use the default targets. 687 Default, 688 /// `--all-targets`. 689 /// 690 /// This causes all non-`doc` targets to be run and the `--doc` target/mode to be run as well. 691 All, 692 /// Non-`doc` targets. 693 Targets(Targets), 694 /// `--doc`. 695 Doc, 696 } 697 /// `cargo` command(s) we should run. 698 #[cfg_attr(test, derive(Debug, PartialEq))] 699 #[derive(Clone, Copy)] 700 pub(crate) enum Cmd { 701 /// `cargo check`. 702 Check(CheckClippyTargets), 703 /// `cargo clippy`. 704 /// 705 /// The contained `bool` is `true` iff `--deny-warnings` was passed. 706 Clippy(CheckClippyTargets, bool), 707 /// `cargo test`. 708 Test(TestTargets, Ignored), 709 /// [`Self::Check`] and [`Self::Clippy`]. 710 CheckClippy(CheckClippyTargets, CheckClippyTargets, bool), 711 /// [`Self::Check`] and [`Self::Test`]. 712 CheckTest(CheckClippyTargets, TestTargets, Ignored), 713 /// [`Self::Clippy`] and [`Self::Test`]. 714 ClippyTest(CheckClippyTargets, bool, TestTargets, Ignored), 715 /// [`Self::Check`], [`Self::Clippy`], and [`Self::Test`]. 716 CheckClippyTest( 717 CheckClippyTargets, 718 CheckClippyTargets, 719 bool, 720 TestTargets, 721 Ignored, 722 ), 723 } 724 impl Cmd { 725 /// Runs the appropriate `cargo` command(s) for all features in `power_set`. 726 /// 727 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 728 /// later used. 729 #[expect(clippy::too_many_lines, reason = "long match expression")] 730 pub(crate) fn run<'a>( 731 self, 732 options: Options<'a, '_, '_>, 733 msrv: Option<&'a str>, 734 power_set: &mut PowerSet<'_>, 735 progress: bool, 736 ) -> Result<(), Box<CargoErr>> { 737 match self { 738 Self::Check(targets) => Self::run_check( 739 progress.then(|| { 740 Progress::check( 741 options.package_name, 742 options.toolchain, 743 msrv.is_some(), 744 power_set.len(), 745 ) 746 }), 747 msrv, 748 options, 749 targets, 750 power_set, 751 ), 752 Self::Clippy(targets, deny_warnings) => Self::run_clippy( 753 progress.then(|| { 754 Progress::clippy( 755 options.package_name, 756 options.toolchain, 757 msrv.is_some(), 758 power_set.len(), 759 ) 760 }), 761 msrv, 762 options, 763 targets, 764 deny_warnings, 765 power_set, 766 ), 767 Self::Test(targets, ignored) => { 768 if matches!(targets, TestTargets::All) { 769 Self::run_test_all_targets( 770 progress.then(|| { 771 Progress::test( 772 options.package_name, 773 options.toolchain, 774 msrv.is_some(), 775 power_set.len(), 776 true, 777 ) 778 }), 779 msrv, 780 options, 781 ignored, 782 power_set, 783 ) 784 } else { 785 Self::run_test( 786 progress.then(|| { 787 Progress::test( 788 options.package_name, 789 options.toolchain, 790 msrv.is_some(), 791 power_set.len(), 792 false, 793 ) 794 }), 795 msrv, 796 options, 797 targets, 798 ignored, 799 power_set, 800 ) 801 } 802 } 803 Self::CheckClippy(check_targets, clippy_targets, deny_warnings) => { 804 Self::run_check_clippy( 805 progress.then(|| { 806 Progress::check_clippy( 807 options.package_name, 808 options.toolchain, 809 msrv.is_some(), 810 power_set.len(), 811 ) 812 }), 813 msrv, 814 options, 815 check_targets, 816 (clippy_targets, deny_warnings), 817 power_set, 818 ) 819 } 820 Self::CheckTest(check_targets, test_targets, ignored) => { 821 if matches!(test_targets, TestTargets::All) { 822 Self::run_check_test_all_targets( 823 progress.then(|| { 824 Progress::check_test( 825 options.package_name, 826 options.toolchain, 827 msrv.is_some(), 828 power_set.len(), 829 true, 830 ) 831 }), 832 msrv, 833 options, 834 check_targets, 835 ignored, 836 power_set, 837 ) 838 } else { 839 Self::run_check_test( 840 progress.then(|| { 841 Progress::check_test( 842 options.package_name, 843 options.toolchain, 844 msrv.is_some(), 845 power_set.len(), 846 false, 847 ) 848 }), 849 msrv, 850 options, 851 check_targets, 852 (test_targets, ignored), 853 power_set, 854 ) 855 } 856 } 857 Self::ClippyTest(clippy_targets, deny_warnings, test_targets, ignored) => { 858 if matches!(test_targets, TestTargets::All) { 859 Self::run_clippy_test_all_targets( 860 progress.then(|| { 861 Progress::clippy_test( 862 options.package_name, 863 options.toolchain, 864 msrv.is_some(), 865 power_set.len(), 866 true, 867 ) 868 }), 869 msrv, 870 options, 871 (clippy_targets, deny_warnings), 872 ignored, 873 power_set, 874 ) 875 } else { 876 Self::run_clippy_test( 877 progress.then(|| { 878 Progress::clippy_test( 879 options.package_name, 880 options.toolchain, 881 msrv.is_some(), 882 power_set.len(), 883 false, 884 ) 885 }), 886 msrv, 887 options, 888 (clippy_targets, deny_warnings), 889 (test_targets, ignored), 890 power_set, 891 ) 892 } 893 } 894 Self::CheckClippyTest( 895 check_targets, 896 clippy_targets, 897 deny_warnings, 898 test_targets, 899 ignored, 900 ) => { 901 if matches!(test_targets, TestTargets::All) { 902 Self::run_check_clippy_test_all_targets( 903 progress.then(|| { 904 Progress::check_clippy_test( 905 options.package_name, 906 options.toolchain, 907 msrv.is_some(), 908 power_set.len(), 909 true, 910 ) 911 }), 912 msrv, 913 options, 914 check_targets, 915 (clippy_targets, deny_warnings), 916 ignored, 917 power_set, 918 ) 919 } else { 920 Self::run_check_clippy_test( 921 progress.then(|| { 922 Progress::check_clippy_test( 923 options.package_name, 924 options.toolchain, 925 msrv.is_some(), 926 power_set.len(), 927 false, 928 ) 929 }), 930 msrv, 931 options, 932 check_targets, 933 (clippy_targets, deny_warnings), 934 (test_targets, ignored), 935 power_set, 936 ) 937 } 938 } 939 } 940 } 941 /// Runs `cargo check` for all features in `power_set`. 942 /// 943 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 944 /// later used. 945 fn run_check<'a>( 946 mut progress: Option<Progress<'_, 'a>>, 947 msrv: Option<&'a str>, 948 mut options: Options<'a, '_, '_>, 949 targets: CheckClippyTargets, 950 power_set: &mut PowerSet<'_>, 951 ) -> Result<(), Box<CargoErr>> { 952 /// Runs the commands for each feature combination. 953 fn run_loop<'a>( 954 progress: &mut Option<Progress<'_, 'a>>, 955 options: &mut Options<'a, '_, '_>, 956 targets: CheckClippyTargets, 957 power_set: &mut PowerSet<'_>, 958 ) -> Result<(), Box<CargoErr>> { 959 let mut feat_counter = 1usize; 960 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 961 // cause some bloat though. 962 if let Some(ref mut prog) = *progress { 963 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 964 prog.cmd_counter = ONE; 965 prog.cmd = CHECK; 966 prog.write_to_stdout(set, feat_counter, skip_count); 967 Check::run(options, targets, set)?; 968 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 969 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 970 // [`NonZeroUsizePlus1::fmt`]. 971 feat_counter = feat_counter.wrapping_add(1); 972 } 973 } else { 974 while let Some(set) = power_set.next_set() { 975 Check::run(options, targets, set)?; 976 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 977 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 978 // [`NonZeroUsizePlus1::fmt`]. 979 feat_counter = feat_counter.wrapping_add(1); 980 } 981 } 982 Ok(()) 983 } 984 run_loop(&mut progress, &mut options, targets, power_set).and_then(|()| { 985 if let Some(msrv_val) = msrv { 986 if let Some(ref mut prog) = progress { 987 prog.toolchain_counter = TWO; 988 prog.cargo_cmd = CARGO_SPACE; 989 prog.toolchain = msrv_val; 990 } 991 options.toolchain = Toolchain::Msrv(msrv_val); 992 power_set.reset(); 993 run_loop(&mut progress, &mut options, targets, power_set) 994 } else { 995 Ok(()) 996 } 997 }) 998 } 999 /// Runs `cargo clippy` for all features in `power_set`. 1000 /// 1001 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1002 /// later used. 1003 fn run_clippy<'a>( 1004 mut progress: Option<Progress<'_, 'a>>, 1005 msrv: Option<&'a str>, 1006 mut options: Options<'a, '_, '_>, 1007 targets: CheckClippyTargets, 1008 deny_warnings: bool, 1009 power_set: &mut PowerSet<'_>, 1010 ) -> Result<(), Box<CargoErr>> { 1011 /// Runs the commands for each feature combination. 1012 fn run_loop<'a>( 1013 progress: &mut Option<Progress<'_, 'a>>, 1014 options: &mut Options<'a, '_, '_>, 1015 targets: CheckClippyTargets, 1016 deny_warnings: bool, 1017 power_set: &mut PowerSet<'_>, 1018 ) -> Result<(), Box<CargoErr>> { 1019 let mut feat_counter = 1usize; 1020 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1021 // cause some bloat though. 1022 if let Some(ref mut prog) = *progress { 1023 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1024 prog.cmd_counter = ONE; 1025 prog.cmd = CLIPPY; 1026 prog.write_to_stdout(set, feat_counter, skip_count); 1027 Clippy::run(options, targets, deny_warnings, set)?; 1028 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1029 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1030 // [`NonZeroUsizePlus1::fmt`]. 1031 feat_counter = feat_counter.wrapping_add(1); 1032 } 1033 } else { 1034 while let Some(set) = power_set.next_set() { 1035 Clippy::run(options, targets, deny_warnings, set)?; 1036 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1037 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1038 // [`NonZeroUsizePlus1::fmt`]. 1039 feat_counter = feat_counter.wrapping_add(1); 1040 } 1041 } 1042 Ok(()) 1043 } 1044 run_loop( 1045 &mut progress, 1046 &mut options, 1047 targets, 1048 deny_warnings, 1049 power_set, 1050 ) 1051 .and_then(|()| { 1052 if let Some(msrv_val) = msrv { 1053 if let Some(ref mut prog) = progress { 1054 prog.toolchain_counter = TWO; 1055 prog.cargo_cmd = CARGO_SPACE; 1056 prog.toolchain = msrv_val; 1057 } 1058 options.toolchain = Toolchain::Msrv(msrv_val); 1059 power_set.reset(); 1060 run_loop( 1061 &mut progress, 1062 &mut options, 1063 targets, 1064 deny_warnings, 1065 power_set, 1066 ) 1067 } else { 1068 Ok(()) 1069 } 1070 }) 1071 } 1072 /// Runs `cargo test` for all features in `power_set`. 1073 /// 1074 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1075 /// later used. 1076 fn run_test<'a>( 1077 mut progress: Option<Progress<'_, 'a>>, 1078 msrv: Option<&'a str>, 1079 mut options: Options<'a, '_, '_>, 1080 targets: TestTargets, 1081 ignored: Ignored, 1082 power_set: &mut PowerSet<'_>, 1083 ) -> Result<(), Box<CargoErr>> { 1084 /// Runs the commands for each feature combination. 1085 fn run_loop<'a>( 1086 progress: &mut Option<Progress<'_, 'a>>, 1087 options: &mut Options<'a, '_, '_>, 1088 targets: TestTargets, 1089 ignored: Ignored, 1090 power_set: &mut PowerSet<'_>, 1091 ) -> Result<(), Box<CargoErr>> { 1092 let mut feat_counter = 1usize; 1093 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1094 // cause some bloat though. 1095 if let Some(ref mut prog) = *progress { 1096 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1097 prog.cmd_counter = ONE; 1098 prog.cmd = TEST; 1099 prog.write_to_stdout(set, feat_counter, skip_count); 1100 Test::run(options, targets, ignored, set)?; 1101 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1102 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1103 // [`NonZeroUsizePlus1::fmt`]. 1104 feat_counter = feat_counter.wrapping_add(1); 1105 } 1106 } else { 1107 while let Some(set) = power_set.next_set() { 1108 Test::run(options, targets, ignored, set)?; 1109 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1110 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1111 // [`NonZeroUsizePlus1::fmt`]. 1112 feat_counter = feat_counter.wrapping_add(1); 1113 } 1114 } 1115 Ok(()) 1116 } 1117 run_loop(&mut progress, &mut options, targets, ignored, power_set).and_then(|()| { 1118 if let Some(msrv_val) = msrv { 1119 if let Some(ref mut prog) = progress { 1120 prog.toolchain_counter = TWO; 1121 prog.cargo_cmd = CARGO_SPACE; 1122 prog.toolchain = msrv_val; 1123 } 1124 options.toolchain = Toolchain::Msrv(msrv_val); 1125 power_set.reset(); 1126 run_loop(&mut progress, &mut options, targets, ignored, power_set) 1127 } else { 1128 Ok(()) 1129 } 1130 }) 1131 } 1132 /// Runs `cargo test --all-targets` and `cargo test --doc` for all features in `power_set`. 1133 /// 1134 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1135 /// later used. 1136 fn run_test_all_targets<'a>( 1137 mut progress: Option<Progress<'_, 'a>>, 1138 msrv: Option<&'a str>, 1139 mut options: Options<'a, '_, '_>, 1140 ignored: Ignored, 1141 power_set: &mut PowerSet<'_>, 1142 ) -> Result<(), Box<CargoErr>> { 1143 /// Runs the commands for each feature combination. 1144 fn run_loop<'a>( 1145 progress: &mut Option<Progress<'_, 'a>>, 1146 options: &mut Options<'a, '_, '_>, 1147 ignored: Ignored, 1148 power_set: &mut PowerSet<'_>, 1149 ) -> Result<(), Box<CargoErr>> { 1150 let mut feat_counter = 1usize; 1151 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1152 // cause some bloat though. 1153 if let Some(ref mut prog) = *progress { 1154 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1155 prog.cmd_counter = ONE; 1156 prog.cmd = TEST_ALL_TARGETS; 1157 prog.write_to_stdout(set, feat_counter, skip_count); 1158 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1159 prog.cmd_counter = TWO; 1160 prog.cmd = TEST_DOC; 1161 prog.write_to_stdout(set, feat_counter, skip_count); 1162 Test::run(options, TestTargets::Doc, ignored, set) 1163 })?; 1164 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1165 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1166 // [`NonZeroUsizePlus1::fmt`]. 1167 feat_counter = feat_counter.wrapping_add(1); 1168 } 1169 } else { 1170 while let Some(set) = power_set.next_set() { 1171 Test::run(options, TestTargets::All, ignored, set) 1172 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set))?; 1173 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1174 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1175 // [`NonZeroUsizePlus1::fmt`]. 1176 feat_counter = feat_counter.wrapping_add(1); 1177 } 1178 } 1179 Ok(()) 1180 } 1181 run_loop(&mut progress, &mut options, ignored, power_set).and_then(|()| { 1182 if let Some(msrv_val) = msrv { 1183 if let Some(ref mut prog) = progress { 1184 prog.toolchain_counter = TWO; 1185 prog.cargo_cmd = CARGO_SPACE; 1186 prog.toolchain = msrv_val; 1187 } 1188 options.toolchain = Toolchain::Msrv(msrv_val); 1189 power_set.reset(); 1190 run_loop(&mut progress, &mut options, ignored, power_set) 1191 } else { 1192 Ok(()) 1193 } 1194 }) 1195 } 1196 /// Runs `cargo check` and `cargo clippy` for all features in `power_set`. 1197 /// 1198 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1199 /// later used. 1200 fn run_check_clippy<'a>( 1201 mut progress: Option<Progress<'_, 'a>>, 1202 msrv: Option<&'a str>, 1203 mut options: Options<'a, '_, '_>, 1204 check_targets: CheckClippyTargets, 1205 clippy_info: (CheckClippyTargets, bool), 1206 power_set: &mut PowerSet<'_>, 1207 ) -> Result<(), Box<CargoErr>> { 1208 /// Runs the commands for each feature combination. 1209 fn run_loop<'a>( 1210 progress: &mut Option<Progress<'_, 'a>>, 1211 options: &mut Options<'a, '_, '_>, 1212 check_targets: CheckClippyTargets, 1213 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1214 power_set: &mut PowerSet<'_>, 1215 ) -> Result<(), Box<CargoErr>> { 1216 let mut feat_counter = 1usize; 1217 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1218 // cause some bloat though. 1219 if let Some(ref mut prog) = *progress { 1220 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1221 prog.cmd_counter = ONE; 1222 prog.cmd = CHECK; 1223 prog.write_to_stdout(set, feat_counter, skip_count); 1224 Check::run(options, check_targets, set).and_then(|()| { 1225 prog.cmd_counter = TWO; 1226 prog.cmd = CLIPPY; 1227 prog.write_to_stdout(set, feat_counter, skip_count); 1228 Clippy::run(options, clippy_targets, deny_warnings, set) 1229 })?; 1230 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1231 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1232 // [`NonZeroUsizePlus1::fmt`]. 1233 feat_counter = feat_counter.wrapping_add(1); 1234 } 1235 } else { 1236 while let Some(set) = power_set.next_set() { 1237 Check::run(options, check_targets, set) 1238 .and_then(|()| Clippy::run(options, clippy_targets, deny_warnings, set))?; 1239 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1240 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1241 // [`NonZeroUsizePlus1::fmt`]. 1242 feat_counter = feat_counter.wrapping_add(1); 1243 } 1244 } 1245 Ok(()) 1246 } 1247 run_loop( 1248 &mut progress, 1249 &mut options, 1250 check_targets, 1251 clippy_info, 1252 power_set, 1253 ) 1254 .and_then(|()| { 1255 if let Some(msrv_val) = msrv { 1256 if let Some(ref mut prog) = progress { 1257 prog.toolchain_counter = TWO; 1258 prog.cargo_cmd = CARGO_SPACE; 1259 prog.toolchain = msrv_val; 1260 } 1261 options.toolchain = Toolchain::Msrv(msrv_val); 1262 power_set.reset(); 1263 run_loop( 1264 &mut progress, 1265 &mut options, 1266 check_targets, 1267 clippy_info, 1268 power_set, 1269 ) 1270 } else { 1271 Ok(()) 1272 } 1273 }) 1274 } 1275 /// Runs `cargo check` and `cargo test` for all features in `power_set`. 1276 /// 1277 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1278 /// later used. 1279 fn run_check_test<'a>( 1280 mut progress: Option<Progress<'_, 'a>>, 1281 msrv: Option<&'a str>, 1282 mut options: Options<'a, '_, '_>, 1283 check_targets: CheckClippyTargets, 1284 test_info: (TestTargets, Ignored), 1285 power_set: &mut PowerSet<'_>, 1286 ) -> Result<(), Box<CargoErr>> { 1287 /// Runs the commands for each feature combination. 1288 fn run_loop<'a>( 1289 progress: &mut Option<Progress<'_, 'a>>, 1290 options: &mut Options<'a, '_, '_>, 1291 check_targets: CheckClippyTargets, 1292 (test_targets, ignored): (TestTargets, Ignored), 1293 power_set: &mut PowerSet<'_>, 1294 ) -> Result<(), Box<CargoErr>> { 1295 let mut feat_counter = 1usize; 1296 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1297 // cause some bloat though. 1298 if let Some(ref mut prog) = *progress { 1299 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1300 prog.cmd_counter = ONE; 1301 prog.cmd = CHECK; 1302 prog.write_to_stdout(set, feat_counter, skip_count); 1303 Check::run(options, check_targets, set).and_then(|()| { 1304 prog.cmd_counter = TWO; 1305 prog.cmd = TEST; 1306 prog.write_to_stdout(set, feat_counter, skip_count); 1307 Test::run(options, test_targets, ignored, set) 1308 })?; 1309 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1310 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1311 // [`NonZeroUsizePlus1::fmt`]. 1312 feat_counter = feat_counter.wrapping_add(1); 1313 } 1314 } else { 1315 while let Some(set) = power_set.next_set() { 1316 Check::run(options, check_targets, set) 1317 .and_then(|()| Test::run(options, test_targets, ignored, set))?; 1318 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1319 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1320 // [`NonZeroUsizePlus1::fmt`]. 1321 feat_counter = feat_counter.wrapping_add(1); 1322 } 1323 } 1324 Ok(()) 1325 } 1326 run_loop( 1327 &mut progress, 1328 &mut options, 1329 check_targets, 1330 test_info, 1331 power_set, 1332 ) 1333 .and_then(|()| { 1334 if let Some(msrv_val) = msrv { 1335 if let Some(ref mut prog) = progress { 1336 prog.toolchain_counter = TWO; 1337 prog.cargo_cmd = CARGO_SPACE; 1338 prog.toolchain = msrv_val; 1339 } 1340 options.toolchain = Toolchain::Msrv(msrv_val); 1341 power_set.reset(); 1342 run_loop( 1343 &mut progress, 1344 &mut options, 1345 check_targets, 1346 test_info, 1347 power_set, 1348 ) 1349 } else { 1350 Ok(()) 1351 } 1352 }) 1353 } 1354 /// Runs `cargo check`, `cargo test --all-targets`, and `cargo test --doc` for all features in `power_set`. 1355 /// 1356 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1357 /// later used. 1358 fn run_check_test_all_targets<'a>( 1359 mut progress: Option<Progress<'_, 'a>>, 1360 msrv: Option<&'a str>, 1361 mut options: Options<'a, '_, '_>, 1362 check_targets: CheckClippyTargets, 1363 ignored: Ignored, 1364 power_set: &mut PowerSet<'_>, 1365 ) -> Result<(), Box<CargoErr>> { 1366 /// Runs the commands for each feature combination. 1367 fn run_loop<'a>( 1368 progress: &mut Option<Progress<'_, 'a>>, 1369 options: &mut Options<'a, '_, '_>, 1370 check_targets: CheckClippyTargets, 1371 ignored: Ignored, 1372 power_set: &mut PowerSet<'_>, 1373 ) -> Result<(), Box<CargoErr>> { 1374 let mut feat_counter = 1usize; 1375 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1376 // cause some bloat though. 1377 if let Some(ref mut prog) = *progress { 1378 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1379 prog.cmd_counter = ONE; 1380 prog.cmd = CHECK; 1381 prog.write_to_stdout(set, feat_counter, skip_count); 1382 Check::run(options, check_targets, set).and_then(|()| { 1383 prog.cmd_counter = TWO; 1384 prog.cmd = TEST_ALL_TARGETS; 1385 prog.write_to_stdout(set, feat_counter, skip_count); 1386 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1387 prog.cmd_counter = THREE; 1388 prog.cmd = TEST_DOC; 1389 prog.write_to_stdout(set, feat_counter, skip_count); 1390 Test::run(options, TestTargets::Doc, ignored, set) 1391 }) 1392 })?; 1393 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1394 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1395 // [`NonZeroUsizePlus1::fmt`]. 1396 feat_counter = feat_counter.wrapping_add(1); 1397 } 1398 } else { 1399 while let Some(set) = power_set.next_set() { 1400 Check::run(options, check_targets, set).and_then(|()| { 1401 Test::run(options, TestTargets::All, ignored, set) 1402 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set)) 1403 })?; 1404 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1405 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1406 // [`NonZeroUsizePlus1::fmt`]. 1407 feat_counter = feat_counter.wrapping_add(1); 1408 } 1409 } 1410 Ok(()) 1411 } 1412 run_loop( 1413 &mut progress, 1414 &mut options, 1415 check_targets, 1416 ignored, 1417 power_set, 1418 ) 1419 .and_then(|()| { 1420 if let Some(msrv_val) = msrv { 1421 if let Some(ref mut prog) = progress { 1422 prog.toolchain_counter = TWO; 1423 prog.cargo_cmd = CARGO_SPACE; 1424 prog.toolchain = msrv_val; 1425 } 1426 options.toolchain = Toolchain::Msrv(msrv_val); 1427 power_set.reset(); 1428 run_loop( 1429 &mut progress, 1430 &mut options, 1431 check_targets, 1432 ignored, 1433 power_set, 1434 ) 1435 } else { 1436 Ok(()) 1437 } 1438 }) 1439 } 1440 /// Runs `cargo clippy` and `cargo test` for all features in `power_set`. 1441 /// 1442 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1443 /// later used. 1444 fn run_clippy_test<'a>( 1445 mut progress: Option<Progress<'_, 'a>>, 1446 msrv: Option<&'a str>, 1447 mut options: Options<'a, '_, '_>, 1448 clippy_info: (CheckClippyTargets, bool), 1449 test_info: (TestTargets, Ignored), 1450 power_set: &mut PowerSet<'_>, 1451 ) -> Result<(), Box<CargoErr>> { 1452 /// Runs the commands for each feature combination. 1453 fn run_loop<'a>( 1454 progress: &mut Option<Progress<'_, 'a>>, 1455 options: &mut Options<'a, '_, '_>, 1456 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1457 (test_targets, ignored): (TestTargets, Ignored), 1458 power_set: &mut PowerSet<'_>, 1459 ) -> Result<(), Box<CargoErr>> { 1460 let mut feat_counter = 1usize; 1461 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1462 // cause some bloat though. 1463 if let Some(ref mut prog) = *progress { 1464 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1465 prog.cmd_counter = ONE; 1466 prog.cmd = CLIPPY; 1467 prog.write_to_stdout(set, feat_counter, skip_count); 1468 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1469 prog.cmd_counter = TWO; 1470 prog.cmd = TEST; 1471 prog.write_to_stdout(set, feat_counter, skip_count); 1472 Test::run(options, test_targets, ignored, set) 1473 })?; 1474 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1475 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1476 // [`NonZeroUsizePlus1::fmt`]. 1477 feat_counter = feat_counter.wrapping_add(1); 1478 } 1479 } else { 1480 while let Some(set) = power_set.next_set() { 1481 Clippy::run(options, clippy_targets, deny_warnings, set) 1482 .and_then(|()| Test::run(options, test_targets, ignored, set))?; 1483 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1484 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1485 // [`NonZeroUsizePlus1::fmt`]. 1486 feat_counter = feat_counter.wrapping_add(1); 1487 } 1488 } 1489 Ok(()) 1490 } 1491 run_loop( 1492 &mut progress, 1493 &mut options, 1494 clippy_info, 1495 test_info, 1496 power_set, 1497 ) 1498 .and_then(|()| { 1499 if let Some(msrv_val) = msrv { 1500 if let Some(ref mut prog) = progress { 1501 prog.toolchain_counter = TWO; 1502 prog.cargo_cmd = CARGO_SPACE; 1503 prog.toolchain = msrv_val; 1504 } 1505 options.toolchain = Toolchain::Msrv(msrv_val); 1506 power_set.reset(); 1507 run_loop( 1508 &mut progress, 1509 &mut options, 1510 clippy_info, 1511 test_info, 1512 power_set, 1513 ) 1514 } else { 1515 Ok(()) 1516 } 1517 }) 1518 } 1519 /// Runs `cargo clippy`, `cargo test --all-targets`, and `cargo test --doc` for all features in `power_set`. 1520 /// 1521 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1522 /// later used. 1523 fn run_clippy_test_all_targets<'a>( 1524 mut progress: Option<Progress<'_, 'a>>, 1525 msrv: Option<&'a str>, 1526 mut options: Options<'a, '_, '_>, 1527 clippy_info: (CheckClippyTargets, bool), 1528 ignored: Ignored, 1529 power_set: &mut PowerSet<'_>, 1530 ) -> Result<(), Box<CargoErr>> { 1531 /// Runs the commands for each feature combination. 1532 fn run_loop<'a>( 1533 progress: &mut Option<Progress<'_, 'a>>, 1534 options: &mut Options<'a, '_, '_>, 1535 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1536 ignored: Ignored, 1537 power_set: &mut PowerSet<'_>, 1538 ) -> Result<(), Box<CargoErr>> { 1539 let mut feat_counter = 1usize; 1540 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1541 // cause some bloat though. 1542 if let Some(ref mut prog) = *progress { 1543 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1544 prog.cmd_counter = ONE; 1545 prog.cmd = CLIPPY; 1546 prog.write_to_stdout(set, feat_counter, skip_count); 1547 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1548 prog.cmd_counter = TWO; 1549 prog.cmd = TEST_ALL_TARGETS; 1550 prog.write_to_stdout(set, feat_counter, skip_count); 1551 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1552 prog.cmd_counter = THREE; 1553 prog.cmd = TEST_DOC; 1554 prog.write_to_stdout(set, feat_counter, skip_count); 1555 Test::run(options, TestTargets::Doc, ignored, set) 1556 }) 1557 })?; 1558 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1559 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1560 // [`NonZeroUsizePlus1::fmt`]. 1561 feat_counter = feat_counter.wrapping_add(1); 1562 } 1563 } else { 1564 while let Some(set) = power_set.next_set() { 1565 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1566 Test::run(options, TestTargets::All, ignored, set) 1567 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set)) 1568 })?; 1569 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1570 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1571 // [`NonZeroUsizePlus1::fmt`]. 1572 feat_counter = feat_counter.wrapping_add(1); 1573 } 1574 } 1575 Ok(()) 1576 } 1577 run_loop(&mut progress, &mut options, clippy_info, ignored, power_set).and_then(|()| { 1578 if let Some(msrv_val) = msrv { 1579 if let Some(ref mut prog) = progress { 1580 prog.toolchain_counter = TWO; 1581 prog.cargo_cmd = CARGO_SPACE; 1582 prog.toolchain = msrv_val; 1583 } 1584 options.toolchain = Toolchain::Msrv(msrv_val); 1585 power_set.reset(); 1586 run_loop(&mut progress, &mut options, clippy_info, ignored, power_set) 1587 } else { 1588 Ok(()) 1589 } 1590 }) 1591 } 1592 /// Runs `cargo check`, `cargo clippy`, and `cargo test` for all features in `power_set`. 1593 /// 1594 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1595 /// later used. 1596 fn run_check_clippy_test<'a>( 1597 mut progress: Option<Progress<'_, 'a>>, 1598 msrv: Option<&'a str>, 1599 mut options: Options<'a, '_, '_>, 1600 check_targets: CheckClippyTargets, 1601 clippy_info: (CheckClippyTargets, bool), 1602 test_info: (TestTargets, Ignored), 1603 power_set: &mut PowerSet<'_>, 1604 ) -> Result<(), Box<CargoErr>> { 1605 /// Runs the commands for each feature combination. 1606 fn run_loop<'a>( 1607 progress: &mut Option<Progress<'_, 'a>>, 1608 options: &mut Options<'a, '_, '_>, 1609 check_targets: CheckClippyTargets, 1610 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1611 (test_targets, ignored): (TestTargets, Ignored), 1612 power_set: &mut PowerSet<'_>, 1613 ) -> Result<(), Box<CargoErr>> { 1614 let mut feat_counter = 1usize; 1615 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1616 // cause some bloat though. 1617 if let Some(ref mut prog) = *progress { 1618 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1619 prog.cmd_counter = ONE; 1620 prog.cmd = CHECK; 1621 prog.write_to_stdout(set, feat_counter, skip_count); 1622 Check::run(options, check_targets, set).and_then(|()| { 1623 prog.cmd_counter = TWO; 1624 prog.cmd = CLIPPY; 1625 prog.write_to_stdout(set, feat_counter, skip_count); 1626 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1627 prog.cmd_counter = THREE; 1628 prog.cmd = TEST; 1629 prog.write_to_stdout(set, feat_counter, skip_count); 1630 Test::run(options, test_targets, ignored, set) 1631 }) 1632 })?; 1633 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1634 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1635 // [`NonZeroUsizePlus1::fmt`]. 1636 feat_counter = feat_counter.wrapping_add(1); 1637 } 1638 } else { 1639 while let Some(set) = power_set.next_set() { 1640 Check::run(options, check_targets, set).and_then(|()| { 1641 Clippy::run(options, clippy_targets, deny_warnings, set) 1642 .and_then(|()| Test::run(options, test_targets, ignored, set)) 1643 })?; 1644 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1645 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1646 // [`NonZeroUsizePlus1::fmt`]. 1647 feat_counter = feat_counter.wrapping_add(1); 1648 } 1649 } 1650 Ok(()) 1651 } 1652 run_loop( 1653 &mut progress, 1654 &mut options, 1655 check_targets, 1656 clippy_info, 1657 test_info, 1658 power_set, 1659 ) 1660 .and_then(|()| { 1661 if let Some(msrv_val) = msrv { 1662 if let Some(ref mut prog) = progress { 1663 prog.toolchain_counter = TWO; 1664 prog.cargo_cmd = CARGO_SPACE; 1665 prog.toolchain = msrv_val; 1666 } 1667 options.toolchain = Toolchain::Msrv(msrv_val); 1668 power_set.reset(); 1669 run_loop( 1670 &mut progress, 1671 &mut options, 1672 check_targets, 1673 clippy_info, 1674 test_info, 1675 power_set, 1676 ) 1677 } else { 1678 Ok(()) 1679 } 1680 }) 1681 } 1682 /// Runs `cargo check`, `cargo clippy`, `cargo test --all-targets`, and `cargo test --doc` for all features 1683 /// in `power_set`. 1684 /// 1685 /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is 1686 /// later used. 1687 fn run_check_clippy_test_all_targets<'a>( 1688 mut progress: Option<Progress<'_, 'a>>, 1689 msrv: Option<&'a str>, 1690 mut options: Options<'a, '_, '_>, 1691 check_targets: CheckClippyTargets, 1692 clippy_info: (CheckClippyTargets, bool), 1693 ignored: Ignored, 1694 power_set: &mut PowerSet<'_>, 1695 ) -> Result<(), Box<CargoErr>> { 1696 /// Runs the commands for each feature combination. 1697 fn run_loop<'a>( 1698 progress: &mut Option<Progress<'_, 'a>>, 1699 options: &mut Options<'a, '_, '_>, 1700 check_targets: CheckClippyTargets, 1701 (clippy_targets, deny_warnings): (CheckClippyTargets, bool), 1702 ignored: Ignored, 1703 power_set: &mut PowerSet<'_>, 1704 ) -> Result<(), Box<CargoErr>> { 1705 let mut feat_counter = 1usize; 1706 // We have two loops instead of one to avoid repeated and unnecessary if branches. This does 1707 // cause some bloat though. 1708 if let Some(ref mut prog) = *progress { 1709 while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { 1710 prog.cmd_counter = ONE; 1711 prog.cmd = CHECK; 1712 prog.write_to_stdout(set, feat_counter, skip_count); 1713 Check::run(options, check_targets, set).and_then(|()| { 1714 prog.cmd_counter = TWO; 1715 prog.cmd = CLIPPY; 1716 prog.write_to_stdout(set, feat_counter, skip_count); 1717 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1718 prog.cmd_counter = THREE; 1719 prog.cmd = TEST_ALL_TARGETS; 1720 prog.write_to_stdout(set, feat_counter, skip_count); 1721 Test::run(options, TestTargets::All, ignored, set).and_then(|()| { 1722 prog.cmd_counter = FOUR; 1723 prog.cmd = TEST_DOC; 1724 prog.write_to_stdout(set, feat_counter, skip_count); 1725 Test::run(options, TestTargets::Doc, ignored, set) 1726 }) 1727 }) 1728 })?; 1729 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1730 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1731 // [`NonZeroUsizePlus1::fmt`]. 1732 feat_counter = feat_counter.wrapping_add(1); 1733 } 1734 } else { 1735 while let Some(set) = power_set.next_set() { 1736 Check::run(options, check_targets, set).and_then(|()| { 1737 Clippy::run(options, clippy_targets, deny_warnings, set).and_then(|()| { 1738 Test::run(options, TestTargets::All, ignored, set) 1739 .and_then(|()| Test::run(options, TestTargets::Doc, ignored, set)) 1740 }) 1741 })?; 1742 // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very 1743 // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via 1744 // [`NonZeroUsizePlus1::fmt`]. 1745 feat_counter = feat_counter.wrapping_add(1); 1746 } 1747 } 1748 Ok(()) 1749 } 1750 run_loop( 1751 &mut progress, 1752 &mut options, 1753 check_targets, 1754 clippy_info, 1755 ignored, 1756 power_set, 1757 ) 1758 .and_then(|()| { 1759 if let Some(msrv_val) = msrv { 1760 if let Some(ref mut prog) = progress { 1761 prog.toolchain_counter = TWO; 1762 prog.cargo_cmd = CARGO_SPACE; 1763 prog.toolchain = msrv_val; 1764 } 1765 options.toolchain = Toolchain::Msrv(msrv_val); 1766 power_set.reset(); 1767 run_loop( 1768 &mut progress, 1769 &mut options, 1770 check_targets, 1771 clippy_info, 1772 ignored, 1773 power_set, 1774 ) 1775 } else { 1776 Ok(()) 1777 } 1778 }) 1779 } 1780 } 1781 /// `cargo` command passed. 1782 enum CargoCmd<'a> { 1783 /// check. 1784 Check(&'a mut CheckClippyTargets), 1785 /// clippy. 1786 Clippy(&'a mut (CheckClippyTargets, bool)), 1787 /// test. 1788 Test(&'a mut (TestTargets, Ignored)), 1789 } 1790 impl CargoCmd<'_> { 1791 /// Adds the `--all-targets` target to `self`. 1792 fn add_all_targets(&mut self) -> Result<(), ArgsErr> { 1793 match *self { 1794 Self::Check(ref mut check_targets) => match **check_targets { 1795 CheckClippyTargets::Default => { 1796 **check_targets = CheckClippyTargets::All; 1797 Ok(()) 1798 } 1799 CheckClippyTargets::All => { 1800 Err(ArgsErr::DuplicateOption(match ALL_TARGETS.parse() { 1801 Ok(arg) => arg, 1802 Err(e) => match e {}, 1803 })) 1804 } 1805 CheckClippyTargets::Targets(_) => Err(ArgsErr::AllTargets), 1806 }, 1807 Self::Clippy(&mut (ref mut clippy_targets, _)) => match *clippy_targets { 1808 CheckClippyTargets::Default => { 1809 *clippy_targets = CheckClippyTargets::All; 1810 Ok(()) 1811 } 1812 CheckClippyTargets::All => { 1813 Err(ArgsErr::DuplicateOption(match ALL_TARGETS.parse() { 1814 Ok(arg) => arg, 1815 Err(e) => match e {}, 1816 })) 1817 } 1818 CheckClippyTargets::Targets(_) => Err(ArgsErr::AllTargets), 1819 }, 1820 Self::Test(&mut (ref mut test_targets, _)) => match *test_targets { 1821 TestTargets::Default => { 1822 *test_targets = TestTargets::All; 1823 Ok(()) 1824 } 1825 TestTargets::All => Err(ArgsErr::DuplicateOption(match ALL_TARGETS.parse() { 1826 Ok(arg) => arg, 1827 Err(e) => match e {}, 1828 })), 1829 TestTargets::Targets(_) | TestTargets::Doc => Err(ArgsErr::AllTargets), 1830 }, 1831 } 1832 } 1833 /// Adds the `target` to `self`. 1834 fn add_target(&mut self, target: Target) -> Result<(), ArgsErr> { 1835 match *self { 1836 Self::Check(ref mut check_targets) => match **check_targets { 1837 CheckClippyTargets::Default => { 1838 **check_targets = CheckClippyTargets::Targets(Targets::new(target)); 1839 Ok(()) 1840 } 1841 CheckClippyTargets::All => Err(ArgsErr::AllTargets), 1842 CheckClippyTargets::Targets(ref mut targets) => { 1843 if targets.add(target) { 1844 Ok(()) 1845 } else { 1846 Err(ArgsErr::DuplicateOption(target.to_os_string())) 1847 } 1848 } 1849 }, 1850 Self::Clippy(&mut (ref mut clippy_targets, _)) => match *clippy_targets { 1851 CheckClippyTargets::Default => { 1852 *clippy_targets = CheckClippyTargets::Targets(Targets::new(target)); 1853 Ok(()) 1854 } 1855 CheckClippyTargets::All => Err(ArgsErr::AllTargets), 1856 CheckClippyTargets::Targets(ref mut targets) => { 1857 if targets.add(target) { 1858 Ok(()) 1859 } else { 1860 Err(ArgsErr::DuplicateOption(target.to_os_string())) 1861 } 1862 } 1863 }, 1864 Self::Test(&mut (ref mut test_targets, _)) => match *test_targets { 1865 TestTargets::Default => { 1866 *test_targets = TestTargets::Targets(Targets::new(target)); 1867 Ok(()) 1868 } 1869 TestTargets::All => Err(ArgsErr::AllTargets), 1870 TestTargets::Targets(ref mut targets) => { 1871 if targets.add(target) { 1872 Ok(()) 1873 } else { 1874 Err(ArgsErr::DuplicateOption(target.to_os_string())) 1875 } 1876 } 1877 TestTargets::Doc => Err(ArgsErr::Doc), 1878 }, 1879 } 1880 } 1881 } 1882 /// Helper to store options from the command line. 1883 #[expect( 1884 clippy::struct_excessive_bools, 1885 reason = "used exclusively in the recursive function MetaCmd::from_args::extract_options" 1886 )] 1887 #[derive(Default)] 1888 struct ArgOpts { 1889 /// `--allow-implied-features`. 1890 allow_implied_features: bool, 1891 /// `--cargo-home` along with the path. 1892 cargo_home: Option<PathBuf>, 1893 /// `--cargo-path` along with the path. 1894 cargo_path: Option<PathBuf>, 1895 /// `--color`. 1896 color: bool, 1897 /// `--default-toolchain`. 1898 default_toolchain: bool, 1899 /// `--dir` along with the path. 1900 dir: Option<PathBuf>, 1901 /// `--ignore-compile-errors`. 1902 ignore_compile_errors: bool, 1903 /// `--ignore-msrv`. 1904 ignore_msrv: bool, 1905 /// `--ignore-features` along with the features to ignore. 1906 ignore_features: Vec<String>, 1907 /// `--progress`. 1908 progress: bool, 1909 /// `--rustup-home` along with the path. 1910 rustup_home: Option<PathBuf>, 1911 /// `--skip-msrv`. 1912 skip_msrv: bool, 1913 /// `--summary`. 1914 summary: bool, 1915 } 1916 /// Returns `"cargo"`. 1917 fn cargo_path() -> PathBuf { 1918 CARGO.to_owned().into() 1919 } 1920 impl From<ArgOpts> for Opts { 1921 fn from(value: ArgOpts) -> Self { 1922 Self { 1923 exec_dir: value.dir, 1924 rustup_home: value.rustup_home, 1925 cargo_path: value.cargo_path.unwrap_or_else(cargo_path), 1926 cargo_home: value.cargo_home, 1927 color: value.color, 1928 default_toolchain: value.default_toolchain, 1929 allow_implied_features: value.allow_implied_features, 1930 ignore_compile_errors: value.ignore_compile_errors, 1931 ignore_msrv: value.ignore_msrv, 1932 progress: value.progress, 1933 skip_msrv: value.skip_msrv, 1934 summary: value.summary, 1935 ignore_features: value.ignore_features, 1936 } 1937 } 1938 } 1939 /// `ci-cargo` command to run. 1940 #[cfg_attr(test, derive(Debug, PartialEq))] 1941 pub(crate) enum MetaCmd { 1942 /// Run the `cargo` command(s). 1943 Cargo(Cmd, Opts), 1944 /// Write help message to stdout. 1945 Help, 1946 /// Write version info to stdout. 1947 Version, 1948 } 1949 impl MetaCmd { 1950 /// Extracts options from `args`. 1951 /// 1952 /// This must only be called from [`Self::from_args`]. `cmd` is the current command command-specific 1953 /// options apply to. 1954 /// 1955 /// Returns the next `CargoCmd`. 1956 #[expect(unsafe_code, reason = "comment justifies correctness")] 1957 #[expect( 1958 clippy::arithmetic_side_effects, 1959 reason = "comment justifies correctness" 1960 )] 1961 #[expect( 1962 clippy::too_many_lines, 1963 reason = "expected since we need to extract all the passed options" 1964 )] 1965 fn extract_options<T: Iterator<Item = OsString>>( 1966 cmd: &mut CargoCmd<'_>, 1967 opts: &mut ArgOpts, 1968 mut args: T, 1969 ) -> Result<Option<OsString>, ArgsErr> { 1970 while let Some(arg) = args.next() { 1971 if let Some(arg_str) = arg.to_str() { 1972 match arg_str { 1973 ALL_TARGETS => cmd.add_all_targets(), 1974 ALLOW_IMPLIED_FEATURES => { 1975 if opts.allow_implied_features { 1976 Err(ArgsErr::DuplicateOption(arg)) 1977 } else { 1978 opts.allow_implied_features = true; 1979 Ok(()) 1980 } 1981 } 1982 BENCHES => cmd.add_target(Target::Benches), 1983 BINS => cmd.add_target(Target::Bins), 1984 CARGO_HOME => args.next().map_or(Err(ArgsErr::MissingCargoHome), |path| { 1985 opts.cargo_home 1986 .replace(path.into()) 1987 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 1988 }), 1989 CARGO_PATH => args.next().map_or(Err(ArgsErr::MissingCargoPath), |p| { 1990 // This won't overflow since `p.len() + 6 < usize::MAX` since 1991 // `p.len() <= isize::MAX`, `usize::MAX >= u16::MAX`, and 1992 // `i16::MAX + 6 < u16::MAX`. 1993 let mut path = PathBuf::with_capacity(CARGO.len() + 1 + p.len()); 1994 path.push(p); 1995 path.push(CARGO); 1996 opts.cargo_path 1997 .replace(path) 1998 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 1999 }), 2000 COLOR => { 2001 if opts.color { 2002 Err(ArgsErr::DuplicateOption(arg)) 2003 } else { 2004 opts.color = true; 2005 Ok(()) 2006 } 2007 } 2008 DEFAULT_TOOLCHAIN => { 2009 if opts.default_toolchain { 2010 Err(ArgsErr::DuplicateOption(arg)) 2011 } else { 2012 opts.default_toolchain = true; 2013 Ok(()) 2014 } 2015 } 2016 DENY_WARNINGS => match *cmd { 2017 CargoCmd::Clippy(&mut (_, ref mut deny_warnings)) => { 2018 if *deny_warnings { 2019 Err(ArgsErr::DuplicateOption(arg)) 2020 } else { 2021 *deny_warnings = true; 2022 Ok(()) 2023 } 2024 } 2025 CargoCmd::Check(_) | CargoCmd::Test(_) => Err(ArgsErr::UnknownArg(arg)), 2026 }, 2027 DIR => args.next().map_or(Err(ArgsErr::MissingDirPath), |path| { 2028 opts.dir 2029 .replace(path.into()) 2030 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 2031 }), 2032 DOC => match *cmd { 2033 CargoCmd::Test(&mut (ref mut test_targets, _)) => match *test_targets { 2034 TestTargets::Default => { 2035 *test_targets = TestTargets::Doc; 2036 Ok(()) 2037 } 2038 TestTargets::All | TestTargets::Targets(_) => Err(ArgsErr::Doc), 2039 TestTargets::Doc => Err(ArgsErr::DuplicateOption(arg)), 2040 }, 2041 CargoCmd::Check(_) | CargoCmd::Clippy(_) => Err(ArgsErr::UnknownArg(arg)), 2042 }, 2043 EXAMPLES => cmd.add_target(Target::Examples), 2044 IGNORE_COMPILE_ERRORS => { 2045 if opts.ignore_compile_errors { 2046 Err(ArgsErr::DuplicateOption(arg)) 2047 } else { 2048 opts.ignore_compile_errors = true; 2049 Ok(()) 2050 } 2051 } 2052 IGNORE_FEATURES => { 2053 if opts.ignore_features.is_empty() { 2054 args.next() 2055 .map_or(Err(ArgsErr::MissingIgnoredFeatures), |feats_os| { 2056 if let Some(feats) = feats_os.to_str() { 2057 feats 2058 .as_bytes() 2059 .split(|b| *b == b',') 2060 .try_fold((), |(), feat| { 2061 if opts 2062 .ignore_features 2063 .iter() 2064 .any(|f| f.as_bytes() == feat) 2065 { 2066 Err(()) 2067 } else { 2068 let utf8 = feat.to_owned(); 2069 // SAFETY: 2070 // `feats` is a valid `str` and was split by 2071 // a single UTF-8 code unit; thus `utf8` is also 2072 // valid UTF-8. 2073 opts.ignore_features.push(unsafe { 2074 String::from_utf8_unchecked(utf8) 2075 }); 2076 Ok(()) 2077 } 2078 }) 2079 .map_err(|()| { 2080 ArgsErr::DuplicateIgnoredFeatures(feats_os) 2081 }) 2082 } else { 2083 Err(ArgsErr::UnknownArg(feats_os)) 2084 } 2085 }) 2086 } else { 2087 Err(ArgsErr::DuplicateOption(arg)) 2088 } 2089 } 2090 IGNORE_MSRV => { 2091 if opts.ignore_msrv { 2092 Err(ArgsErr::DuplicateOption(arg)) 2093 } else { 2094 opts.ignore_msrv = true; 2095 Ok(()) 2096 } 2097 } 2098 IGNORED => match *cmd { 2099 CargoCmd::Test(&mut (_, ref mut ignored)) => match *ignored { 2100 Ignored::None => { 2101 *ignored = Ignored::Only; 2102 Ok(()) 2103 } 2104 Ignored::Only => Err(ArgsErr::DuplicateOption(arg)), 2105 Ignored::Include => Err(ArgsErr::IgnoredIncludeIgnored), 2106 }, 2107 CargoCmd::Check(_) | CargoCmd::Clippy(_) => Err(ArgsErr::UnknownArg(arg)), 2108 }, 2109 INCLUDE_IGNORED => match *cmd { 2110 CargoCmd::Test(&mut (_, ref mut ignored)) => match *ignored { 2111 Ignored::None => { 2112 *ignored = Ignored::Include; 2113 Ok(()) 2114 } 2115 Ignored::Only => Err(ArgsErr::IgnoredIncludeIgnored), 2116 Ignored::Include => Err(ArgsErr::DuplicateOption(arg)), 2117 }, 2118 CargoCmd::Check(_) | CargoCmd::Clippy(_) => Err(ArgsErr::UnknownArg(arg)), 2119 }, 2120 LIB => cmd.add_target(Target::Lib), 2121 PROGRESS => { 2122 if opts.progress { 2123 Err(ArgsErr::DuplicateOption(arg)) 2124 } else { 2125 opts.progress = true; 2126 Ok(()) 2127 } 2128 } 2129 RUSTUP_HOME => args.next().map_or(Err(ArgsErr::MissingRustupHome), |path| { 2130 opts.rustup_home 2131 .replace(path.into()) 2132 .map_or(Ok(()), |_| Err(ArgsErr::DuplicateOption(arg))) 2133 }), 2134 SKIP_MSRV => { 2135 if opts.skip_msrv { 2136 Err(ArgsErr::DuplicateOption(arg)) 2137 } else { 2138 opts.skip_msrv = true; 2139 Ok(()) 2140 } 2141 } 2142 SUMMARY => { 2143 if opts.summary { 2144 Err(ArgsErr::DuplicateOption(arg)) 2145 } else { 2146 opts.summary = true; 2147 Ok(()) 2148 } 2149 } 2150 TESTS => cmd.add_target(Target::Tests), 2151 _ => return Ok(Some(arg)), 2152 } 2153 } else { 2154 Err(ArgsErr::UnknownArg(arg)) 2155 }?; 2156 } 2157 Ok(None) 2158 } 2159 /// Returns data we need by reading the supplied CLI arguments. 2160 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 2161 pub(crate) fn from_args<T: Iterator<Item = OsString>>(mut args: T) -> Result<Self, ArgsErr> { 2162 args.next().ok_or(ArgsErr::NoArgs).and_then(|_| { 2163 args.next().ok_or(ArgsErr::NoCommand).and_then(|fst_cmd| { 2164 if let Some(fst_cmd_str) = fst_cmd.to_str() { 2165 let mut opts = ArgOpts::default(); 2166 let mut check_targets = None; 2167 let mut clippy_info: Option<(CheckClippyTargets, bool)> = None; 2168 let mut test_info: Option<(TestTargets, Ignored)> = None; 2169 let mut cargo_cmd = match fst_cmd_str { 2170 CHECK => CargoCmd::Check(check_targets.insert(CheckClippyTargets::Default)), 2171 CLIPPY => CargoCmd::Clippy( 2172 clippy_info.insert((CheckClippyTargets::Default, false)), 2173 ), 2174 HELP => { 2175 return args 2176 .next() 2177 .map_or_else(|| Ok(Self::Help), |_| Err(ArgsErr::HelpWithArgs)); 2178 } 2179 TEST => { 2180 CargoCmd::Test(test_info.insert((TestTargets::Default, Ignored::None))) 2181 } 2182 VERSION => { 2183 return args.next().map_or_else( 2184 || Ok(Self::Version), 2185 |_| Err(ArgsErr::VersionWithArgs), 2186 ); 2187 } 2188 _ => return Err(ArgsErr::NoCommand), 2189 }; 2190 while let Some(arg) = 2191 Self::extract_options(&mut cargo_cmd, &mut opts, &mut args)? 2192 { 2193 match arg.to_str().unwrap_or_else(|| { 2194 unreachable!("there is a bug in args::MetaCmd::extract_options") 2195 }) { 2196 CHECK => { 2197 if check_targets.is_some() { 2198 return Err(ArgsErr::DuplicateOption(arg)); 2199 } 2200 cargo_cmd = CargoCmd::Check( 2201 check_targets.insert(CheckClippyTargets::Default), 2202 ); 2203 } 2204 CLIPPY => { 2205 if clippy_info.is_some() { 2206 return Err(ArgsErr::DuplicateOption(arg)); 2207 } 2208 cargo_cmd = CargoCmd::Clippy( 2209 clippy_info.insert((CheckClippyTargets::Default, false)), 2210 ); 2211 } 2212 TEST => { 2213 if test_info.is_some() { 2214 return Err(ArgsErr::DuplicateOption(arg)); 2215 } 2216 cargo_cmd = CargoCmd::Test( 2217 test_info.insert((TestTargets::Default, Ignored::None)), 2218 ); 2219 } 2220 _ => return Err(ArgsErr::UnknownArg(arg)), 2221 } 2222 } 2223 if let Some(check) = check_targets { 2224 Ok(Self::Cargo( 2225 clippy_info.map_or_else( 2226 || { 2227 test_info.map_or(Cmd::Check(check), |test| { 2228 Cmd::CheckTest(check, test.0, test.1) 2229 }) 2230 }, 2231 |clippy| { 2232 test_info.map_or( 2233 Cmd::CheckClippy(check, clippy.0, clippy.1), 2234 |test| { 2235 Cmd::CheckClippyTest( 2236 check, clippy.0, clippy.1, test.0, test.1, 2237 ) 2238 }, 2239 ) 2240 }, 2241 ), 2242 opts.into(), 2243 )) 2244 } else if let Some(clippy) = clippy_info { 2245 Ok(Self::Cargo( 2246 test_info.map_or(Cmd::Clippy(clippy.0, clippy.1), |test| { 2247 Cmd::ClippyTest(clippy.0, clippy.1, test.0, test.1) 2248 }), 2249 opts.into(), 2250 )) 2251 } else if let Some(test) = test_info { 2252 Ok(Self::Cargo(Cmd::Test(test.0, test.1), opts.into())) 2253 } else { 2254 Err(ArgsErr::NoCommand) 2255 } 2256 } else { 2257 Err(ArgsErr::UnknownArg(fst_cmd)) 2258 } 2259 }) 2260 }) 2261 } 2262 }