README.md (21789B)
1 CI app for Rust code 2 ==================== 3 4 [<img alt="git" src="https://git.philomathiclife.com/badges/ci-cargo.svg" height="20">](https://git.philomathiclife.com/ci-cargo/log.html) 5 [<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) 6 7 `ci-cargo` is a CLI application that runs [`cargo`](https://doc.rust-lang.org/cargo/index.html) with `check`, 8 `clippy`, `test --tests`, and `test --doc` for all possible combinations of features defined in `Cargo.toml`. 9 10 The toolchain(s) used depend on platform support for [`rustup`](https://rust-lang.github.io/rustup/), the existence 11 of `rust-toolchain.toml`, the defined MSRV (if there is one), and if `--default-toolchain`, `--skip-msrv`, or 12 `--rustup-home` were passed. Specifically `cargo +stable` will be used if all of the following conditions are met: 13 14 * `--default-toolchain` was not passed. 15 * `rust-toolchain.toml` does not exist in the package directory nor its ancestor directories. 16 * `--rustup-home` was not passed for platforms that don't support `rustup`. 17 18 If the above are not met, `cargo` will be used instead. `cargo +<MSRV>` will also be used if all of the following 19 conditions are met: 20 21 * `--skip-msrv` was not passed. 22 * Package has an MSRV defined via `rust-version` that is less than the `stable` or not equivalent to the default 23 toolchain used. 24 * `--rustup-home` was passed or the platform supports `rustup`. 25 26 `ci-cargo` avoids superfluous combinations of features. For example if feature `foo` depends on feature `bar` and 27 `bar` depends on feature `fizz`; then no combination of features that contain `foo` and `bar`, `foo` and `fizz`, or 28 `bar` and `fizz` will be tested. 29 30 When a command errors, `ci-cargo` will terminate; upon termination (successful or not), `ci-cargo` will write all 31 _unique_ messages that were written to `stderr` to `stderr` followed by the offending command in case of an error. 32 33 ## Why is this useful? 34 35 The number of possible configurations grows exponentially based on the number of features in `Cargo.toml`. This can 36 easily cause a crate to not be tested with certain combinations of features. Instead of manually invoking `cargo` 37 with each possible combination of features, this handles it automatically. Additionally it automatically ensures the 38 build works on both the stable or default toolchain _and_ the stated MSRV (if one is defined). 39 40 ## Commands 41 42 * `<none>`: `cargo clippy` and `cargo test` are invoked for each combination of features. 43 * `help`: Prints help message. 44 * `version`: Prints version info. 45 * `check`: `cargo check` is invoked for each combination of features. 46 * `clippy`: `cargo clippy` is invoked for each combination of features. 47 * `tests`: `cargo test --tests` is invoked for each combination of features. 48 * `doc-tests`: `cargo test --doc` is invoked for each combination of features. 49 50 ## Options 51 52 * `--all-targets`: `cargo clippy --all-targets` or `cargo check --all-targets` is invoked for each combination of 53 features. 54 * `--allow-implied-features`: Features implied by optional dependencies are allowed; by default features must be 55 explicitly defined or an error will occur (e.g., `foo = ["dep:bar"]`). 56 * `--cargo-home <PATH>`: Sets the storage directory used by `cargo`. 57 * `--cargo-path <PATH>`: Sets the directory to search for `cargo`. Defaults to `cargo`. 58 * `--color`: `--color always` is passed to the above commands; otherwise without this option, `--color never` is 59 passed. 60 * `--default-toolchain`: `cargo` is used instead of `cargo +stable`. 61 * `--deny-warnings`: `cargo clippy -- --Dwarnings` is invoked for each combination of features. 62 * `--dir <PATH>`: Changes the working directory to the passed path before executing. Without this, the current 63 directory and all ancestor directories are searched for `Cargo.toml` before changing the working directory to its 64 location. 65 * `--ignore-compile-errors`: [`compile_error`](https://doc.rust-lang.org/core/macro.compile_error.html)s are ignored 66 and don't lead to termination. 67 * `--ignore-features <FEATURES>`: Any combination of features that depend on any of the features in the 68 comma-separated list will be ignored. The features must be unique and represent valid features in the package. The 69 features can contain implied features so long as `--allow-implied-features` is also passed. An empty value 70 represents the empty set of features (i.e., `--no-default-features`). For example `--ignore-features`, will error 71 since no features were passed. `--ignore-features ''` will ignore the empty set of features. `--ignore-features a,` 72 will ignore the empty set of features and any combination of features that depend on feature `a`. 73 * `--ignore-msrv`: `--ignore-rust-version` is passed to the commands for the default toolchain. 74 * `--ignored`: `cargo test -- --ignored` is invoked for each combination of features. 75 * `--include-ignored`: `cargo test -- --include-ignored` is invoked for each combination of features. 76 * `--progress`: Writes the current progress to `stdout`. 77 * `--rustup-home <PATH>`: Sets the storage directory used by `rustup`. 78 * `--skip-msrv`: `cargo +<MSRV>` is not used. 79 * `--summary`: Writes the toolchain(s) used and the combinations of features run on to `stdout` on success. 80 81 Any unique sequence of the above options are allowed to be passed after the command so long as the following 82 conditions are met: 83 84 * No options are allowed for the `help` or `version` commands. 85 * `--all-targets` is allowed iff `check`, `clippy`, or no command is passed. 86 * `--deny-warnings` is allowed iff `clippy` or no command is passed. 87 * `--ignored` is allowed iff `tests` or no command is passed and `--include-ignored` is not passed. 88 * `--include-ignored` is allowed iff `tests` or no command is passed and `--ignored` is not passed. 89 * `--ignore-msrv` is allowed iff the `stable` toolchain is used. 90 91 ## `ci-cargo` in action 92 93 ```bash 94 [zack@laptop example]$ cat Cargo.toml 95 [package] 96 authors = ["Johann Carl Friedrich Gauß <gauss@invalid.com>"] 97 categories = ["mathematics"] 98 description = "Example." 99 documentation = "https://example.com/" 100 edition = "2024" 101 keywords = ["example"] 102 license = "MIT OR Apache-2.0" 103 name = "example" 104 readme = "README.md" 105 repository = "https://example.com/" 106 rust-version = "1.89.0" 107 version = "0.1.0" 108 109 [lints.rust] 110 ambiguous_negative_literals = { level = "deny", priority = -1 } 111 closure_returning_async_block = { level = "deny", priority = -1 } 112 deprecated_safe = { level = "deny", priority = -1 } 113 deref_into_dyn_supertrait = { level = "deny", priority = -1 } 114 ffi_unwind_calls = { level = "deny", priority = -1 } 115 future_incompatible = { level = "deny", priority = -1 } 116 impl_trait_redundant_captures = { level = "deny", priority = -1 } 117 keyword_idents = { level = "deny", priority = -1 } 118 let_underscore = { level = "deny", priority = -1 } 119 linker_messages = { level = "deny", priority = -1 } 120 macro_use_extern_crate = { level = "deny", priority = -1 } 121 meta_variable_misuse = { level = "deny", priority = -1 } 122 missing_copy_implementations = { level = "deny", priority = -1 } 123 missing_debug_implementations = { level = "deny", priority = -1 } 124 missing_docs = { level = "deny", priority = -1 } 125 non_ascii_idents = { level = "deny", priority = -1 } 126 nonstandard_style = { level = "deny", priority = -1 } 127 redundant_imports = { level = "deny", priority = -1 } 128 redundant_lifetimes = { level = "deny", priority = -1 } 129 refining_impl_trait = { level = "deny", priority = -1 } 130 rust_2018_compatibility = { level = "deny", priority = -1 } 131 rust_2018_idioms = { level = "deny", priority = -1 } 132 rust_2021_compatibility = { level = "deny", priority = -1 } 133 rust_2024_compatibility = { level = "deny", priority = -1 } 134 single_use_lifetimes = { level = "deny", priority = -1 } 135 trivial_casts = { level = "deny", priority = -1 } 136 trivial_numeric_casts = { level = "deny", priority = -1 } 137 unit_bindings = { level = "deny", priority = -1 } 138 unknown-or-malformed-diagnostic-attributes = { level = "deny", priority = -1 } 139 unnameable_types = { level = "deny", priority = -1 } 140 unreachable_pub = { level = "deny", priority = -1 } 141 unsafe_code = { level = "deny", priority = -1 } 142 unstable_features = { level = "deny", priority = -1 } 143 unused = { level = "deny", priority = -1 } 144 unused_crate_dependencies = { level = "deny", priority = -1 } 145 unused_import_braces = { level = "deny", priority = -1 } 146 unused_lifetimes = { level = "deny", priority = -1 } 147 unused_qualifications = { level = "deny", priority = -1 } 148 unused_results = { level = "deny", priority = -1 } 149 variant_size_differences = { level = "deny", priority = -1 } 150 warnings = { level = "deny", priority = -1 } 151 152 [lints.clippy] 153 cargo = { level = "deny", priority = -1 } 154 complexity = { level = "deny", priority = -1 } 155 correctness = { level = "deny", priority = -1 } 156 nursery = { level = "deny", priority = -1 } 157 pedantic = { level = "deny", priority = -1 } 158 perf = { level = "deny", priority = -1 } 159 restriction = { level = "deny", priority = -1 } 160 style = { level = "deny", priority = -1 } 161 suspicious = { level = "deny", priority = -1 } 162 blanket_clippy_restriction_lints = "allow" 163 implicit_return = "allow" 164 165 [dependencies] 166 buzz = { version = "0.1.0", default-features = false, optional = true } 167 168 [features] 169 buzz = ["dep:buzz"] 170 default = ["foo"] 171 foo = [] 172 bar = ["fizz"] 173 fizz = [] 174 [zack@laptop example]$ ci-cargo --all-targets --include-ignored --progress --summary 175 Package: example. Toolchain (1/2): cargo +stable. Features (1/32, 5 skipped): buzz,fizz,foo. Command (1/2): clippy. Time running: 0 s. 176 Package: example. Toolchain (1/2): cargo +stable. Features (1/32, 5 skipped): buzz,fizz,foo. Command (2/2): test. Time running: 0 s. 177 Package: example. Toolchain (1/2): cargo +stable. Features (2/32, 6 skipped): fizz,foo. Command (1/2): clippy. Time running: 0 s. 178 Package: example. Toolchain (1/2): cargo +stable. Features (2/32, 6 skipped): fizz,foo. Command (2/2): test. Time running: 0 s. 179 Package: example. Toolchain (1/2): cargo +stable. Features (3/32, 10 skipped): bar,buzz,foo. Command (1/2): clippy. Time running: 0 s. 180 Package: example. Toolchain (1/2): cargo +stable. Features (3/32, 10 skipped): bar,buzz,foo. Command (2/2): test. Time running: 0 s. 181 Package: example. Toolchain (1/2): cargo +stable. Features (4/32, 10 skipped): buzz,foo. Command (1/2): clippy. Time running: 0 s. 182 Package: example. Toolchain (1/2): cargo +stable. Features (4/32, 10 skipped): buzz,foo. Command (2/2): test. Time running: 0 s. 183 Package: example. Toolchain (1/2): cargo +stable. Features (5/32, 10 skipped): bar,foo. Command (1/2): clippy. Time running: 1 s. 184 Package: example. Toolchain (1/2): cargo +stable. Features (5/32, 10 skipped): bar,foo. Command (2/2): test. Time running: 1 s. 185 Package: example. Toolchain (1/2): cargo +stable. Features (6/32, 10 skipped): foo. Command (1/2): clippy. Time running: 1 s. 186 Package: example. Toolchain (1/2): cargo +stable. Features (6/32, 10 skipped): foo. Command (2/2): test. Time running: 1 s. 187 Package: example. Toolchain (1/2): cargo +stable. Features (7/32, 11 skipped): buzz,default,fizz. Command (1/2): clippy. Time running: 1 s. 188 Package: example. Toolchain (1/2): cargo +stable. Features (7/32, 11 skipped): buzz,default,fizz. Command (2/2): test. Time running: 1 s. 189 Package: example. Toolchain (1/2): cargo +stable. Features (8/32, 12 skipped): default,fizz. Command (1/2): clippy. Time running: 1 s. 190 Package: example. Toolchain (1/2): cargo +stable. Features (8/32, 12 skipped): default,fizz. Command (2/2): test. Time running: 1 s. 191 Package: example. Toolchain (1/2): cargo +stable. Features (9/32, 13 skipped): buzz,fizz. Command (1/2): clippy. Time running: 2 s. 192 Package: example. Toolchain (1/2): cargo +stable. Features (9/32, 13 skipped): buzz,fizz. Command (2/2): test. Time running: 2 s. 193 Package: example. Toolchain (1/2): cargo +stable. Features (10/32, 14 skipped): fizz. Command (1/2): clippy. Time running: 2 s. 194 Package: example. Toolchain (1/2): cargo +stable. Features (10/32, 14 skipped): fizz. Command (2/2): test. Time running: 2 s. 195 Package: example. Toolchain (1/2): cargo +stable. Features (11/32, 14 skipped): bar,buzz,default. Command (1/2): clippy. Time running: 2 s. 196 Package: example. Toolchain (1/2): cargo +stable. Features (11/32, 14 skipped): bar,buzz,default. Command (2/2): test. Time running: 2 s. 197 Package: example. Toolchain (1/2): cargo +stable. Features (12/32, 14 skipped): buzz,default. Command (1/2): clippy. Time running: 2 s. 198 Package: example. Toolchain (1/2): cargo +stable. Features (12/32, 14 skipped): buzz,default. Command (2/2): test. Time running: 2 s. 199 Package: example. Toolchain (1/2): cargo +stable. Features (13/32, 14 skipped): bar,default. Command (1/2): clippy. Time running: 3 s. 200 Package: example. Toolchain (1/2): cargo +stable. Features (13/32, 14 skipped): bar,default. Command (2/2): test. Time running: 3 s. 201 Package: example. Toolchain (1/2): cargo +stable. Features (14/32, 14 skipped): default. Command (1/2): clippy. Time running: 3 s. 202 Package: example. Toolchain (1/2): cargo +stable. Features (14/32, 14 skipped): default. Command (2/2): test. Time running: 3 s. 203 Package: example. Toolchain (1/2): cargo +stable. Features (15/32, 14 skipped): bar,buzz. Command (1/2): clippy. Time running: 3 s. 204 Package: example. Toolchain (1/2): cargo +stable. Features (15/32, 14 skipped): bar,buzz. Command (2/2): test. Time running: 3 s. 205 Package: example. Toolchain (1/2): cargo +stable. Features (16/32, 14 skipped): buzz. Command (1/2): clippy. Time running: 3 s. 206 Package: example. Toolchain (1/2): cargo +stable. Features (16/32, 14 skipped): buzz. Command (2/2): test. Time running: 3 s. 207 Package: example. Toolchain (1/2): cargo +stable. Features (17/32, 14 skipped): bar. Command (1/2): clippy. Time running: 4 s. 208 Package: example. Toolchain (1/2): cargo +stable. Features (17/32, 14 skipped): bar. Command (2/2): test. Time running: 4 s. 209 Package: example. Toolchain (1/2): cargo +stable. Features (18/32, 14 skipped): <none>. Command (1/2): clippy. Time running: 4 s. 210 Package: example. Toolchain (1/2): cargo +stable. Features (18/32, 14 skipped): <none>. Command (2/2): test. Time running: 4 s. 211 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. 212 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. 213 Package: example. Toolchain (2/2): cargo +1.89.0. Features (2/32, 6 skipped): fizz,foo. Command (1/2): clippy. Time running: 4 s. 214 Package: example. Toolchain (2/2): cargo +1.89.0. Features (2/32, 6 skipped): fizz,foo. Command (2/2): test. Time running: 4 s. 215 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. 216 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. 217 Package: example. Toolchain (2/2): cargo +1.89.0. Features (4/32, 10 skipped): buzz,foo. Command (1/2): clippy. Time running: 5 s. 218 Package: example. Toolchain (2/2): cargo +1.89.0. Features (4/32, 10 skipped): buzz,foo. Command (2/2): test. Time running: 5 s. 219 Package: example. Toolchain (2/2): cargo +1.89.0. Features (5/32, 10 skipped): bar,foo. Command (1/2): clippy. Time running: 5 s. 220 Package: example. Toolchain (2/2): cargo +1.89.0. Features (5/32, 10 skipped): bar,foo. Command (2/2): test. Time running: 5 s. 221 Package: example. Toolchain (2/2): cargo +1.89.0. Features (6/32, 10 skipped): foo. Command (1/2): clippy. Time running: 6 s. 222 Package: example. Toolchain (2/2): cargo +1.89.0. Features (6/32, 10 skipped): foo. Command (2/2): test. Time running: 6 s. 223 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. 224 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. 225 Package: example. Toolchain (2/2): cargo +1.89.0. Features (8/32, 12 skipped): default,fizz. Command (1/2): clippy. Time running: 6 s. 226 Package: example. Toolchain (2/2): cargo +1.89.0. Features (8/32, 12 skipped): default,fizz. Command (2/2): test. Time running: 6 s. 227 Package: example. Toolchain (2/2): cargo +1.89.0. Features (9/32, 13 skipped): buzz,fizz. Command (1/2): clippy. Time running: 6 s. 228 Package: example. Toolchain (2/2): cargo +1.89.0. Features (9/32, 13 skipped): buzz,fizz. Command (2/2): test. Time running: 7 s. 229 Package: example. Toolchain (2/2): cargo +1.89.0. Features (10/32, 14 skipped): fizz. Command (1/2): clippy. Time running: 7 s. 230 Package: example. Toolchain (2/2): cargo +1.89.0. Features (10/32, 14 skipped): fizz. Command (2/2): test. Time running: 7 s. 231 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. 232 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. 233 Package: example. Toolchain (2/2): cargo +1.89.0. Features (12/32, 14 skipped): buzz,default. Command (1/2): clippy. Time running: 7 s. 234 Package: example. Toolchain (2/2): cargo +1.89.0. Features (12/32, 14 skipped): buzz,default. Command (2/2): test. Time running: 8 s. 235 Package: example. Toolchain (2/2): cargo +1.89.0. Features (13/32, 14 skipped): bar,default. Command (1/2): clippy. Time running: 8 s. 236 Package: example. Toolchain (2/2): cargo +1.89.0. Features (13/32, 14 skipped): bar,default. Command (2/2): test. Time running: 8 s. 237 Package: example. Toolchain (2/2): cargo +1.89.0. Features (14/32, 14 skipped): default. Command (1/2): clippy. Time running: 8 s. 238 Package: example. Toolchain (2/2): cargo +1.89.0. Features (14/32, 14 skipped): default. Command (2/2): test. Time running: 8 s. 239 Package: example. Toolchain (2/2): cargo +1.89.0. Features (15/32, 14 skipped): bar,buzz. Command (1/2): clippy. Time running: 8 s. 240 Package: example. Toolchain (2/2): cargo +1.89.0. Features (15/32, 14 skipped): bar,buzz. Command (2/2): test. Time running: 8 s. 241 Package: example. Toolchain (2/2): cargo +1.89.0. Features (16/32, 14 skipped): buzz. Command (1/2): clippy. Time running: 9 s. 242 Package: example. Toolchain (2/2): cargo +1.89.0. Features (16/32, 14 skipped): buzz. Command (2/2): test. Time running: 9 s. 243 Package: example. Toolchain (2/2): cargo +1.89.0. Features (17/32, 14 skipped): bar. Command (1/2): clippy. Time running: 9 s. 244 Package: example. Toolchain (2/2): cargo +1.89.0. Features (17/32, 14 skipped): bar. Command (2/2): test. Time running: 9 s. 245 Package: example. Toolchain (2/2): cargo +1.89.0. Features (18/32, 14 skipped): <none>. Command (1/2): clippy. Time running: 9 s. 246 Package: example. Toolchain (2/2): cargo +1.89.0. Features (18/32, 14 skipped): <none>. Command (2/2): test. Time running: 9 s. 247 Toolchains used: cargo +stable and cargo +1.89.0 248 Features used: 249 buzz,fizz,foo 250 fizz,foo 251 bar,buzz,foo 252 buzz,foo 253 bar,foo 254 foo 255 buzz,default,fizz 256 default,fizz 257 buzz,fizz 258 fizz 259 bar,buzz,default 260 buzz,default 261 bar,default 262 default 263 bar,buzz 264 buzz 265 bar 266 <none> 267 [zack@laptop example]$ ci-cargo clippy --deny-warnings --ignore-compile-errors --ignore-features buzz, --ignore-msrv --skip-msrv 268 [zack@laptop ~]$ ci-cargo tests --allow-implied-features --cargo-home ~/.cargo/ --cargo-path ~/.cargo/bin --default-toolchain --dir ~/example/ --ignored --rustup-home ~/.rustup/ 269 [zack@laptop ~]$ ci-cargo version 270 ci-cargo 0.1.0 271 [zack@laptop example]$ ci-cargo --summary doc-tests 272 doc-tests is an unknown argument. See ci-cargo help for more information. 273 ``` 274 275 ## Limitations 276 277 Functionality when using old versions of `cargo` may not exist (e.g., `cargo check` wasn't available until 1.16.0). 278 279 There is a hard limit on the number of features allowed. Specifically the number of features can't exceed the 280 number of bits that make up a pointer; however practical limits will almost always be a factor long before hitting 281 such a hard limit due to the exponential effect features have. 282 283 Cyclic and redundant features are forbidden. For example the below snippets from `Cargo.toml` files will cause an 284 error: 285 286 ```toml 287 [features] 288 # Loops are forbidden by `cargo`, so this is not an additional limitation. 289 a = ["a"] 290 [features] 291 # Cyclic features are disallowed even though `cargo` allows them. 292 a = ["b"] 293 b = ["a"] 294 [features] 295 # `a` should more concisely be assigned `["b"]` since `b` already depends on `c`. 296 a = ["b", "c"] 297 b = ["c"] 298 c = [] 299 ``` 300 301 ## Minimum Supported Rust Version (MSRV) 302 303 This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that 304 update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV 305 will be updated. 306 307 MSRV changes will correspond to a SemVer patch version bump pre-`1.0.0`; otherwise a minor version bump. 308 309 ## SemVer Policy 310 311 * All on-by-default features of this library are covered by SemVer 312 * MSRV is considered exempt from SemVer as noted above 313 314 ## License 315 316 Licensed under either of 317 318 * Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0)) 319 * MIT license ([LICENSE-MIT](https://opensource.org/licenses/MIT)) 320 321 at your option. 322 323 ## Contribution 324 325 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, 326 as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 327 328 Before any PR is sent, `ci-cargo --all-targets --include-ignored` should be run on itself. Additionally 329 `cargo +nightly doc` should be run to ensure documentation can be built. 330 331 ### Status 332 333 The crate is only tested on the `x86_64-unknown-linux-gnu`, `x86_64-unknown-openbsd`, and `aarch64-apple-darwin` 334 targets; but it should work on most platforms.