ci-cargo

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

commit 530efb82f6712d7b55c168b8a7382da4cf06f4ab
parent 75f9d76322dcf9c6ccb9b963235336fe811ec9c0
Author: Zack Newman <zack@philomathiclife.com>
Date:   Fri, 17 Oct 2025 09:27:28 -0600

pass package name to commands. use test instead of t for backcompat. add ignore-msrv opt.

Diffstat:
MREADME.md | 195+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/args.rs | 494++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/cargo.rs | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/main.rs | 19+++++++++++--------
Msrc/manifest.rs | 497++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
5 files changed, 925 insertions(+), 414 deletions(-)

diff --git a/README.md b/README.md @@ -4,8 +4,8 @@ CI app for Rust code [<img alt="git" src="https://git.philomathiclife.com/badges/ci-cargo.svg" height="20">](https://git.philomathiclife.com/ci-cargo/log.html) [<img alt="crates.io" src="https://img.shields.io/crates/v/ci-cargo.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/ci-cargo) -`ci-cargo` is a CLI application that runs [`cargo`](https://doc.rust-lang.org/cargo/index.html) with -`clippy`, `t --tests`, and `t --doc` for all possible combinations of features defined in `Cargo.toml`. +`ci-cargo` is a CLI application that runs [`cargo`](https://doc.rust-lang.org/cargo/index.html) with `check`, +`clippy`, `test --tests`, and `test --doc` for all possible combinations of features defined in `Cargo.toml`. The toolchain(s) used depend on platform support for [`rustup`](https://rust-lang.github.io/rustup/), the existence of `rust-toolchain.toml`, the defined MSRV (if there is one), and if `--default-toolchain`, `--skip-msrv`, or @@ -19,8 +19,8 @@ If the above are not met, `cargo` will be used instead. `cargo +<MSRV>` will als conditions are met: * `--skip-msrv` was not passed. -* Package has an MSRV defined via `rust-version` that is semantically less than the `stable` or default toolchain - used. +* Package has an MSRV defined via `rust-version` that is less than the `stable` or not equivalent to the default + toolchain used. * `--rustup-home` was passed or the platform supports `rustup`. `ci-cargo` avoids superfluous combinations of features. For example if feature `foo` depends on feature `bar` and @@ -39,16 +39,18 @@ build works on both the stable or default toolchain _and_ the stated MSRV (if on ## Commands -* `<none>`: `cargo clippy` and `cargo t` are invoked for each combination of features. -* `help`/`h`: Prints help message. -* `version`/`v`: Prints version info. -* `clippy`/`c`: `cargo clippy` is invoked for each combination of features. -* `tests`/`t`: `cargo t --tests` is invoked for each combination of features. -* `doc_tests`/`d`: `cargo t --doc` is invoked for each combination of features. +* `<none>`: `cargo clippy` and `cargo test` are invoked for each combination of features. +* `help`: Prints help message. +* `version`: Prints version info. +* `check`: `cargo check` is invoked for each combination of features. +* `clippy`: `cargo clippy` is invoked for each combination of features. +* `tests`: `cargo test --tests` is invoked for each combination of features. +* `doc-tests`: `cargo test --doc` is invoked for each combination of features. ## Options -* `--all-targets`: `cargo clippy --all-targets` is invoked for each combination of features. +* `--all-targets`: `cargo clippy --all-targets` or `cargo check --all-targets` is invoked for each combination of + features. * `--allow-implied-features`: Features implied by optional dependencies are allowed; by default features must be explicitly defined or an error will occur (e.g., `foo = ["dep:bar"]`). * `--cargo-home <PATH>`: Sets the storage directory used by `cargo`. @@ -68,8 +70,9 @@ build works on both the stable or default toolchain _and_ the stated MSRV (if on represents the empty set of features (i.e., `--no-default-features`). For example `--ignore-features`, will error since no features were passed. `--ignore-features ''` will ignore the empty set of features. `--ignore-features a,` will ignore the empty set of features and any combination of features that depend on feature `a`. -* `--ignored`: `cargo t --tests -- --ignored` is invoked for each combination of features. -* `--include-ignored`: `cargo t --tests -- --include-ignored` is invoked for each combination of features. +* `--ignore-msrv`: `--ignore-rust-version` is passed to the commands for the default toolchain. +* `--ignored`: `cargo test -- --ignored` is invoked for each combination of features. +* `--include-ignored`: `cargo test -- --include-ignored` is invoked for each combination of features. * `--progress`: Writes the current progress to `stdout`. * `--rustup-home <PATH>`: Sets the storage directory used by `rustup`. * `--skip-msrv`: `cargo +<MSRV>` is not used. @@ -78,10 +81,12 @@ build works on both the stable or default toolchain _and_ the stated MSRV (if on Any unique sequence of the above options are allowed to be passed after the command so long as the following conditions are met: -* No options are allowed for the `help`/`h` or `version`/`v` commands. -* `--all-targets` and `--deny-warnings` are allowed iff `clippy`/`c` or no command is passed. -* `--ignored` is allowed iff `tests`/`t` or no command is passed and `--include-ignored` is not passed. -* `--include-ignored` is allowed iff `tests`/`t` or no command is passed and `--ignored` is not passed. +* No options are allowed for the `help` or `version` commands. +* `--all-targets` is allowed iff `check`, `clippy`, or no command is passed. +* `--deny-warnings` is allowed iff `clippy` or no command is passed. +* `--ignored` is allowed iff `tests` or no command is passed and `--include-ignored` is not passed. +* `--include-ignored` is allowed iff `tests` or no command is passed and `--ignored` is not passed. +* `--ignore-msrv` is allowed iff the `stable` toolchain is used. ## `ci-cargo` in action @@ -167,78 +172,78 @@ foo = [] bar = ["fizz"] fizz = [] [zack@laptop example]$ ci-cargo --all-targets --include-ignored --progress --summary -Toolchain (1/2): cargo +stable. Features (1/32, 5 skipped): buzz,fizz,foo. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (1/32, 5 skipped): buzz,fizz,foo. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (2/32, 6 skipped): fizz,foo. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (2/32, 6 skipped): fizz,foo. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (3/32, 10 skipped): bar,buzz,foo. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (3/32, 10 skipped): bar,buzz,foo. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (4/32, 10 skipped): buzz,foo. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (4/32, 10 skipped): buzz,foo. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (5/32, 10 skipped): bar,foo. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (5/32, 10 skipped): bar,foo. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (6/32, 10 skipped): foo. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (6/32, 10 skipped): foo. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (7/32, 11 skipped): buzz,default,fizz. Command (1/2): clippy. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (7/32, 11 skipped): buzz,default,fizz. Command (2/2): t. Time running: 0 s. -Toolchain (1/2): cargo +stable. Features (8/32, 12 skipped): default,fizz. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (8/32, 12 skipped): default,fizz. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (9/32, 13 skipped): buzz,fizz. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (9/32, 13 skipped): buzz,fizz. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (10/32, 14 skipped): fizz. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (10/32, 14 skipped): fizz. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (11/32, 14 skipped): bar,buzz,default. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (11/32, 14 skipped): bar,buzz,default. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (12/32, 14 skipped): buzz,default. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (12/32, 14 skipped): buzz,default. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (13/32, 14 skipped): bar,default. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (13/32, 14 skipped): bar,default. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (14/32, 14 skipped): default. Command (1/2): clippy. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (14/32, 14 skipped): default. Command (2/2): t. Time running: 1 s. -Toolchain (1/2): cargo +stable. Features (15/32, 14 skipped): bar,buzz. Command (1/2): clippy. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (15/32, 14 skipped): bar,buzz. Command (2/2): t. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (16/32, 14 skipped): buzz. Command (1/2): clippy. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (16/32, 14 skipped): buzz. Command (2/2): t. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (17/32, 14 skipped): bar. Command (1/2): clippy. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (17/32, 14 skipped): bar. Command (2/2): t. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (18/32, 14 skipped): <none>. Command (1/2): clippy. Time running: 2 s. -Toolchain (1/2): cargo +stable. Features (18/32, 14 skipped): <none>. Command (2/2): t. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (1/32, 5 skipped): buzz,fizz,foo. Command (1/2): clippy. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (1/32, 5 skipped): buzz,fizz,foo. Command (2/2): t. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (2/32, 6 skipped): fizz,foo. Command (1/2): clippy. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (2/32, 6 skipped): fizz,foo. Command (2/2): t. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (3/32, 10 skipped): bar,buzz,foo. Command (1/2): clippy. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (3/32, 10 skipped): bar,buzz,foo. Command (2/2): t. Time running: 2 s. -Toolchain (2/2): cargo +1.89.0. Features (4/32, 10 skipped): buzz,foo. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (4/32, 10 skipped): buzz,foo. Command (2/2): t. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (5/32, 10 skipped): bar,foo. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (5/32, 10 skipped): bar,foo. Command (2/2): t. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (6/32, 10 skipped): foo. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (6/32, 10 skipped): foo. Command (2/2): t. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (7/32, 11 skipped): buzz,default,fizz. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (7/32, 11 skipped): buzz,default,fizz. Command (2/2): t. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (8/32, 12 skipped): default,fizz. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (8/32, 12 skipped): default,fizz. Command (2/2): t. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (9/32, 13 skipped): buzz,fizz. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (9/32, 13 skipped): buzz,fizz. Command (2/2): t. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (10/32, 14 skipped): fizz. Command (1/2): clippy. Time running: 3 s. -Toolchain (2/2): cargo +1.89.0. Features (10/32, 14 skipped): fizz. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (11/32, 14 skipped): bar,buzz,default. Command (1/2): clippy. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (11/32, 14 skipped): bar,buzz,default. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (12/32, 14 skipped): buzz,default. Command (1/2): clippy. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (12/32, 14 skipped): buzz,default. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (13/32, 14 skipped): bar,default. Command (1/2): clippy. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (13/32, 14 skipped): bar,default. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (14/32, 14 skipped): default. Command (1/2): clippy. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (14/32, 14 skipped): default. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (15/32, 14 skipped): bar,buzz. Command (1/2): clippy. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (15/32, 14 skipped): bar,buzz. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (16/32, 14 skipped): buzz. Command (1/2): clippy. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (16/32, 14 skipped): buzz. Command (2/2): t. Time running: 4 s. -Toolchain (2/2): cargo +1.89.0. Features (17/32, 14 skipped): bar. Command (1/2): clippy. Time running: 5 s. -Toolchain (2/2): cargo +1.89.0. Features (17/32, 14 skipped): bar. Command (2/2): t. Time running: 5 s. -Toolchain (2/2): cargo +1.89.0. Features (18/32, 14 skipped): <none>. Command (1/2): clippy. Time running: 5 s. -Toolchain (2/2): cargo +1.89.0. Features (18/32, 14 skipped): <none>. Command (2/2): t. Time running: 5 s. +Package: example. Toolchain (1/2): cargo +stable. Features (1/32, 5 skipped): buzz,fizz,foo. Command (1/2): clippy. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (1/32, 5 skipped): buzz,fizz,foo. Command (2/2): test. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (2/32, 6 skipped): fizz,foo. Command (1/2): clippy. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (2/32, 6 skipped): fizz,foo. Command (2/2): test. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (3/32, 10 skipped): bar,buzz,foo. Command (1/2): clippy. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (3/32, 10 skipped): bar,buzz,foo. Command (2/2): test. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (4/32, 10 skipped): buzz,foo. Command (1/2): clippy. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (4/32, 10 skipped): buzz,foo. Command (2/2): test. Time running: 0 s. +Package: example. Toolchain (1/2): cargo +stable. Features (5/32, 10 skipped): bar,foo. Command (1/2): clippy. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (5/32, 10 skipped): bar,foo. Command (2/2): test. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (6/32, 10 skipped): foo. Command (1/2): clippy. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (6/32, 10 skipped): foo. Command (2/2): test. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (7/32, 11 skipped): buzz,default,fizz. Command (1/2): clippy. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (7/32, 11 skipped): buzz,default,fizz. Command (2/2): test. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (8/32, 12 skipped): default,fizz. Command (1/2): clippy. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (8/32, 12 skipped): default,fizz. Command (2/2): test. Time running: 1 s. +Package: example. Toolchain (1/2): cargo +stable. Features (9/32, 13 skipped): buzz,fizz. Command (1/2): clippy. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (9/32, 13 skipped): buzz,fizz. Command (2/2): test. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (10/32, 14 skipped): fizz. Command (1/2): clippy. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (10/32, 14 skipped): fizz. Command (2/2): test. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (11/32, 14 skipped): bar,buzz,default. Command (1/2): clippy. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (11/32, 14 skipped): bar,buzz,default. Command (2/2): test. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (12/32, 14 skipped): buzz,default. Command (1/2): clippy. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (12/32, 14 skipped): buzz,default. Command (2/2): test. Time running: 2 s. +Package: example. Toolchain (1/2): cargo +stable. Features (13/32, 14 skipped): bar,default. Command (1/2): clippy. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (13/32, 14 skipped): bar,default. Command (2/2): test. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (14/32, 14 skipped): default. Command (1/2): clippy. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (14/32, 14 skipped): default. Command (2/2): test. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (15/32, 14 skipped): bar,buzz. Command (1/2): clippy. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (15/32, 14 skipped): bar,buzz. Command (2/2): test. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (16/32, 14 skipped): buzz. Command (1/2): clippy. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (16/32, 14 skipped): buzz. Command (2/2): test. Time running: 3 s. +Package: example. Toolchain (1/2): cargo +stable. Features (17/32, 14 skipped): bar. Command (1/2): clippy. Time running: 4 s. +Package: example. Toolchain (1/2): cargo +stable. Features (17/32, 14 skipped): bar. Command (2/2): test. Time running: 4 s. +Package: example. Toolchain (1/2): cargo +stable. Features (18/32, 14 skipped): <none>. Command (1/2): clippy. Time running: 4 s. +Package: example. Toolchain (1/2): cargo +stable. Features (18/32, 14 skipped): <none>. Command (2/2): test. Time running: 4 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (1/32, 5 skipped): buzz,fizz,foo. Command (1/2): clippy. Time running: 4 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (1/32, 5 skipped): buzz,fizz,foo. Command (2/2): test. Time running: 4 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (2/32, 6 skipped): fizz,foo. Command (1/2): clippy. Time running: 4 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (2/32, 6 skipped): fizz,foo. Command (2/2): test. Time running: 4 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (3/32, 10 skipped): bar,buzz,foo. Command (1/2): clippy. Time running: 5 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (3/32, 10 skipped): bar,buzz,foo. Command (2/2): test. Time running: 5 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (4/32, 10 skipped): buzz,foo. Command (1/2): clippy. Time running: 5 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (4/32, 10 skipped): buzz,foo. Command (2/2): test. Time running: 5 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (5/32, 10 skipped): bar,foo. Command (1/2): clippy. Time running: 5 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (5/32, 10 skipped): bar,foo. Command (2/2): test. Time running: 5 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (6/32, 10 skipped): foo. Command (1/2): clippy. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (6/32, 10 skipped): foo. Command (2/2): test. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (7/32, 11 skipped): buzz,default,fizz. Command (1/2): clippy. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (7/32, 11 skipped): buzz,default,fizz. Command (2/2): test. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (8/32, 12 skipped): default,fizz. Command (1/2): clippy. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (8/32, 12 skipped): default,fizz. Command (2/2): test. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (9/32, 13 skipped): buzz,fizz. Command (1/2): clippy. Time running: 6 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (9/32, 13 skipped): buzz,fizz. Command (2/2): test. Time running: 7 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (10/32, 14 skipped): fizz. Command (1/2): clippy. Time running: 7 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (10/32, 14 skipped): fizz. Command (2/2): test. Time running: 7 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (11/32, 14 skipped): bar,buzz,default. Command (1/2): clippy. Time running: 7 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (11/32, 14 skipped): bar,buzz,default. Command (2/2): test. Time running: 7 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (12/32, 14 skipped): buzz,default. Command (1/2): clippy. Time running: 7 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (12/32, 14 skipped): buzz,default. Command (2/2): test. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (13/32, 14 skipped): bar,default. Command (1/2): clippy. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (13/32, 14 skipped): bar,default. Command (2/2): test. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (14/32, 14 skipped): default. Command (1/2): clippy. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (14/32, 14 skipped): default. Command (2/2): test. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (15/32, 14 skipped): bar,buzz. Command (1/2): clippy. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (15/32, 14 skipped): bar,buzz. Command (2/2): test. Time running: 8 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (16/32, 14 skipped): buzz. Command (1/2): clippy. Time running: 9 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (16/32, 14 skipped): buzz. Command (2/2): test. Time running: 9 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (17/32, 14 skipped): bar. Command (1/2): clippy. Time running: 9 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (17/32, 14 skipped): bar. Command (2/2): test. Time running: 9 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (18/32, 14 skipped): <none>. Command (1/2): clippy. Time running: 9 s. +Package: example. Toolchain (2/2): cargo +1.89.0. Features (18/32, 14 skipped): <none>. Command (2/2): test. Time running: 9 s. Toolchains used: cargo +stable and cargo +1.89.0 Features used: buzz,fizz,foo @@ -259,16 +264,18 @@ bar,buzz buzz bar <none> -[zack@laptop example]$ ci-cargo clippy --deny-warnings --ignore-compile-errors --ignore-features buzz, --skip-msrv -[zack@laptop ~]$ ci-cargo t --allow-implied-features --cargo-home ~/.cargo/ --cargo-path ~/.cargo/bin --default-toolchain --dir ~/example/ --ignored --rustup-home ~/.rustup/ -[zack@laptop ~]$ ci-cargo v +[zack@laptop example]$ ci-cargo clippy --deny-warnings --ignore-compile-errors --ignore-features buzz, --ignore-msrv --skip-msrv +[zack@laptop ~]$ ci-cargo tests --allow-implied-features --cargo-home ~/.cargo/ --cargo-path ~/.cargo/bin --default-toolchain --dir ~/example/ --ignored --rustup-home ~/.rustup/ +[zack@laptop ~]$ ci-cargo version ci-cargo 0.1.0 -[zack@laptop example]$ ci-cargo --summary d -d is an unknown argument. See ci-cargo help for more information. +[zack@laptop example]$ ci-cargo --summary doc-tests +doc-tests is an unknown argument. See ci-cargo help for more information. ``` ## Limitations +Functionality when using old versions of `cargo` may not exist (e.g., `cargo check` wasn't available until 1.16.0). + There is a hard limit on the number of features allowed. Specifically the number of features can't exceed the number of bits that make up a pointer; however practical limits will almost always be a factor long before hitting such a hard limit due to the exponential effect features have. diff --git a/src/args.rs b/src/args.rs @@ -1,5 +1,5 @@ use super::{ - cargo::{CargoErr, Clippy, Options, TestKind, Tests, Toolchain}, + cargo::{CargoErr, Check, Clippy, Options, TestKind, Tests, Toolchain}, manifest::PowerSet, }; use core::{ @@ -18,15 +18,16 @@ pub(crate) const HELP_MSG: &str = "Continuous integration of all features using Usage: ci-cargo [COMMAND] [OPTIONS] Commands: - <none> cargo clippy and cargo t - help, h This message - version, v Prints version info - clippy, c cargo clippy - tests, t cargo t --tests - doc-tests, d cargo t --doc + <none> cargo clippy and cargo test + help This message + version Prints version info + check cargo check + clippy cargo clippy + tests cargo test --tests + doc-tests cargo test --doc Options: - --all-targets --all-targets is passed to cargo clippy + --all-targets --all-targets is passed to cargo clippy or cargo check --allow-implied features Allow implied features from optional dependencies --cargo-home <PATH> Set the storage directory used by cargo --cargo-path <PATH> Set the path cargo is in. Defaults to cargo @@ -36,8 +37,9 @@ Options: --dir <PATH> Set the working directory --ignore-compile-errors compile_error!s are ignored --ignore-features <feats> Ignore the provided comma-separated features - --ignored --ignored is passed to cargo t --tests - --include-ignored --include-ignored is passed to cargo t --tests + --ignore-msrv --ignore-rust-version is passed to each command for the default toolchain + --ignored -- --ignored is passed to cargo test + --include-ignored -- --include-ignored is passed to cargo test --progress Writes the progress to stdout --rustup-home <PATH> Set the storage directory used by rustup --skip-msrv cargo +<MSRV> is not used @@ -46,12 +48,14 @@ Options: Any unique sequence of the above options are allowed so long as the following conditions are met: -* no options are allowed for the help/h or version/v commands -* --all-targets and --deny-warnings are allowed iff clippy/c or no command is passed -* --ignored is allowed iff tests/t or no command is passed and --include-ignored +* no options are allowed for the help or version commands +* --all-targets is allowed iff check, clippy, or no command is passed +* --deny-warnings is allowed iff clippy or no command is passed +* --ignored is allowed iff tests or no command is passed and --include-ignored is not passed -* --include-ignored is allowed iff tests/t or no command is passed and --ignored +* --include-ignored is allowed iff tests or no command is passed and --ignored is not passed +* --ignore-msrv is not allowed if the stable toolchain is used cargo +stable will be used to run the command(s) if all of the following condtions are met: @@ -63,8 +67,8 @@ If the above are not met, cargo will be used instead. cargo +<MSRV> will also be if all of the following conditions are met: * --skip-msrv was not passed -* Package has an MSRV defined via rust-version that is semantically less than the stable or default - toolchain used +* Package has an MSRV defined via rust-version that is semantically less than the stable or not + equivalent to the default toolchain used * --rustup-home was passed or the platform supports rustup For the toolchain(s) used, the command(s) are run for each combination of features sans any provided @@ -73,24 +77,16 @@ features in the package. An empty value is interpreted as the empty set of featu "; /// `"help"`. const HELP: &str = "help"; -/// `"h"`. -const H: &str = "h"; /// `"version"`. const VERSION: &str = "version"; -/// `"v"`. -const V: &str = "v"; +/// `"check"`. +const CHECK: &str = "check"; /// `"clippy"`. const CLIPPY: &str = "clippy"; -/// `"c"`. -const C: &str = "c"; /// `"tests"`. const TESTS: &str = "tests"; -/// `"t"`. -const T: &str = "t"; /// `"doc-tests"`. const DOC_TESTS: &str = "doc-tests"; -/// `"d"`. -const D: &str = "d"; /// `"--all-targets"`. const ALL_TARGETS: &str = "--all-targets"; /// `"--allow-implied-features"`. @@ -111,6 +107,8 @@ const DIR: &str = "--dir"; const IGNORE_COMPILE_ERRORS: &str = "--ignore-compile-errors"; /// `"--ignore-features"`. const IGNORE_FEATURES: &str = "--ignore-features"; +/// `"--ignore-msrv"`. +const IGNORE_MSRV: &str = "--ignore-msrv"; /// `"--ignored"`. const IGNORED: &str = "--ignored"; /// `"--include-ignored"`. @@ -147,10 +145,10 @@ pub(crate) enum ArgsErr { MissingRustupHome, /// Error when `--all-targets` is passed for `tests` or `doc-tests`. AllTargetsTests, - /// Error when `--deny-warnings` is passed for `tests` or `doc-tests`. - DenyWarningsTests, - /// Error when `--ignored` or `--include-ignored` is passed for `clippy` or `doc-tests`. - IgnoredClippyDoc, + /// Error when `--deny-warnings` is passed for `check`, `tests`, or `doc-tests`. + DenyWarningsCheckTests, + /// Error when `--ignored` or `--include-ignored` is passed for `check`, `clippy`, or `doc-tests`. + IgnoredCheckClippyDoc, /// Error when `--ignored` and `--include-ignored` are passed. IgnoredIncludeIgnored, /// Error when `--ignore-features` was not passed any features. @@ -226,16 +224,16 @@ impl ArgsErr { "{ALL_TARGETS} was passed with {TESTS} or {DOC_TESTS}.{FINAL_SENTENCE}" ) } - Self::DenyWarningsTests => { + Self::DenyWarningsCheckTests => { writeln!( stderr, - "{DENY_WARNINGS} was passed with {TESTS} or {DOC_TESTS}.{FINAL_SENTENCE}" + "{DENY_WARNINGS} was passed with {CHECK}, {TESTS}, or {DOC_TESTS}.{FINAL_SENTENCE}" ) } - Self::IgnoredClippyDoc => { + Self::IgnoredCheckClippyDoc => { writeln!( stderr, - "{IGNORED} or {INCLUDE_IGNORED} was passed with {CLIPPY} or {DOC_TESTS}.{FINAL_SENTENCE}" + "{IGNORED} or {INCLUDE_IGNORED} was passed with {CHECK}, {CLIPPY}, or {DOC_TESTS}.{FINAL_SENTENCE}" ) } Self::IgnoredIncludeIgnored => { @@ -283,6 +281,8 @@ pub(crate) struct Opts { pub allow_implied_features: bool, /// `true` iff `compile_error`s should be ignored. pub ignore_compile_errors: bool, + /// `true` iff `--ignore-rust-version` should be passed. + pub ignore_msrv: bool, /// `true` iff progress should be written to `stdout`. pub progress: bool, /// `true` iff the MSRV toolchain should not be used. @@ -308,6 +308,7 @@ impl Default for Opts { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -315,7 +316,7 @@ impl Default for Opts { } } } -/// Controls if `cargo t -tests -- --ignored` or `cargo t --tests --include-ignored` should be run. +/// Controls if `cargo test --tests -- --ignored` or `cargo test --tests --include-ignored` should be run. #[cfg_attr(test, derive(Debug, PartialEq))] #[derive(Clone, Copy)] pub(crate) enum Ignored { @@ -383,7 +384,9 @@ impl Display for NonZeroUsizePlus1 { } } /// Progress tracker for when `--progress` was passed. -struct Progress<'toolchain> { +struct Progress<'package, 'toolchain> { + /// The name of the package. + package: &'package str, /// The current toolchain counter. toolchain_counter: &'static str, /// The total toolchains that will be used. @@ -410,29 +413,69 @@ struct Progress<'toolchain> { /// to it. stdout: Option<StdoutLock<'static>>, } -impl Progress<'_> { +impl<'package> Progress<'package, '_> { /// Returns `Self` based on running both clippy and tests. - fn all(toolchain: Toolchain<'_>, use_msrv: bool, features_total: NonZeroUsizePlus1) -> Self { - Self::inner_new("clippy", "2", toolchain, use_msrv, features_total) + fn all( + package: &'package str, + toolchain: Toolchain<'_>, + use_msrv: bool, + features_total: NonZeroUsizePlus1, + ) -> Self { + Self::inner_new(package, "clippy", "2", toolchain, use_msrv, features_total) } /// Returns `Self` based on running clippy. - fn clippy(toolchain: Toolchain<'_>, use_msrv: bool, features_total: NonZeroUsizePlus1) -> Self { - Self::inner_new("clippy", "1", toolchain, use_msrv, features_total) + fn clippy( + package: &'package str, + toolchain: Toolchain<'_>, + use_msrv: bool, + features_total: NonZeroUsizePlus1, + ) -> Self { + Self::inner_new(package, "clippy", "1", toolchain, use_msrv, features_total) } /// Returns `Self` based on running tests --tests. - fn tests(toolchain: Toolchain<'_>, use_msrv: bool, features_total: NonZeroUsizePlus1) -> Self { - Self::inner_new("t --tests", "1", toolchain, use_msrv, features_total) + fn tests( + package: &'package str, + toolchain: Toolchain<'_>, + use_msrv: bool, + features_total: NonZeroUsizePlus1, + ) -> Self { + Self::inner_new( + package, + "test --tests", + "1", + toolchain, + use_msrv, + features_total, + ) } /// Returns `Self` based on running tests --doc. fn doc_tests( + package: &'package str, toolchain: Toolchain<'_>, use_msrv: bool, features_total: NonZeroUsizePlus1, ) -> Self { - Self::inner_new("t --doc", "1", toolchain, use_msrv, features_total) + Self::inner_new( + package, + "test --doc", + "1", + toolchain, + use_msrv, + features_total, + ) + } + /// Returns `Self` based on running check. + fn check( + package: &'package str, + toolchain: Toolchain<'_>, + use_msrv: bool, + features_total: NonZeroUsizePlus1, + ) -> Self { + Self::inner_new(package, "check", "1", toolchain, use_msrv, features_total) } /// Returns `Self` based on the passed arguments. fn inner_new( + package: &'package str, cmd: &'static str, cmd_total: &'static str, tool: Toolchain<'_>, @@ -445,6 +488,7 @@ impl Progress<'_> { ("cargo", "") }; Self { + package, toolchain_counter: "1", toolchain_total: if use_msrv { "2" } else { "1" }, cargo_cmd, @@ -468,9 +512,9 @@ impl Progress<'_> { ) { if let Some(ref mut std) = self.stdout { // Example: - // "Toolchain (1/2): cargo +stable. Features (18/128, 3 skipped): foo,bar. Command (1/2): clippy. Time running: 49 s."); + // "Package: foo. Toolchain (1/2): cargo +stable. Features (18/128, 3 skipped): foo,bar. Command (1/2): clippy. Time running: 49 s."); // Note `features_skipped` maxes at `usize::MAX` since the empty set is never skipped. - if writeln!(std, "Toolchain ({}/{}): {}{}. Features ({}/{}, {} skipped): {}. Command ({}/{}): {}. Time running: {} s.", 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() { + 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() { drop(self.stdout.take()); } } @@ -479,7 +523,7 @@ impl Progress<'_> { /// `cargo` command(s) we should run. #[cfg_attr(test, derive(Debug, PartialEq))] pub(crate) enum Cmd { - /// Execute all `cargo` commands. + /// Execute `cargo clippy` and `cargo test` commands. /// /// The first `bool` is `true` iff `--all-targets` was passed, /// the second `bool` is `true` iff `--deny-warnings` was passed, and @@ -491,26 +535,37 @@ pub(crate) enum Cmd { /// The first `bool` is `true` iff `--all-targets` was passed, /// and the second `bool` is `true` iff `--deny-warnings` was passed. Clippy(bool, bool), - /// `cargo clippy t --tests`. + /// `cargo test --tests`. Tests(Ignored), - /// `cargo clippy t --doc`. + /// `cargo test --doc`. DocTests, + /// `cargo check`. + /// + /// The contained `bool` is `true` iff `--all-targets` was passed, + Check(bool), } impl Cmd { - /// Runs the appropriate `cargo` command for all features in `power_set`. + /// Runs the appropriate `cargo` command(s) for all features in `power_set`. /// /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is /// later used. pub(crate) fn run<'a>( self, - options: Options<'a, '_>, + options: Options<'a, '_, '_>, msrv: Option<&'a str>, power_set: &mut PowerSet<'_>, progress: bool, ) -> Result<(), Box<CargoErr>> { match self { Self::All(all_targets, deny_warning, ignored_tests) => Self::run_all( - progress.then(|| Progress::all(options.toolchain, msrv.is_some(), power_set.len())), + progress.then(|| { + Progress::all( + options.package_name, + options.toolchain, + msrv.is_some(), + power_set.len(), + ) + }), msrv, options, all_targets, @@ -519,8 +574,14 @@ impl Cmd { power_set, ), Self::Clippy(all_targets, deny_warnings) => Self::run_clippy( - progress - .then(|| Progress::clippy(options.toolchain, msrv.is_some(), power_set.len())), + progress.then(|| { + Progress::clippy( + options.package_name, + options.toolchain, + msrv.is_some(), + power_set.len(), + ) + }), msrv, options, all_targets, @@ -528,8 +589,14 @@ impl Cmd { power_set, ), Self::Tests(ignored_tests) => Self::run_unit_tests( - progress - .then(|| Progress::tests(options.toolchain, msrv.is_some(), power_set.len())), + progress.then(|| { + Progress::tests( + options.package_name, + options.toolchain, + msrv.is_some(), + power_set.len(), + ) + }), msrv, options, ignored_tests, @@ -537,23 +604,42 @@ impl Cmd { ), Self::DocTests => Self::run_doc_tests( progress.then(|| { - Progress::doc_tests(options.toolchain, msrv.is_some(), power_set.len()) + Progress::doc_tests( + options.package_name, + options.toolchain, + msrv.is_some(), + power_set.len(), + ) }), msrv, options, power_set, ), + Self::Check(all_targets) => Self::run_check( + progress.then(|| { + Progress::check( + options.package_name, + options.toolchain, + msrv.is_some(), + power_set.len(), + ) + }), + msrv, + options, + all_targets, + power_set, + ), } } - /// Runs `cargo clippy` and `cargo t` for all features in `power_set`. + /// Runs `cargo clippy` and `cargo test` for all features in `power_set`. /// /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is /// later used. #[expect(clippy::else_if_without_else, reason = "don't want an empty else")] fn run_all<'a>( - mut progress: Option<Progress<'a>>, + mut progress: Option<Progress<'_, 'a>>, msrv: Option<&'a str>, - mut options: Options<'a, '_>, + mut options: Options<'a, '_, '_>, all_targets: bool, deny_warnings: bool, ignored_tests: Ignored, @@ -570,7 +656,7 @@ impl Cmd { } // Note we run tests even if a `compile_error` occurred since it may not occur for tests. prog.cmd_counter = "2"; - prog.cmd = "t"; + prog.cmd = "test"; prog.write_to_stdout(set, feat_counter, skip_count); if let Err(e) = Tests::run(&mut options, TestKind::All(ignored_tests), set) { return Err(e); @@ -596,7 +682,7 @@ impl Cmd { } // Note we run tests even if a `compile_error` occurred since it may not occur for tests. prog.cmd_counter = "2"; - prog.cmd = "t"; + prog.cmd = "test"; prog.write_to_stdout(set, feat_counter, skip_count); if let Err(e) = Tests::run(&mut options, TestKind::All(ignored_tests), set) { return Err(e); @@ -638,9 +724,9 @@ impl Cmd { /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is /// later used. fn run_clippy<'a>( - mut progress: Option<Progress<'a>>, + mut progress: Option<Progress<'_, 'a>>, msrv: Option<&'a str>, - mut options: Options<'a, '_>, + mut options: Options<'a, '_, '_>, all_targets: bool, deny_warnings: bool, power_set: &mut PowerSet<'_>, @@ -693,14 +779,14 @@ impl Cmd { } Ok(()) } - /// Runs `cargo t --tests` for all features in `power_set`. + /// Runs `cargo test --tests` for all features in `power_set`. /// /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is /// later used. fn run_unit_tests<'a>( - mut progress: Option<Progress<'a>>, + mut progress: Option<Progress<'_, 'a>>, msrv: Option<&'a str>, - mut options: Options<'a, '_>, + mut options: Options<'a, '_, '_>, ignored_tests: Ignored, power_set: &mut PowerSet<'_>, ) -> Result<(), Box<CargoErr>> { @@ -752,14 +838,14 @@ impl Cmd { } Ok(()) } - /// Runs `cargo t --doc` for all features in `power_set`. + /// Runs `cargo test --doc` for all features in `power_set`. /// /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is /// later used. fn run_doc_tests<'a>( - mut progress: Option<Progress<'a>>, + mut progress: Option<Progress<'_, 'a>>, msrv: Option<&'a str>, - mut options: Options<'a, '_>, + mut options: Options<'a, '_, '_>, power_set: &mut PowerSet<'_>, ) -> Result<(), Box<CargoErr>> { if let Some(ref mut prog) = progress { @@ -803,8 +889,8 @@ impl Cmd { match Tests::run(&mut options, TestKind::Doc, set) { Ok(no_library_target) => { if no_library_target { - // We don't want to continue invoking `cargo t --doc` once we know this is not a library - // target. + // We don't want to continue invoking `cargo test --doc` once we know this is not a + // library target. return Ok(()); } } @@ -824,6 +910,65 @@ impl Cmd { } Ok(()) } + /// Runs `cargo check` for all features in `power_set`. + /// + /// Note the [`Toolchain`] in `options` is first used; and if `msrv.is_some()`, then [`Toolchain::Msrv`] is + /// later used. + fn run_check<'a>( + mut progress: Option<Progress<'_, 'a>>, + msrv: Option<&'a str>, + mut options: Options<'a, '_, '_>, + all_targets: bool, + power_set: &mut PowerSet<'_>, + ) -> Result<(), Box<CargoErr>> { + if let Some(ref mut prog) = progress { + let mut feat_counter = 1; + while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { + prog.write_to_stdout(set, feat_counter, skip_count); + if let Err(e) = Check::run(&mut options, all_targets, set) { + return Err(e); + } + // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very + // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via + // [`NonZeroUsizePlus1::fmt`]. + feat_counter = feat_counter.wrapping_add(1); + } + if let Some(msrv_val) = msrv { + feat_counter = 1; + prog.toolchain_counter = "2"; + prog.cargo_cmd = "cargo "; + prog.toolchain = msrv_val; + options.toolchain = Toolchain::Msrv(msrv_val); + power_set.reset(); + while let Some((set, skip_count)) = power_set.next_set_with_skip_count() { + prog.write_to_stdout(set, feat_counter, skip_count); + if let Err(e) = Check::run(&mut options, all_targets, set) { + return Err(e); + } + // The maximum number possible is `usize::MAX + 1`; however that can only happen at the very + // last item. Since we never display 0, we treat 0 as `usize::MAX + 1` when we display it via + // [`NonZeroUsizePlus1::fmt`]. + feat_counter = feat_counter.wrapping_add(1); + } + } + } else { + while let Some(set) = power_set.next_set() { + if let Err(e) = Check::run(&mut options, all_targets, set) { + return Err(e); + } + } + if let Some(msrv_val) = msrv { + options.toolchain = Toolchain::Msrv(msrv_val); + power_set.reset(); + while let Some(set) = power_set.next_set() { + if let Err(e) = Check::run(&mut options, all_targets, set) { + return Err(e); + } + } + } + } + Ok(()) + } } /// `ci-cargo` command to run. #[cfg_attr(test, derive(Debug, PartialEq))] @@ -860,6 +1005,8 @@ struct ArgOpts { dir: Option<PathBuf>, /// `--ignore-compile-errors`. ignore_compile_errors: bool, + /// `--ignore-msrv`. + ignore_msrv: bool, /// `--ignore-features` along with the features to ignore. ignore_features: Vec<String>, /// `--ignored`. @@ -904,6 +1051,7 @@ impl From<ArgOpts> for Opts { default_toolchain: value.default_toolchain, allow_implied_features: value.allow_implied_features, ignore_compile_errors: value.ignore_compile_errors, + ignore_msrv: value.ignore_msrv, progress: value.progress, skip_msrv: value.skip_msrv, summary: value.summary, @@ -1046,6 +1194,12 @@ impl MetaCmd { return Err(ArgsErr::DuplicateOption(val)); } } + IGNORE_MSRV => { + if opts.ignore_msrv { + return Err(ArgsErr::DuplicateOption(val)); + } + opts.ignore_msrv = true; + } IGNORED => { if opts.ignored { return Err(ArgsErr::DuplicateOption(val)); @@ -1099,6 +1253,10 @@ impl MetaCmd { ) } /// Returns data we need by reading the supplied CLI arguments. + #[expect( + clippy::too_many_lines, + reason = "expected even if we extract options separately" + )] pub(crate) fn from_args<T: Iterator<Item = OsString>>(mut args: T) -> Result<Self, ArgsErr> { args.next().ok_or(ArgsErr::NoArgs).and_then(|_| { args.next().map_or_else( @@ -1111,26 +1269,26 @@ impl MetaCmd { |arg| { if let Some(arg_str) = arg.to_str() { match arg_str { - H | HELP => { + HELP => { if args.next().is_none() { Ok(Self::Help) } else { Err(ArgsErr::HelpWithArgs) } } - V | VERSION => { + VERSION => { if args.next().is_none() { Ok(Self::Version) } else { Err(ArgsErr::VersionWithArgs) } } - C | CLIPPY => { + CLIPPY => { let mut opts = ArgOpts::default(); Self::extract_options(&mut opts, args.next(), &mut args).and_then( |()| { if opts.ignored || opts.include_ignored { - Err(ArgsErr::IgnoredClippyDoc) + Err(ArgsErr::IgnoredCheckClippyDoc) } else { Ok(Self::Cargo( Cmd::Clippy(opts.all_targets, opts.deny_warnings), @@ -1140,14 +1298,14 @@ impl MetaCmd { }, ) } - T | TESTS => { + TESTS => { let mut opts = ArgOpts::default(); Self::extract_options(&mut opts, args.next(), &mut args).and_then( |()| { if opts.all_targets { Err(ArgsErr::AllTargetsTests) } else if opts.deny_warnings { - Err(ArgsErr::DenyWarningsTests) + Err(ArgsErr::DenyWarningsCheckTests) } else { let ignored = opts.ignored(); Ok(Self::Cargo(Cmd::Tests(ignored), opts.into())) @@ -1155,22 +1313,39 @@ impl MetaCmd { }, ) } - D | DOC_TESTS => { + DOC_TESTS => { let mut opts = ArgOpts::default(); Self::extract_options(&mut opts, args.next(), &mut args).and_then( |()| { if opts.all_targets { Err(ArgsErr::AllTargetsTests) } else if opts.deny_warnings { - Err(ArgsErr::DenyWarningsTests) + Err(ArgsErr::DenyWarningsCheckTests) } else if opts.ignored || opts.include_ignored { - Err(ArgsErr::IgnoredClippyDoc) + Err(ArgsErr::IgnoredCheckClippyDoc) } else { Ok(Self::Cargo(Cmd::DocTests, opts.into())) } }, ) } + CHECK => { + let mut opts = ArgOpts::default(); + Self::extract_options(&mut opts, args.next(), &mut args).and_then( + |()| { + if opts.deny_warnings { + Err(ArgsErr::DenyWarningsCheckTests) + } else if opts.ignored || opts.include_ignored { + Err(ArgsErr::IgnoredCheckClippyDoc) + } else { + Ok(Self::Cargo( + Cmd::Check(opts.all_targets), + opts.into(), + )) + } + }, + ) + } _ => { let mut opts = ArgOpts::default(); Self::extract_options(&mut opts, Some(arg), &mut args).map(|()| { @@ -1221,6 +1396,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1269,7 +1445,7 @@ mod tests { MetaCmd::from_args( [ OsString::new(), - "t".to_owned().into(), + "tests".to_owned().into(), "--deny-warnings".to_owned().into(), "--deny-warnings".to_owned().into() ] @@ -1283,7 +1459,7 @@ mod tests { MetaCmd::from_args( [ OsString::new(), - "h".to_owned().into(), + "help".to_owned().into(), "--summary".to_owned().into() ] .into_iter() @@ -1317,7 +1493,7 @@ mod tests { MetaCmd::from_args( [ OsString::new(), - "t".to_owned().into(), + "tests".to_owned().into(), "--all-targets".to_owned().into() ] .into_iter() @@ -1328,7 +1504,7 @@ mod tests { MetaCmd::from_args( [ OsString::new(), - "d".to_owned().into(), + "doc-tests".to_owned().into(), "--all-targets".to_owned().into() ] .into_iter() @@ -1344,7 +1520,7 @@ mod tests { ] .into_iter() ), - Err(ArgsErr::DenyWarningsTests) + Err(ArgsErr::DenyWarningsCheckTests) ); assert_eq!( MetaCmd::from_args( @@ -1355,29 +1531,51 @@ mod tests { ] .into_iter() ), - Err(ArgsErr::DenyWarningsTests) + Err(ArgsErr::DenyWarningsCheckTests) + ); + assert_eq!( + MetaCmd::from_args( + [ + OsString::new(), + "check".to_owned().into(), + "--deny-warnings".to_owned().into() + ] + .into_iter() + ), + Err(ArgsErr::DenyWarningsCheckTests) ); assert_eq!( MetaCmd::from_args( [ OsString::new(), - "c".to_owned().into(), + "clippy".to_owned().into(), "--ignored".to_owned().into() ] .into_iter() ), - Err(ArgsErr::IgnoredClippyDoc) + Err(ArgsErr::IgnoredCheckClippyDoc) ); assert_eq!( MetaCmd::from_args( [ OsString::new(), - "d".to_owned().into(), + "doc-tests".to_owned().into(), "--ignored".to_owned().into() ] .into_iter() ), - Err(ArgsErr::IgnoredClippyDoc) + Err(ArgsErr::IgnoredCheckClippyDoc) + ); + assert_eq!( + MetaCmd::from_args( + [ + OsString::new(), + "check".to_owned().into(), + "--ignored".to_owned().into() + ] + .into_iter() + ), + Err(ArgsErr::IgnoredCheckClippyDoc) ); assert_eq!( MetaCmd::from_args( @@ -1388,7 +1586,7 @@ mod tests { ] .into_iter() ), - Err(ArgsErr::IgnoredClippyDoc) + Err(ArgsErr::IgnoredCheckClippyDoc) ); assert_eq!( MetaCmd::from_args( @@ -1399,7 +1597,18 @@ mod tests { ] .into_iter() ), - Err(ArgsErr::IgnoredClippyDoc) + Err(ArgsErr::IgnoredCheckClippyDoc) + ); + assert_eq!( + MetaCmd::from_args( + [ + OsString::new(), + "check".to_owned().into(), + "--include-ignored".to_owned().into() + ] + .into_iter() + ), + Err(ArgsErr::IgnoredCheckClippyDoc) ); assert_eq!( MetaCmd::from_args( @@ -1505,6 +1714,7 @@ mod tests { "--ignore-compile-errors".to_owned().into(), "--ignore-features".to_owned().into(), "--include-ignored".to_owned().into(), + "--ignore-msrv".to_owned().into(), "--rustup-home".to_owned().into(), OsString::new(), "--progress".to_owned().into(), @@ -1524,6 +1734,7 @@ mod tests { default_toolchain: true, allow_implied_features: true, ignore_compile_errors: true, + ignore_msrv: true, progress: true, skip_msrv: true, summary: true, @@ -1535,7 +1746,7 @@ mod tests { MetaCmd::from_args( [ OsString::new(), - "c".to_owned().into(), + "clippy".to_owned().into(), "--all-targets".to_owned().into(), "--allow-implied-features".to_owned().into(), "--cargo-home".to_owned().into(), @@ -1550,6 +1761,7 @@ mod tests { "--ignore-compile-errors".to_owned().into(), "--ignore-features".to_owned().into(), ",a".to_owned().into(), + "--ignore-msrv".to_owned().into(), "--rustup-home".to_owned().into(), "a".to_owned().into(), "--progress".to_owned().into(), @@ -1569,6 +1781,7 @@ mod tests { default_toolchain: true, allow_implied_features: true, ignore_compile_errors: true, + ignore_msrv: true, progress: true, skip_msrv: true, summary: true, @@ -1597,6 +1810,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1621,6 +1835,7 @@ mod tests { "--ignore-compile-errors".to_owned().into(), "--ignore-features".to_owned().into(), OsString::new(), + "--ignore-msrv".to_owned().into(), "--ignored".to_owned().into(), "--rustup-home".to_owned().into(), OsString::new(), @@ -1641,6 +1856,7 @@ mod tests { default_toolchain: true, allow_implied_features: true, ignore_compile_errors: true, + ignore_msrv: true, progress: true, skip_msrv: true, summary: true, @@ -1649,7 +1865,7 @@ mod tests { )) ); assert_eq!( - MetaCmd::from_args([OsString::new(), "t".to_owned().into(),].into_iter()), + MetaCmd::from_args([OsString::new(), "tests".to_owned().into(),].into_iter()), Ok(MetaCmd::Cargo( Cmd::Tests(Ignored::None), Opts { @@ -1661,6 +1877,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1672,7 +1889,7 @@ mod tests { MetaCmd::from_args( [ OsString::new(), - "t".to_owned().into(), + "tests".to_owned().into(), "--include-ignored".to_owned().into() ] .into_iter() @@ -1688,6 +1905,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1712,6 +1930,7 @@ mod tests { "--ignore-compile-errors".to_owned().into(), "--ignore-features".to_owned().into(), "a,".to_owned().into(), + "--ignore-msrv".to_owned().into(), "--rustup-home".to_owned().into(), OsString::new(), "--progress".to_owned().into(), @@ -1731,6 +1950,7 @@ mod tests { default_toolchain: true, allow_implied_features: true, ignore_compile_errors: true, + ignore_msrv: true, progress: true, skip_msrv: true, summary: true, @@ -1739,7 +1959,7 @@ mod tests { )) ); assert_eq!( - MetaCmd::from_args([OsString::new(), "d".to_owned().into(),].into_iter()), + MetaCmd::from_args([OsString::new(), "doc-tests".to_owned().into(),].into_iter()), Ok(MetaCmd::Cargo( Cmd::DocTests, Opts { @@ -1751,6 +1971,74 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, + progress: false, + skip_msrv: false, + summary: false, + ignore_features: Vec::new(), + } + )) + ); + assert_eq!( + MetaCmd::from_args( + [ + OsString::new(), + "check".to_owned().into(), + "--all-targets".to_owned().into(), + "--allow-implied-features".to_owned().into(), + "--cargo-home".to_owned().into(), + "--ignored".to_owned().into(), + "--cargo-path".to_owned().into(), + "cargo".to_owned().into(), + "--color".to_owned().into(), + "--default-toolchain".to_owned().into(), + "--dir".to_owned().into(), + OsString::new(), + "--ignore-compile-errors".to_owned().into(), + "--ignore-features".to_owned().into(), + "a,".to_owned().into(), + "--ignore-msrv".to_owned().into(), + "--rustup-home".to_owned().into(), + OsString::new(), + "--progress".to_owned().into(), + "--skip-msrv".to_owned().into(), + "--summary".to_owned().into(), + ] + .into_iter() + ), + Ok(MetaCmd::Cargo( + Cmd::Check(true), + Opts { + exec_dir: Some(PathBuf::new()), + rustup_home: Some(PathBuf::new()), + cargo_home: Some("--ignored".to_owned().into()), + cargo_path: "cargo/cargo".to_owned().into(), + color: true, + default_toolchain: true, + allow_implied_features: true, + ignore_compile_errors: true, + ignore_msrv: true, + progress: true, + skip_msrv: true, + summary: true, + ignore_features: vec!["a".to_owned(), String::new()], + } + )) + ); + assert_eq!( + MetaCmd::from_args([OsString::new(), "check".to_owned().into(),].into_iter()), + Ok(MetaCmd::Cargo( + Cmd::Check(false), + Opts { + exec_dir: None, + rustup_home: None, + cargo_home: None, + cargo_path: "cargo".to_owned().into(), + color: false, + default_toolchain: false, + allow_implied_features: false, + ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1778,6 +2066,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1805,6 +2094,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1832,6 +2122,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1860,6 +2151,7 @@ mod tests { default_toolchain: false, allow_implied_features: false, ignore_compile_errors: false, + ignore_msrv: false, progress: false, skip_msrv: false, summary: false, @@ -1873,18 +2165,10 @@ mod tests { )) ); assert_eq!( - MetaCmd::from_args([OsString::new(), "h".to_owned().into(),].into_iter()), - Ok(MetaCmd::Help) - ); - assert_eq!( MetaCmd::from_args([OsString::new(), "help".to_owned().into(),].into_iter()), Ok(MetaCmd::Help) ); assert_eq!( - MetaCmd::from_args([OsString::new(), "v".to_owned().into(),].into_iter()), - Ok(MetaCmd::Version) - ); - assert_eq!( MetaCmd::from_args([OsString::new(), "version".to_owned().into(),].into_iter()), Ok(MetaCmd::Version) ); diff --git a/src/cargo.rs b/src/cargo.rs @@ -217,19 +217,25 @@ pub(crate) struct Version { /// Patch version. pub patch: u64, } +/// `"+stable"`. +const PLUS_STABLE: &str = "+stable"; /// `"RUSTUP_HOME"`. const RUSTUP_HOME: &str = "RUSTUP_HOME"; /// `"CARGO_HOME"`. const CARGO_HOME: &str = "CARGO_HOME"; -/// `"-q"`. -const DASH_Q: &str = "-q"; /// Toolchain to use. +#[expect( + variant_size_differences, + reason = "fine. This doesn't get triggered if the other variants were unit variants despite Toolchain being the same size." +)] #[derive(Clone, Copy)] pub(crate) enum Toolchain<'a> { /// `cargo +stable`. Stable, /// `cargo`. - Default, + /// + /// Contained `bool` is `true` iff `--ignore-rust-version` should be passed. + Default(bool), /// `cargo +<MSRV>`. Msrv(&'a str), } @@ -306,7 +312,7 @@ impl Toolchain<'_> { Self::Stable => { _ = cmd.arg(PLUS_STABLE); } - Self::Default => {} + Self::Default(_) => {} Self::Msrv(val) => { _ = cmd.arg(val); } @@ -337,8 +343,10 @@ impl Toolchain<'_> { } } } -/// `"+stable"`. -const PLUS_STABLE: &str = "+stable"; +/// `"-p"`. +const DASH_P: &str = "-p"; +/// `"-q"`. +const DASH_Q: &str = "-q"; /// `"--color"`. const DASH_DASH_COLOR: &str = "--color"; /// `"always"`. @@ -347,14 +355,16 @@ const ALWAYS: &str = "always"; const NEVER: &str = "never"; /// `"--no-default-features"`. const DASH_DASH_NO_DEFAULT_FEATURES: &str = "--no-default-features"; -/// `"-F"`. -const DASH_F: &str = "-F"; +/// `"--features"`. +const DASH_DASH_FEATURES: &str = "--features"; /// `"--"`. const DASH_DASH: &str = "--"; /// `"default"`. const DEFAULT: &str = "default"; +/// `"--ignore-rust-version"`. +const DASH_DASH_IGNORE_RUST_VERSION: &str = "--ignore-rust-version"; /// Common options to pass to [`Clippy::run`] and [`Tests::run`]. -pub(crate) struct Options<'toolchain, 'errs> { +pub(crate) struct Options<'toolchain, 'package, 'errs> { /// The `cargo` toolchain to use. pub toolchain: Toolchain<'toolchain>, /// The path to the `rustup` storage directory. @@ -363,6 +373,8 @@ pub(crate) struct Options<'toolchain, 'errs> { pub cargo_path: PathBuf, /// The path to the `cargo` storage directory. pub cargo_home: Option<PathBuf>, + /// Name of the package. + pub package_name: &'package str, /// `true` iff color should be written to `stdout` and `stderr`. pub color: bool, /// `true` iff `compile_error`s should be ignored. @@ -377,7 +389,7 @@ pub(crate) struct Options<'toolchain, 'errs> { /// Returns `true` iff a no-library target error occurred and `doc_only` was `true`. fn execute_command( mut cmd: Command, - options: &mut Options<'_, '_>, + options: &mut Options<'_, '_, '_>, features: &str, doc_only: bool, ) -> Result<bool, Box<CargoErr>> { @@ -479,7 +491,7 @@ impl Clippy { reason = "want to crash when there is a bug" )] pub(crate) fn run( - options: &mut Options<'_, '_>, + options: &mut Options<'_, '_, '_>, all_targets: bool, deny_warnings: bool, features: &str, @@ -492,25 +504,34 @@ impl Clippy { if let Some(ref env) = options.cargo_home { _ = c.env(CARGO_HOME, env); } - match options.toolchain { + let ignore_msrv = match options.toolchain { Toolchain::Stable => { _ = c.arg(PLUS_STABLE); + false } - Toolchain::Default => {} + Toolchain::Default(ignore_msrv) => ignore_msrv, Toolchain::Msrv(ref msrv) => { _ = c.arg(msrv); + false } - } - _ = c.arg("clippy").arg(DASH_Q); + }; + _ = c + .arg("clippy") + .arg(DASH_P) + .arg(options.package_name) + .arg(DASH_Q); if all_targets { _ = c.arg("--all-targets"); } + if ignore_msrv { + _ = c.arg(DASH_DASH_IGNORE_RUST_VERSION); + } _ = c .arg(DASH_DASH_COLOR) .arg(if options.color { ALWAYS } else { NEVER }) .arg(DASH_DASH_NO_DEFAULT_FEATURES); if !features.is_empty() { - _ = c.arg(DASH_F).arg(features); + _ = c.arg(DASH_DASH_FEATURES).arg(features); } if deny_warnings { _ = c.arg(DASH_DASH).arg("-Dwarnings"); @@ -529,14 +550,14 @@ pub(crate) enum TestKind { /// Only doc tests. Doc, } -/// `cargo t --tests/--doc`. +/// `cargo test --tests/--doc`. pub(crate) struct Tests; impl Tests { - /// Execute `cargo t`. + /// Execute `cargo test`. /// /// Returns `true` iff only doc tests were run and a no-library error was returned. pub(crate) fn run( - options: &mut Options<'_, '_>, + options: &mut Options<'_, '_, '_>, kind: TestKind, features: &str, ) -> Result<bool, Box<CargoErr>> { @@ -552,23 +573,31 @@ impl Tests { if let Some(ref env) = options.cargo_home { _ = c.env(CARGO_HOME, env); } - match options.toolchain { + let ignore_msrv = match options.toolchain { Toolchain::Stable => { _ = c.arg(PLUS_STABLE); + false } - Toolchain::Default => {} + Toolchain::Default(ignore_msrv) => ignore_msrv, Toolchain::Msrv(ref msrv) => { _ = c.arg(msrv); + false } + }; + _ = c + .arg("test") + .arg(DASH_P) + .arg(options.package_name) + .arg(DASH_Q); + if ignore_msrv { + _ = c.arg(DASH_DASH_IGNORE_RUST_VERSION); } _ = c - .arg("t") - .arg(DASH_Q) .arg(DASH_DASH_COLOR) .arg(if options.color { ALWAYS } else { NEVER }) .arg(DASH_DASH_NO_DEFAULT_FEATURES); if !features.is_empty() { - _ = c.arg(DASH_F).arg(features); + _ = c.arg(DASH_DASH_FEATURES).arg(features); } let mut doc_only = false; match kind { @@ -616,6 +645,63 @@ impl Tests { execute_command(c, options, features, doc_only) } } +/// `cargo check`. +pub(crate) struct Check; +impl Check { + /// Execute `cargo check`. + /// + /// Returns `false` iff the command ran successfully. Note this can only return `true` if + /// [`Options::ignore_compile_errors`] is `true` since an error would be returned instead. + #[expect( + clippy::panic_in_result_fn, + reason = "want to crash when there is a bug" + )] + pub(crate) fn run( + options: &mut Options<'_, '_, '_>, + all_targets: bool, + features: &str, + ) -> Result<(), Box<CargoErr>> { + let mut c = Command::new(options.cargo_path.as_path()); + _ = c.stderr(Stdio::piped()).stdin(Stdio::null()); + if let Some(ref env) = options.rustup_home { + _ = c.env(RUSTUP_HOME, env); + } + if let Some(ref env) = options.cargo_home { + _ = c.env(CARGO_HOME, env); + } + let ignore_msrv = match options.toolchain { + Toolchain::Stable => { + _ = c.arg(PLUS_STABLE); + false + } + Toolchain::Default(ignore_msrv) => ignore_msrv, + Toolchain::Msrv(ref msrv) => { + _ = c.arg(msrv); + false + } + }; + _ = c + .arg("check") + .arg(DASH_P) + .arg(options.package_name) + .arg(DASH_Q); + if all_targets { + _ = c.arg("--all-targets"); + } + if ignore_msrv { + _ = c.arg(DASH_DASH_IGNORE_RUST_VERSION); + } + _ = c + .arg(DASH_DASH_COLOR) + .arg(if options.color { ALWAYS } else { NEVER }) + .arg(DASH_DASH_NO_DEFAULT_FEATURES); + if !features.is_empty() { + _ = c.arg(DASH_DASH_FEATURES).arg(features); + } + execute_command(c, options, features, false) + .map(|no_library_target| assert!(!no_library_target, "there is a bug in cargo::execute_command since a no-library target error was returned when running Check.")) + } +} #[cfg(test)] mod tests { use super::{Command, Toolchain, ToolchainErr, Version}; diff --git a/src/main.rs b/src/main.rs @@ -42,10 +42,12 @@ enum E { SetDir(Error, PathBuf), /// Error reading `Cargo.toml`. CargoTomlRead(Error, PathBuf), - /// Error looking for `rust-toolchain.toml`. - RustToolchainTomlIo(Error, PathBuf), /// Error related to extracting the necessary data from `Cargo.toml`. Manifest(Box<ManifestErr>), + /// Error looking for `rust-toolchain.toml`. + RustToolchainTomlIo(Error, PathBuf), + /// Error when `--ignore-msrv` was passed when using the `stable` toolchain. + IgnoreMsrvStable, /// Error from `Msrv::compare_to_other`. Toolchain(Box<ToolchainErr>), /// Error from OpenBSD `pledge`. @@ -104,13 +106,14 @@ impl E { Self::CargoTomlRead(err, p) => { writeln!(stderr, "There was an error reading {}: {err}.", p.display()) } + Self::Manifest(e) => e.write(stderr), Self::RustToolchainTomlIo(err, p) => { writeln!( stderr, "There was an error looking for rust-toolchain.toml in {} and its ancestor directories: {err}.", p.display() ) } - Self::Manifest(e) => e.write(stderr), + Self::IgnoreMsrvStable => writeln!(stderr, "--ignore-msrv was passed when using the stable toolchain."), Self::Toolchain(e) => e.write(stderr), #[cfg(target_os = "openbsd")] Self::Pledge(e) => writeln!(stderr, "pledge(2) erred: {e}."), @@ -260,20 +263,20 @@ fn main() -> ExitCode { } fs::read_to_string(&cur_dir).map_err(|e| E::CargoTomlRead(e, cur_dir.clone())).and_then(|toml| Manifest::from_toml(toml, opts.allow_implied_features, &cur_dir, &opts.ignore_features).map_err(E::Manifest).and_then(|man| { if opts.default_toolchain || (!rustup::SUPPORTED && opts.rustup_home.is_none()) { - Ok(Toolchain::Default) + Ok(Toolchain::Default(opts.ignore_msrv)) } else { let mut cargo_toml_path = cur_dir.clone(); _ = cargo_toml_path.pop(); - get_path_of_file(&mut cargo_toml_path, rust_toolchain_toml()).map_err(|e| E::RustToolchainTomlIo(e, cargo_toml_path)).map(|rust_toolchain_exists| if rust_toolchain_exists { Toolchain::Default } else { Toolchain::Stable }) - }.and_then(|toolchain| priv_sep_final(&mut proms, &opts.cargo_path).and_then(|()| man.msrv().map_or(Ok(None), |msrv| if !opts.skip_msrv && (rustup::SUPPORTED || opts.rustup_home.is_some()) { - msrv.compare_to_other(matches!(toolchain, Toolchain::Default), opts.rustup_home.as_deref(), &opts.cargo_path, opts.cargo_home.as_deref()).map_err(E::Toolchain) + get_path_of_file(&mut cargo_toml_path, rust_toolchain_toml()).map_err(|e| E::RustToolchainTomlIo(e, cargo_toml_path)).and_then(|rust_toolchain_exists| if rust_toolchain_exists { Ok(Toolchain::Default(opts.ignore_msrv)) } else if opts.ignore_msrv { Err(E::IgnoreMsrvStable) } else { Ok(Toolchain::Stable) }) + }.and_then(|toolchain| priv_sep_final(&mut proms, &opts.cargo_path).and_then(|()| man.package().msrv().map_or(Ok(None), |msrv| if !opts.skip_msrv && (rustup::SUPPORTED || opts.rustup_home.is_some()) { + msrv.compare_to_other(matches!(toolchain, Toolchain::Default(_)), opts.rustup_home.as_deref(), &opts.cargo_path, opts.cargo_home.as_deref()).map_err(E::Toolchain) } else { Ok(None) }).and_then(|msrv_string| { let default_feature_does_not_exist = !man.features().contains_default(); man.features().power_set(skip_no_feats).map_err(|_e| E::TooManyFeatures(cur_dir)).and_then(|power_set_opt| power_set_opt.map_or_else(|| Ok(()), |mut power_set| { let mut non_term_errs = HashSet::new(); - cmd.run(Options { toolchain, rustup_home: opts.rustup_home, cargo_path: opts.cargo_path, cargo_home: opts.cargo_home, color: opts.color, ignore_compile_errors: opts.ignore_compile_errors, default_feature_does_not_exist, non_terminating_errors: &mut non_term_errs, }, msrv_string.as_deref(), &mut power_set, opts.progress).map_err(E::Cargo).and_then(|()| { + cmd.run(Options { toolchain, rustup_home: opts.rustup_home, cargo_path: opts.cargo_path, cargo_home: opts.cargo_home, package_name: man.package().name(), color: opts.color, ignore_compile_errors: opts.ignore_compile_errors, default_feature_does_not_exist, non_terminating_errors: &mut non_term_errs, }, msrv_string.as_deref(), &mut power_set, opts.progress).map_err(E::Cargo).and_then(|()| { if non_term_errs.is_empty() { Ok(()) } else { diff --git a/src/manifest.rs b/src/manifest.rs @@ -81,6 +81,8 @@ impl WorkspaceErr { } } } +/// `"name"`. +const NAME: &str = "name"; /// Error returned from extracting `"package"`. #[cfg_attr(test, derive(Debug))] pub(crate) enum PackageErr { @@ -88,6 +90,10 @@ pub(crate) enum PackageErr { Missing, /// Variant returned when `"package"` is not a table. InvalidType, + /// Variant returned when `packagen.name` does not exist. + MissingName, + /// Variant returned when `package.name` is not a string. + InvalidNameType, /// Variant returned when `package.rust-version` is not a string nor table. InvalidMsrvType, /// Variant returned when `package.rust-version` is not a valid MSRV. @@ -131,6 +137,12 @@ impl PackageErr { "'{PACKAGE}' exists but is not a table in {}.", file.display() ), + Self::MissingName => writeln!(stderr, "'{PACKAGE}.{NAME}' does not exist in {}.", file.display()), + Self::InvalidNameType => writeln!( + stderr, + "'{PACKAGE}.{NAME}' exists but is not a string in {}.", + file.display() + ), Self::InvalidMsrvType => writeln!( stderr, "'{PACKAGE}.{RUST_VERSION}' exists but is not a string nor table in {}.", @@ -373,7 +385,7 @@ impl ImpliedFeaturesErr { DependenciesErr::ImpliedFeature(name, dep_name) => { writeln!( stderr, - "'{name}.{dep_name}' causes an implied feature to be defined in {}, but implied features were forbidden.", + "'{name}.{dep_name}' causes an implied feature to be defined in {}, but implied features were forbidden. --allow-implied-features can be passed to allow it.", file.display() ) } @@ -420,7 +432,7 @@ impl ImpliedFeaturesErr { DependenciesErr::ImpliedFeature(table_name, dep_name) => { writeln!( stderr, - "'{TARGET}.{name}.{table_name}.{dep_name}' causes an implied feature to be defined in {}, but implied features were forbidden.", + "'{TARGET}.{name}.{table_name}.{dep_name}' causes an implied feature to be defined in {}, but implied features were forbidden. --allow-implied-features can be passed to allow it.", file.display() ) } @@ -461,7 +473,7 @@ impl ManifestErr { Self::ImpliedFeatures(e, file) => e.write(stderr, &file), Self::UndefinedIgnoreFeature(feature, file) => writeln!( stderr, - "The feature '{feature}' was requested to be ignored, but it is not a feature in the package file {}. --allow-implied-features may need to be passed if this feature is an implied one.", + "The feature '{feature}' was requested to be ignored, but it is not a feature in the package file {}.", file.display() ), } @@ -628,71 +640,66 @@ impl Msrv { } } } - /// Extracts `"package"` from `toml` before extracting `"rust-version"` from it. + /// Extracts `"rust-version"` from `"package"` or `toml` in the case it's defined in a workspace. #[expect( clippy::panic_in_result_fn, reason = "want to crash when there is a bug" )] fn extract_from_toml( toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, + package: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, cargo_toml: &Path, ) -> Result<Option<Self>, PackageErr> { - toml.get(PACKAGE) - .ok_or(PackageErr::Missing) - .and_then(|pack_span| { - if let DeValue::Table(ref package) = *pack_span.get_ref() { - package.get(RUST_VERSION).map_or(Ok(None), |msrv_span| { - match *msrv_span.get_ref() { - DeValue::String(ref msrv) => Self::extract_msrv(msrv) - .map_err(|()| PackageErr::Msrv) - .map(Some), - DeValue::Table(ref msrv) => msrv - .get(WORKSPACE) - .ok_or(PackageErr::MsrvWorkspaceMissing) - .and_then(|work| { - if matches!(*work.get_ref(), DeValue::Boolean(b) if b) { - package.get(WORKSPACE).map_or_else( - || if toml.contains_key(WORKSPACE) { - Self::extract_workspace(toml).map_err(|e| PackageErr::Workspace(e, cargo_toml.to_path_buf())).map(Some) - } else { - let mut search_path = cargo_toml.to_path_buf(); - assert!(search_path.pop(), "there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Carg.toml."); - if search_path.pop() { - Self::get_workspace_toml(search_path).map(Some) - } else { - Err(PackageErr::WorkspaceDoesNotExist) - } - }, - |path_span| { - if let DeValue::String(ref workspace_path) = *path_span.get_ref() { - let mut path = cargo_toml.to_path_buf(); - assert!(path.pop(), "there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Carg.toml."); - path.push(workspace_path.as_ref()); - path.push(super::cargo_toml()); - fs::read_to_string(&path).map_err(|e| PackageErr::WorkspaceRead(e, path.clone())).and_then(|workspace_file| Map::parse(&workspace_file).map_err(|e| PackageErr::WorkspaceToml(e, path.clone())).and_then(|workspace_toml| Self::extract_workspace(workspace_toml.get_ref()).map_err(|e| PackageErr::Workspace(e, path)).map(Some))) - } else { - Err(PackageErr::InvalidWorkspaceType) - } - }, - ) + package.get(RUST_VERSION).map_or(Ok(None), |msrv_span| { + match *msrv_span.get_ref() { + DeValue::String(ref msrv) => Self::extract_msrv(msrv) + .map_err(|()| PackageErr::Msrv) + .map(Some), + DeValue::Table(ref msrv) => msrv + .get(WORKSPACE) + .ok_or(PackageErr::MsrvWorkspaceMissing) + .and_then(|work| { + if matches!(*work.get_ref(), DeValue::Boolean(b) if b) { + package.get(WORKSPACE).map_or_else( + || if toml.contains_key(WORKSPACE) { + Self::extract_workspace(toml).map_err(|e| PackageErr::Workspace(e, cargo_toml.to_path_buf())).map(Some) + } else { + let mut search_path = cargo_toml.to_path_buf(); + assert!(search_path.pop(), "there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml."); + if search_path.pop() { + Self::get_workspace_toml(search_path).map(Some) } else { - Err(PackageErr::MsrvWorkspaceVal) + Err(PackageErr::WorkspaceDoesNotExist) } - }), - DeValue::Integer(_) - | DeValue::Float(_) - | DeValue::Boolean(_) - | DeValue::Datetime(_) - | DeValue::Array(_) => Err(PackageErr::InvalidMsrvType), + }, + |path_span| { + if let DeValue::String(ref workspace_path) = *path_span.get_ref() { + let mut path = cargo_toml.to_path_buf(); + assert!(path.pop(), "there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml."); + path.push(workspace_path.as_ref()); + path.push(super::cargo_toml()); + fs::read_to_string(&path).map_err(|e| PackageErr::WorkspaceRead(e, path.clone())).and_then(|workspace_file| Map::parse(&workspace_file).map_err(|e| PackageErr::WorkspaceToml(e, path.clone())).and_then(|workspace_toml| Self::extract_workspace(workspace_toml.get_ref()).map_err(|e| PackageErr::Workspace(e, path)).map(Some))) + } else { + Err(PackageErr::InvalidWorkspaceType) + } + }, + ) + } else { + Err(PackageErr::MsrvWorkspaceVal) } - }) - } else { - Err(PackageErr::InvalidType) - } - }) + }), + DeValue::Integer(_) + | DeValue::Float(_) + | DeValue::Boolean(_) + | DeValue::Datetime(_) + | DeValue::Array(_) => Err(PackageErr::InvalidMsrvType), + } + }) } - /// Returns `Some` containing the MSRV with `'+'` prepended iff the stable or default toolchain is semantically - /// greater than `self`; otherwise returns `None`. + /// Returns `Some` containing the MSRV with `'+'` prepended iff `stable` is semantically greater than `self` + /// or the default toolchan is semantically not equivalent to `self`; otherwise returns `None`. + /// + /// When `stable` is semantically less than the MSRV, an error is returned. pub(crate) fn compare_to_other( &self, default: bool, @@ -701,7 +708,7 @@ impl Msrv { cargo_home: Option<&Path>, ) -> Result<Option<String>, Box<ToolchainErr>> { if default { - Toolchain::Default + Toolchain::Default(false) } else { Toolchain::Stable } @@ -718,13 +725,31 @@ impl Msrv { |pat| match pat.cmp(&stable_dflt_version.patch) { Ordering::Less => Ok(true), Ordering::Equal => Ok(false), - Ordering::Greater => Err(Box::new(ToolchainErr::MsrvTooHigh)), + Ordering::Greater => { + if default { + Ok(true) + } else { + Err(Box::new(ToolchainErr::MsrvTooHigh)) + } + } }, ), - Ordering::Greater => Err(Box::new(ToolchainErr::MsrvTooHigh)), + Ordering::Greater => { + if default { + Ok(true) + } else { + Err(Box::new(ToolchainErr::MsrvTooHigh)) + } + } }, ), - Ordering::Greater => Err(Box::new(ToolchainErr::MsrvTooHigh)), + Ordering::Greater => { + if default { + Ok(true) + } else { + Err(Box::new(ToolchainErr::MsrvTooHigh)) + } + } } .and_then(|get_msrv| { if get_msrv { @@ -762,6 +787,53 @@ impl Msrv { }) } } +/// `package` info. +#[cfg_attr(test, derive(Debug, PartialEq))] +pub(crate) struct Package { + /// `rust-version`. + msrv: Option<Msrv>, + /// `name`. + name: String, +} +impl Package { + /// MSRV. + pub(crate) const fn msrv(&self) -> Option<&Msrv> { + self.msrv.as_ref() + } + /// Name. + pub(crate) const fn name(&self) -> &str { + self.name.as_str() + } + /// Extracts `"package"` from `toml`. + fn extract_from_toml( + toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, + cargo_toml: &Path, + ) -> Result<Self, PackageErr> { + toml.get(PACKAGE) + .ok_or(PackageErr::Missing) + .and_then(|pack_span| { + if let DeValue::Table(ref package) = *pack_span.get_ref() { + package + .get(NAME) + .ok_or(PackageErr::MissingName) + .and_then(|name_span| { + if let DeValue::String(ref name) = *name_span.get_ref() { + Msrv::extract_from_toml(toml, package, cargo_toml).map(|msrv| { + Self { + msrv, + name: name.clone().into_owned(), + } + }) + } else { + Err(PackageErr::InvalidNameType) + } + }) + } else { + Err(PackageErr::InvalidType) + } + }) + } +} /// Returns `true` iff `nodes` is pairwise disconnected. #[expect( clippy::arithmetic_side_effects, @@ -1505,18 +1577,18 @@ impl Features { PowerSet::new(self, skip_no_feats) } } -/// MSRV and features in `Cargo.toml`. +/// Package and features in `Cargo.toml`. #[cfg_attr(test, derive(Debug, PartialEq))] pub(crate) struct Manifest { - /// The MSRV. - msrv: Option<Msrv>, + /// The package. + package: Package, /// The features. features: Features, } impl Manifest { /// Returns the defined MSRV iff there was one defined. - pub(crate) const fn msrv(&self) -> Option<&Msrv> { - self.msrv.as_ref() + pub(crate) const fn package(&self) -> &Package { + &self.package } /// Returns the defined features. /// @@ -1546,9 +1618,9 @@ impl Manifest { .map_err(|e| Box::new(ManifestErr::Toml(e, cargo_toml.to_path_buf()))) .and_then(|span| { let cargo = span.get_ref(); - Msrv::extract_from_toml(cargo, cargo_toml) + Package::extract_from_toml(cargo, cargo_toml) .map_err(|e| Box::new(ManifestErr::Package(e, cargo_toml.to_path_buf()))) - .and_then(|msrv| { + .and_then(|package| { Features::extract_from_toml(cargo, allow_implied_features) .map_err(|e| { Box::new(ManifestErr::Features(e, cargo_toml.to_path_buf())) @@ -1619,7 +1691,7 @@ impl Manifest { !info.1.iter().any(|d| d == ig_feat) }); }); - Self { msrv, features } + Self { package, features } }) }) }) @@ -1631,14 +1703,16 @@ impl Manifest { mod tests { use super::{ DependenciesErr, FeatureDependenciesErr, Features, FeaturesErr, ImpliedFeaturesErr, - Manifest, ManifestErr, Msrv, NonZeroUsizePlus1, PackageErr, Path, PathBuf, PowerSet, - TooManyFeaturesErr, WorkspaceErr, + Manifest, ManifestErr, Msrv, NonZeroUsizePlus1, Package, PackageErr, Path, PathBuf, + PowerSet, TooManyFeaturesErr, WorkspaceErr, }; impl PartialEq for PackageErr { fn eq(&self, other: &Self) -> bool { match *self { Self::Missing => matches!(*other, Self::Missing), Self::InvalidType => matches!(*other, Self::InvalidType), + Self::MissingName => matches!(*other, Self::MissingName), + Self::InvalidNameType => matches!(*other, Self::InvalidNameType), Self::InvalidMsrvType => matches!(*other, Self::InvalidMsrvType), Self::Msrv => matches!(*other, Self::Msrv), Self::MsrvWorkspaceMissing => matches!(*other, Self::MsrvWorkspaceMissing), @@ -1700,8 +1774,22 @@ mod tests { ))) ); assert_eq!( + Manifest::from_toml("[package]".to_owned(), false, Path::new(""), &[]), + Err(Box::new(ManifestErr::Package( + PackageErr::MissingName, + PathBuf::new() + ))) + ); + assert_eq!( + Manifest::from_toml("[package]\nname=true".to_owned(), false, Path::new(""), &[]), + Err(Box::new(ManifestErr::Package( + PackageErr::InvalidNameType, + PathBuf::new() + ))) + ); + assert_eq!( Manifest::from_toml( - "[package]\nrust-version=2".to_owned(), + "[package]\nname=\"\"\n\nrust-version=2".to_owned(), false, Path::new(""), &[] @@ -1713,7 +1801,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"\"".to_owned(), false, Path::new(""), &[] @@ -1725,7 +1813,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"a\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"a\"".to_owned(), false, Path::new(""), &[] @@ -1737,7 +1825,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"1.00.0\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"1.00.0\"".to_owned(), false, Path::new(""), &[] @@ -1749,7 +1837,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"1..0\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"1..0\"".to_owned(), false, Path::new(""), &[] @@ -1761,7 +1849,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"1.\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"1.\"".to_owned(), false, Path::new(""), &[] @@ -1773,7 +1861,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"01.0.0\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"01.0.0\"".to_owned(), false, Path::new(""), &[] @@ -1785,7 +1873,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"1.0.0.1\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"1.0.0.1\"".to_owned(), false, Path::new(""), &[] @@ -1797,7 +1885,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"111111111111111111111111.2.3\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"111111111111111111111111.2.3\"".to_owned(), false, Path::new(""), &[] @@ -1809,7 +1897,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"1.0.0-nightly\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"1.0.0-nightly\"".to_owned(), false, Path::new(""), &[] @@ -1821,7 +1909,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"-1.0.0\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"-1.0.0\"".to_owned(), false, Path::new(""), &[] @@ -1833,7 +1921,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\" 1.0.0\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\" 1.0.0\"".to_owned(), false, Path::new(""), &[] @@ -1845,7 +1933,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"1.0.0 \"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"1.0.0 \"".to_owned(), false, Path::new(""), &[] @@ -1857,7 +1945,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version={}".to_owned(), + "[package]\nname=\"\"\nrust-version={}".to_owned(), false, Path::new(""), &[] @@ -1869,7 +1957,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version={workspace=2}".to_owned(), + "[package]\nname=\"\"\nrust-version={workspace=2}".to_owned(), false, Path::new(""), &[] @@ -1881,7 +1969,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version={workspace=false}".to_owned(), + "[package]\nname=\"\"\nrust-version={workspace=false}".to_owned(), false, Path::new(""), &[] @@ -1893,7 +1981,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version={workspace=true}\nworkspace=2".to_owned(), + "[package]\nname=\"\"\nrust-version={workspace=true}\nworkspace=2".to_owned(), false, Path::new(""), &[] @@ -1905,7 +1993,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "workspace=2\n[package]\nrust-version={workspace=true}".to_owned(), + "workspace=2\n[package]\nname=\"\"\nrust-version={workspace=true}".to_owned(), false, Path::new(""), &[] @@ -1917,7 +2005,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[workspace]\n[package]\nrust-version={workspace=true}".to_owned(), + "[workspace]\n[package]\nname=\"\"\nrust-version={workspace=true}".to_owned(), false, Path::new(""), &[] @@ -1929,7 +2017,8 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[workspace]\npackage=2\n[package]\nrust-version={workspace=true}".to_owned(), + "[workspace]\npackage=2\n[package]\nname=\"\"\nrust-version={workspace=true}" + .to_owned(), false, Path::new(""), &[] @@ -1941,7 +2030,8 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[workspace.package]\n[package]\nrust-version={workspace=true}".to_owned(), + "[workspace.package]\n[package]\nname=\"\"\nrust-version={workspace=true}" + .to_owned(), false, Path::new(""), &[] @@ -1953,7 +2043,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[workspace.package]\nrust-version={}\n[package]\nrust-version={workspace=true}" + "[workspace.package]\nrust-version={}\n[package]\nname=\"\"\nrust-version={workspace=true}" .to_owned(), false, Path::new(""), @@ -1966,7 +2056,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[workspace.package]\nrust-version=\"\"\n[package]\nrust-version={workspace=true}" + "[workspace.package]\nrust-version=\"\"\n[package]\nname=\"\"\nrust-version={workspace=true}" .to_owned(), false, Path::new(""), @@ -1979,7 +2069,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "features=2\n[package]".to_owned(), + "features=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -1991,7 +2081,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"/\"=[]\n[package]".to_owned(), + "[features]\n\"/\"=[]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2003,7 +2093,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"dep:\"=[]\n[package]".to_owned(), + "[features]\n\"dep:\"=[]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2015,7 +2105,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=2\n[package]".to_owned(), + "[features]\n\"\"=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2029,7 +2119,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[true]\n[package]".to_owned(), + "[features]\n\"\"=[true]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2043,7 +2133,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"foo\"]\n[package]".to_owned(), + "[features]\n\"\"=[\"foo\"]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2059,7 +2149,7 @@ mod tests { // Feature dependencies can't be implied features when implied features are forbidden. assert_eq!( Manifest::from_toml( - "[dependencies]\nfoo={optional=true}\n[features]\n\"\"=[\"foo\"]\n[package]" + "[dependencies]\nfoo={optional=true}\n[features]\n\"\"=[\"foo\"]\n[package]\nname=\"\"" .to_owned(), false, Path::new(""), @@ -2075,7 +2165,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"\"]\n[package]".to_owned(), + "[features]\n\"\"=[\"\"]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2089,7 +2179,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"a\"]\na=[\"\"]\n[package]".to_owned(), + "[features]\n\"\"=[\"a\"]\na=[\"\"]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2103,7 +2193,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"a\"]\na=[\"b\"]\nb=[\"a\"]\n[package]".to_owned(), + "[features]\n\"\"=[\"a\"]\na=[\"b\"]\nb=[\"a\"]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2117,7 +2207,8 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"a\"]\na=[\"c\",\"b\"]\nb=[\"a\"]\nc=[]\n[package]".to_owned(), + "[features]\n\"\"=[\"a\"]\na=[\"c\",\"b\"]\nb=[\"a\"]\nc=[]\n[package]\nname=\"\"" + .to_owned(), false, Path::new(""), &[] @@ -2131,7 +2222,8 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[]\na=[\"c\",\"b\"]\nb=[\"a\"]\nc=[]\n[package]".to_owned(), + "[features]\n\"\"=[]\na=[\"c\",\"b\"]\nb=[\"a\"]\nc=[]\n[package]\nname=\"\"" + .to_owned(), false, Path::new(""), &[] @@ -2145,7 +2237,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"a\",\"b\"]\na=[\"b\"]\nb=[]\n[package]".to_owned(), + "[features]\n\"\"=[\"a\",\"b\"]\na=[\"b\"]\nb=[]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2160,7 +2252,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"a\",\"a\"]\na=[]\n[package]".to_owned(), + "[features]\n\"\"=[\"a\",\"a\"]\na=[]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2176,7 +2268,7 @@ mod tests { // Duplicate `"dep:"` feature dependencies error. assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"dep:\",\"dep:\"]\na=[]\n[package]".to_owned(), + "[features]\n\"\"=[\"dep:\",\"dep:\"]\na=[]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2190,7 +2282,12 @@ mod tests { ))) ); assert_eq!( - Manifest::from_toml("target=2\n[package]".to_owned(), false, Path::new(""), &[]), + Manifest::from_toml( + "target=2\n[package]\nname=\"\"".to_owned(), + false, + Path::new(""), + &[] + ), Err(Box::new(ManifestErr::ImpliedFeatures( ImpliedFeaturesErr::TargetType, PathBuf::new() @@ -2198,7 +2295,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "dependencies=2\n[package]".to_owned(), + "dependencies=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2210,7 +2307,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "build-dependencies=2\n[package]".to_owned(), + "build-dependencies=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2222,7 +2319,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[dependencies]\n\"dep:\"=\"\"\n[package]".to_owned(), + "[dependencies]\n\"dep:\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2237,7 +2334,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[dependencies]\n\"/\"=\"\"\n[package]".to_owned(), + "[dependencies]\n\"/\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2252,7 +2349,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[build-dependencies]\n\"dep:\"=\"\"\n[package]".to_owned(), + "[build-dependencies]\n\"dep:\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2267,7 +2364,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[build-dependencies]\n\"/\"=\"\"\n[package]".to_owned(), + "[build-dependencies]\n\"/\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2282,7 +2379,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[dependencies]\n\"\"=2\n[package]".to_owned(), + "[dependencies]\n\"\"=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2297,7 +2394,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[build-dependencies]\n\"\"=2\n[package]".to_owned(), + "[build-dependencies]\n\"\"=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2312,7 +2409,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[dependencies]\n\"\"={optional=2}\n[package]".to_owned(), + "[dependencies]\n\"\"={optional=2}\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2327,7 +2424,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[build-dependencies]\n\"\"={optional=2}\n[package]".to_owned(), + "[build-dependencies]\n\"\"={optional=2}\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2343,7 +2440,7 @@ mod tests { // Implied features are disallowed iff `!allow_implied_features`. assert_eq!( Manifest::from_toml( - "[dependencies]\nfoo={optional=true}\n[package]".to_owned(), + "[dependencies]\nfoo={optional=true}\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2358,7 +2455,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target]\n\"\"=2\n[package]".to_owned(), + "[target]\n\"\"=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2370,7 +2467,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\"]\ndependencies=2\n[package]".to_owned(), + "[target.\"\"]\ndependencies=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2385,7 +2482,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\"]\nbuild-dependencies=2\n[package]".to_owned(), + "[target.\"\"]\nbuild-dependencies=2\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2400,7 +2497,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".dependencies]\n\"/\"=\"\"\n[package]".to_owned(), + "[target.\"\".dependencies]\n\"/\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2415,7 +2512,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".dependencies]\n\"dep:\"=\"\"\n[package]".to_owned(), + "[target.\"\".dependencies]\n\"dep:\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2430,7 +2527,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".build-dependencies]\n\"/\"=\"\"\n[package]".to_owned(), + "[target.\"\".build-dependencies]\n\"/\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2445,7 +2542,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".build-dependencies]\n\"dep:\"=\"\"\n[package]".to_owned(), + "[target.\"\".build-dependencies]\n\"dep:\"=\"\"\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2460,7 +2557,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".dependencies]\n\"\"=false\n[package]".to_owned(), + "[target.\"\".dependencies]\n\"\"=false\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2475,7 +2572,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".build-dependencies]\n\"\"=false\n[package]".to_owned(), + "[target.\"\".build-dependencies]\n\"\"=false\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2490,7 +2587,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".dependencies]\n\"\"={optional=2}\n[package]".to_owned(), + "[target.\"\".dependencies]\n\"\"={optional=2}\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2505,7 +2602,8 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[target.\"\".build-dependencies]\n\"\"={optional=2}\n[package]".to_owned(), + "[target.\"\".build-dependencies]\n\"\"={optional=2}\n[package]\nname=\"\"" + .to_owned(), false, Path::new(""), &[] @@ -2522,7 +2620,7 @@ mod tests { // implied features aren't added until after feature extraction. assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"foo\"]\n[package]".to_owned(), + "[features]\n\"\"=[\"foo\"]\n[package]\nname=\"\"".to_owned(), true, Path::new(""), &[] @@ -2535,7 +2633,7 @@ mod tests { // In contrast, above would have erred sooner if `!allow_implied_features`. assert_eq!( Manifest::from_toml( - "[features]\n\"\"=[\"foo\"]\n[package]".to_owned(), + "[features]\n\"\"=[\"foo\"]\n[package]\nname=\"\"".to_owned(), false, Path::new(""), &[] @@ -2550,7 +2648,7 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]".to_owned(), + "[package]\nname=\"\"".to_owned(), false, Path::new(""), &["a".to_owned()] @@ -2568,71 +2666,76 @@ mod tests { // the explict feature `foo` and the implied feature from the dependency `foo`. assert_eq!( Manifest::from_toml( - "[dependencies]\nfoo={optional=true}\n[features]\nfoo=[]\n[package]".to_owned(), + "[dependencies]\nfoo={optional=true}\n[features]\nfoo=[]\n[package]\nname=\"\"" + .to_owned(), false, Path::new(""), &[] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![("foo".to_owned(), Vec::new())]), }) ); - // Allow empty `package`. - assert_eq!( - Manifest::from_toml("[package]".to_owned(), false, Path::new(""), &[]), - Ok(Manifest { - msrv: None, - features: Features(Vec::new()), - }) - ); // Allow major-only MSRV. assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"0\"".to_owned(), + "[package]\nname=\"foo\"\nrust-version=\"0\"".to_owned(), false, Path::new(""), &[] ), Ok(Manifest { - msrv: Some(Msrv { - major: 0, - minor: None, - patch: None, - }), + package: Package { + msrv: Some(Msrv { + major: 0, + minor: None, + patch: None, + }), + name: "foo".to_owned(), + }, features: Features(Vec::new()), }) ); // Allow escapes. assert_eq!( Manifest::from_toml( - "[\"\\u0070ackage\"]\n\"\\u0072ust-version\"=\"0\\u002E\\u0031\"".to_owned(), + "[\"\\u0070ackage\"]\n\"n\\u0061me\"=\"\\u0066oo\"\n\"\\u0072ust-version\"=\"0\\u002E\\u0031\"".to_owned(), false, Path::new(""), &[] ), Ok(Manifest { - msrv: Some(Msrv { - major: 0, - minor: Some(1), - patch: None, - }), + package: Package { + msrv: Some(Msrv { + major: 0, + minor: Some(1), + patch: None, + }), + name: "foo".to_owned(), + }, features: Features(Vec::new()), }) ); assert_eq!( Manifest::from_toml( - "[package]\nrust-version=\"0.0.0\"".to_owned(), + "[package]\nname=\"\"\nrust-version=\"0.0.0\"".to_owned(), false, Path::new(""), &[] ), Ok(Manifest { - msrv: Some(Msrv { - major: 0, - minor: Some(0), - patch: Some(0), - }), + package: Package { + msrv: Some(Msrv { + major: 0, + minor: Some(0), + patch: Some(0), + }), + name: String::new(), + }, features: Features(Vec::new()), }) ); @@ -2641,18 +2744,21 @@ mod tests { // `target.<something>` unless the key is `dependencies` or `build-dependencies`. Don't treat // `<something>` special in `target.<something>` other than its being a table. assert_eq!( - Manifest::from_toml("dev-dependencies=2\n[package]\nfoo=2\nrust-version=\"18446744073709551615.18446744073709551615.18446744073709551615\"\n[foo]\nbar=false\n[target.\"\".foo]\nbar=2\n[target.foo]\nbar=false\n[target.dependencies]\nfoo=2\n[target.build-dependencies]\nfoo=false\n[target.dev-dependencies]\nfoo=true\n".to_owned(), false, Path::new(""), &[]), + Manifest::from_toml("dev-dependencies=2\n[package]\nname=\"\"\n\nfoo=2\nrust-version=\"18446744073709551615.18446744073709551615.18446744073709551615\"\n[foo]\nbar=false\n[target.\"\".foo]\nbar=2\n[target.foo]\nbar=false\n[target.dependencies]\nfoo=2\n[target.build-dependencies]\nfoo=false\n[target.dev-dependencies]\nfoo=true\n".to_owned(), false, Path::new(""), &[]), Ok(Manifest { - - msrv: Some(Msrv { - major: u64::MAX, - minor: Some(u64::MAX), - patch: Some(u64::MAX), - }), + package: Package { + msrv: Some(Msrv { + major: u64::MAX, + minor: Some(u64::MAX), + patch: Some(u64::MAX), + }), + name: String::new(), + }, features: Features(Vec::new()), }) ); // [package] + // name = "" // // ["\u0064ependencies"] // "\u0000" = "\u0000" @@ -2702,17 +2808,21 @@ mod tests { // 6. (c, []) assert_eq!( Manifest::from_toml( - "[\"\\u0064ependencies\"]\n\"\\u0000\"=\"\\u0000\"\na={optional=true}\n[\"build-\\u0064ependencies\"]\n\"\\u0000\"={optional=true}\n[target.\"\".dependencies]\nb={optional=false,foo=2}\nfizz={optional=true,foo=3}\n[features]\ndefault=[\"bar\",\"dep:lk\",\"a/ak\",\"a/ak\"]\nbar=[\"dep\\u003Awuzz\"]\n[dev-dependencies]\nbuzz={optional=true}\n[target.a.dependencies]\nc={optional=true}\nwuzz={optional=true}\n[package]".to_owned(), + "[\"\\u0064ependencies\"]\n\"\\u0000\"=\"\\u0000\"\na={optional=true}\n[\"build-\\u0064ependencies\"]\n\"\\u0000\"={optional=true}\n[target.\"\".dependencies]\nb={optional=false,foo=2}\nfizz={optional=true,foo=3}\n[features]\ndefault=[\"bar\",\"dep:lk\",\"a/ak\",\"a/ak\"]\nbar=[\"dep\\u003Awuzz\"]\n[dev-dependencies]\nbuzz={optional=true}\n[target.a.dependencies]\nc={optional=true}\nwuzz={optional=true}\n[package]\nname=\"\"".to_owned(), true, Path::new(""), &[] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![("bar".to_owned(), Vec::new()), ("default".to_owned(), vec!["bar".to_owned()]), ("a".to_owned(), Vec::new()), ("\0".to_owned(), Vec::new()), ("fizz".to_owned(), Vec::new()), ("c".to_owned(), Vec::new())]), }) ); // [package] + // name = "" // // [dependencies] // foo = { "optional" = true } @@ -2723,13 +2833,16 @@ mod tests { // bar = ["dep:foo"] assert_eq!( Manifest::from_toml( - "[package]\n[dependencies]\nfoo={optional=true}\nfizz={optional=true}\n[features]\nfizz=[\"dep:fizz\"]\nbar=[\"dep:foo\"]".to_owned(), + "[package]\nname=\"\"\n[dependencies]\nfoo={optional=true}\nfizz={optional=true}\n[features]\nfizz=[\"dep:fizz\"]\nbar=[\"dep:foo\"]".to_owned(), false, Path::new(""), &[] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![ ("bar".to_owned(), Vec::new()), ("fizz".to_owned(), Vec::new()) @@ -2737,6 +2850,7 @@ mod tests { }) ); // [package] + // name = "" // // [dependencies] // bar = { "optional" = true } @@ -2745,14 +2859,17 @@ mod tests { // foo = ["bar"] assert_eq!( Manifest::from_toml( - "[package]\n[dependencies]\nbar={optional=true}\n[features]\nfoo=[\"bar\"]" + "[package]\nname=\"\"\n[dependencies]\nbar={optional=true}\n[features]\nfoo=[\"bar\"]" .to_owned(), true, Path::new(""), &[] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![ ("foo".to_owned(), vec!["bar".to_owned()]), ("bar".to_owned(), Vec::new()), @@ -2761,49 +2878,63 @@ mod tests { ); assert_eq!( Manifest::from_toml( - "[package]\n[features]\na=[]\nb=[\"a\"]".to_owned(), + "[package]\nname=\"\"\n[features]\na=[]\nb=[\"a\"]".to_owned(), false, Path::new(""), &["a".to_owned()] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![]), }) ); assert_eq!( Manifest::from_toml( - "[package]\n[features]\na=[]\nb=[\"a\"]".to_owned(), + "[package]\nname=\"\"\n[features]\na=[]\nb=[\"a\"]".to_owned(), false, Path::new(""), &["b".to_owned()] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![("a".to_owned(), Vec::new())]), }) ); assert_eq!( Manifest::from_toml( - "[package]\n[dependencies]\nc={optional=true}\n[features]\nb=[\"c\"]".to_owned(), + "[package]\nname=\"\"\n[dependencies]\nc={optional=true}\n[features]\nb=[\"c\"]" + .to_owned(), true, Path::new(""), &["c".to_owned()] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![]), }) ); assert_eq!( Manifest::from_toml( - "[package]\n[dependencies]\nc={optional=true}\n[features]\nb=[\"c\"]".to_owned(), + "[package]\nname=\"\"\n[dependencies]\nc={optional=true}\n[features]\nb=[\"c\"]" + .to_owned(), true, Path::new(""), &["b".to_owned()] ), Ok(Manifest { - msrv: None, + package: Package { + msrv: None, + name: String::new(), + }, features: Features(vec![("c".to_owned(), Vec::new())]), }) );