ci-cargo

CI for Rust code.
git clone https://git.philomathiclife.com/repos/ci-cargo
Log | Files | Refs | README

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 }